mirror of
https://github.com/supabase/agent-skills.git
synced 2026-01-26 19:09:51 +08:00
automatically reorder sections on agents.md by priority
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ dist/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
.claude
|
.claude
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ npm run validate # Check rule format
|
|||||||
npm run build # Generate AGENTS.md
|
npm run build # Generate AGENTS.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Automatic Section Ordering**: The build system automatically reads section order
|
||||||
|
from `_sections.md`. To reorder sections (e.g., promoting Security from MEDIUM-HIGH
|
||||||
|
to CRITICAL priority), simply edit the section numbers in `_sections.md` and
|
||||||
|
rebuild. The section mapping is generated dynamically, so manual updates to
|
||||||
|
`config.ts` are no longer needed.
|
||||||
|
|
||||||
### Impact Levels
|
### Impact Levels
|
||||||
|
|
||||||
| Level | Improvement | Examples |
|
| Level | Improvement | Examples |
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ function parseSections(): Section[] {
|
|||||||
const content = readFileSync(sectionsFile, "utf-8");
|
const content = readFileSync(sectionsFile, "utf-8");
|
||||||
const sections: Section[] = [];
|
const sections: Section[] = [];
|
||||||
|
|
||||||
|
// Match format: Impact and Description on separate lines
|
||||||
|
// ## 1. Query Performance (query)
|
||||||
|
// **Impact:** CRITICAL
|
||||||
|
// **Description:** Description text
|
||||||
const sectionMatches = content.matchAll(
|
const sectionMatches = content.matchAll(
|
||||||
/##\s+(\d+)\.\s+([^\n(]+)\s*\((\w+)\)\s*\n\*\*Impact:\*\*\s*(\w+(?:-\w+)?)\s*\n\*\*Description:\*\*\s*([^\n]+)/g,
|
/##\s+(\d+)\.\s+([^\n(]+)\s*\((\w+)\)\s*\n\*\*Impact:\*\*\s*(\w+(?:-\w+)?)\s*\n\*\*Description:\*\*\s*([^\n]+)/g,
|
||||||
);
|
);
|
||||||
@@ -56,25 +60,25 @@ function getDefaultSections(): Section[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
number: 3,
|
number: 3,
|
||||||
|
title: "Security & RLS",
|
||||||
|
prefix: "security",
|
||||||
|
impact: "CRITICAL",
|
||||||
|
description: "Row-Level Security, privileges, auth patterns",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: 4,
|
||||||
title: "Schema Design",
|
title: "Schema Design",
|
||||||
prefix: "schema",
|
prefix: "schema",
|
||||||
impact: "HIGH",
|
impact: "HIGH",
|
||||||
description: "Table design, indexes, partitioning, data types",
|
description: "Table design, indexes, partitioning, data types",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
number: 4,
|
number: 5,
|
||||||
title: "Concurrency & Locking",
|
title: "Concurrency & Locking",
|
||||||
prefix: "lock",
|
prefix: "lock",
|
||||||
impact: "MEDIUM-HIGH",
|
impact: "MEDIUM-HIGH",
|
||||||
description: "Transactions, isolation, deadlocks",
|
description: "Transactions, isolation, deadlocks",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
number: 5,
|
|
||||||
title: "Security & RLS",
|
|
||||||
prefix: "security",
|
|
||||||
impact: "MEDIUM-HIGH",
|
|
||||||
description: "Row-Level Security, privileges, auth patterns",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
number: 6,
|
number: 6,
|
||||||
title: "Data Access Patterns",
|
title: "Data Access Patterns",
|
||||||
@@ -129,6 +133,19 @@ function toAnchor(text: string): string {
|
|||||||
.replace(/\s+/g, "-");
|
.replace(/\s+/g, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate SECTION_MAP from parsed sections
|
||||||
|
*/
|
||||||
|
export function generateSectionMap(
|
||||||
|
sections: Section[],
|
||||||
|
): Record<string, number> {
|
||||||
|
const map: Record<string, number> = {};
|
||||||
|
for (const section of sections) {
|
||||||
|
map[section.prefix] = section.number;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build AGENTS.md from all rule files
|
* Build AGENTS.md from all rule files
|
||||||
*/
|
*/
|
||||||
@@ -138,6 +155,7 @@ function buildAgents(): void {
|
|||||||
// Load metadata and sections
|
// Load metadata and sections
|
||||||
const metadata = loadMetadata();
|
const metadata = loadMetadata();
|
||||||
const sections = parseSections();
|
const sections = parseSections();
|
||||||
|
const sectionMap = generateSectionMap(sections);
|
||||||
|
|
||||||
// Get all rule files
|
// Get all rule files
|
||||||
const ruleFiles = readdirSync(RULES_DIR)
|
const ruleFiles = readdirSync(RULES_DIR)
|
||||||
@@ -152,7 +170,7 @@ function buildAgents(): void {
|
|||||||
const rules: Rule[] = [];
|
const rules: Rule[] = [];
|
||||||
|
|
||||||
for (const file of ruleFiles) {
|
for (const file of ruleFiles) {
|
||||||
const validation = validateRuleFile(file);
|
const validation = validateRuleFile(file, sectionMap);
|
||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
console.error(`Skipping invalid file ${basename(file)}:`);
|
console.error(`Skipping invalid file ${basename(file)}:`);
|
||||||
for (const e of validation.errors) {
|
for (const e of validation.errors) {
|
||||||
@@ -161,7 +179,7 @@ function buildAgents(): void {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = parseRuleFile(file);
|
const result = parseRuleFile(file, sectionMap);
|
||||||
if (result.success && result.rule) {
|
if (result.success && result.rule) {
|
||||||
rules.push(result.rule);
|
rules.push(result.rule);
|
||||||
}
|
}
|
||||||
@@ -303,4 +321,4 @@ if (isMainModule) {
|
|||||||
buildAgents();
|
buildAgents();
|
||||||
}
|
}
|
||||||
|
|
||||||
export { buildAgents };
|
export { buildAgents, parseSections };
|
||||||
|
|||||||
@@ -20,14 +20,17 @@ export const RULES_DIR = join(SKILL_DIR, "rules");
|
|||||||
export const AGENTS_OUTPUT = join(SKILL_DIR, "AGENTS.md");
|
export const AGENTS_OUTPUT = join(SKILL_DIR, "AGENTS.md");
|
||||||
export const METADATA_FILE = join(SKILL_DIR, "metadata.json");
|
export const METADATA_FILE = join(SKILL_DIR, "metadata.json");
|
||||||
|
|
||||||
// Section prefix to number mapping
|
// Section prefix to number mapping (DEPRECATED)
|
||||||
|
// This is kept as a fallback, but the build system now generates
|
||||||
|
// the section map dynamically from _sections.md.
|
||||||
|
// To reorder sections, simply change the order in _sections.md.
|
||||||
export const SECTION_MAP: Record<string, number> = {
|
export const SECTION_MAP: Record<string, number> = {
|
||||||
query: 1,
|
query: 1,
|
||||||
conn: 2,
|
conn: 2,
|
||||||
connection: 2,
|
connection: 2,
|
||||||
schema: 3,
|
security: 3,
|
||||||
lock: 4,
|
schema: 4,
|
||||||
security: 5,
|
lock: 5,
|
||||||
data: 6,
|
data: 6,
|
||||||
monitor: 7,
|
monitor: 7,
|
||||||
advanced: 8,
|
advanced: 8,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { basename } from "node:path";
|
import { basename } from "node:path";
|
||||||
import { IMPACT_LEVELS, SECTION_MAP } from "./config.js";
|
import { IMPACT_LEVELS } from "./config.js";
|
||||||
import type { CodeExample, ImpactLevel, ParseResult, Rule } from "./types.js";
|
import type { CodeExample, ImpactLevel, ParseResult, Rule } from "./types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,10 +48,13 @@ function parseFrontmatter(content: string): {
|
|||||||
/**
|
/**
|
||||||
* Extract section number from filename prefix
|
* Extract section number from filename prefix
|
||||||
*/
|
*/
|
||||||
function getSectionFromFilename(filename: string): number | null {
|
function getSectionFromFilename(
|
||||||
|
filename: string,
|
||||||
|
sectionMap: Record<string, number>,
|
||||||
|
): number | null {
|
||||||
const base = basename(filename, ".md");
|
const base = basename(filename, ".md");
|
||||||
const prefix = base.split("-")[0];
|
const prefix = base.split("-")[0];
|
||||||
return SECTION_MAP[prefix] ?? null;
|
return sectionMap[prefix] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -207,7 +210,10 @@ function extractReferences(body: string): string[] {
|
|||||||
/**
|
/**
|
||||||
* Parse a rule file and return structured data
|
* Parse a rule file and return structured data
|
||||||
*/
|
*/
|
||||||
export function parseRuleFile(filePath: string): ParseResult {
|
export function parseRuleFile(
|
||||||
|
filePath: string,
|
||||||
|
sectionMap: Record<string, number>,
|
||||||
|
): ParseResult {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
|
|
||||||
@@ -216,7 +222,7 @@ export function parseRuleFile(filePath: string): ParseResult {
|
|||||||
const { frontmatter, body } = parseFrontmatter(content);
|
const { frontmatter, body } = parseFrontmatter(content);
|
||||||
|
|
||||||
// Extract section from filename
|
// Extract section from filename
|
||||||
const section = getSectionFromFilename(filePath);
|
const section = getSectionFromFilename(filePath, sectionMap);
|
||||||
if (section === null) {
|
if (section === null) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Could not determine section from filename: ${basename(filePath)}`,
|
`Could not determine section from filename: ${basename(filePath)}`,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { readdirSync } from "node:fs";
|
import { readdirSync } from "node:fs";
|
||||||
import { basename, join } from "node:path";
|
import { basename, join } from "node:path";
|
||||||
|
import { generateSectionMap, parseSections } from "./build.js";
|
||||||
import { IMPACT_LEVELS, RULES_DIR } from "./config.js";
|
import { IMPACT_LEVELS, RULES_DIR } from "./config.js";
|
||||||
import { parseRuleFile } from "./parser.js";
|
import { parseRuleFile } from "./parser.js";
|
||||||
import type { ValidationResult } from "./types.js";
|
import type { ValidationResult } from "./types.js";
|
||||||
@@ -34,11 +35,20 @@ function isGoodExample(label: string): boolean {
|
|||||||
/**
|
/**
|
||||||
* Validate a single rule file
|
* Validate a single rule file
|
||||||
*/
|
*/
|
||||||
export function validateRuleFile(filePath: string): ValidationResult {
|
export function validateRuleFile(
|
||||||
|
filePath: string,
|
||||||
|
sectionMap?: Record<string, number>,
|
||||||
|
): ValidationResult {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
|
|
||||||
const result = parseRuleFile(filePath);
|
// Generate section map if not provided
|
||||||
|
if (!sectionMap) {
|
||||||
|
const sections = parseSections();
|
||||||
|
sectionMap = generateSectionMap(sections);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = parseRuleFile(filePath, sectionMap);
|
||||||
|
|
||||||
// Add parser errors and warnings
|
// Add parser errors and warnings
|
||||||
errors.push(...result.errors);
|
errors.push(...result.errors);
|
||||||
|
|||||||
@@ -30,22 +30,22 @@ Comprehensive Postgres performance optimization guide for developers using Supab
|
|||||||
- 2.4 [Use Prepared Statements Correctly with Pooling](#24-use-prepared-statements-correctly-with-pooling)
|
- 2.4 [Use Prepared Statements Correctly with Pooling](#24-use-prepared-statements-correctly-with-pooling)
|
||||||
|
|
||||||
3. [Security & RLS](#security-rls) - **CRITICAL**
|
3. [Security & RLS](#security-rls) - **CRITICAL**
|
||||||
- 3.1 [Choose Appropriate Data Types](#31-choose-appropriate-data-types)
|
- 3.1 [Apply Principle of Least Privilege](#31-apply-principle-of-least-privilege)
|
||||||
- 3.2 [Index Foreign Key Columns](#32-index-foreign-key-columns)
|
- 3.2 [Enable Row Level Security for Multi-Tenant Data](#32-enable-row-level-security-for-multi-tenant-data)
|
||||||
- 3.3 [Partition Large Tables for Better Performance](#33-partition-large-tables-for-better-performance)
|
- 3.3 [Optimize RLS Policies for Performance](#33-optimize-rls-policies-for-performance)
|
||||||
- 3.4 [Select Optimal Primary Key Strategy](#34-select-optimal-primary-key-strategy)
|
|
||||||
- 3.5 [Use Lowercase Identifiers for Compatibility](#35-use-lowercase-identifiers-for-compatibility)
|
|
||||||
|
|
||||||
4. [Schema Design](#schema-design) - **HIGH**
|
4. [Schema Design](#schema-design) - **HIGH**
|
||||||
- 4.1 [Keep Transactions Short to Reduce Lock Contention](#41-keep-transactions-short-to-reduce-lock-contention)
|
- 4.1 [Choose Appropriate Data Types](#41-choose-appropriate-data-types)
|
||||||
- 4.2 [Prevent Deadlocks with Consistent Lock Ordering](#42-prevent-deadlocks-with-consistent-lock-ordering)
|
- 4.2 [Index Foreign Key Columns](#42-index-foreign-key-columns)
|
||||||
- 4.3 [Use Advisory Locks for Application-Level Locking](#43-use-advisory-locks-for-application-level-locking)
|
- 4.3 [Partition Large Tables for Better Performance](#43-partition-large-tables-for-better-performance)
|
||||||
- 4.4 [Use SKIP LOCKED for Non-Blocking Queue Processing](#44-use-skip-locked-for-non-blocking-queue-processing)
|
- 4.4 [Select Optimal Primary Key Strategy](#44-select-optimal-primary-key-strategy)
|
||||||
|
- 4.5 [Use Lowercase Identifiers for Compatibility](#45-use-lowercase-identifiers-for-compatibility)
|
||||||
|
|
||||||
5. [Concurrency & Locking](#concurrency-locking) - **MEDIUM-HIGH**
|
5. [Concurrency & Locking](#concurrency-locking) - **MEDIUM-HIGH**
|
||||||
- 5.1 [Apply Principle of Least Privilege](#51-apply-principle-of-least-privilege)
|
- 5.1 [Keep Transactions Short to Reduce Lock Contention](#51-keep-transactions-short-to-reduce-lock-contention)
|
||||||
- 5.2 [Enable Row Level Security for Multi-Tenant Data](#52-enable-row-level-security-for-multi-tenant-data)
|
- 5.2 [Prevent Deadlocks with Consistent Lock Ordering](#52-prevent-deadlocks-with-consistent-lock-ordering)
|
||||||
- 5.3 [Optimize RLS Policies for Performance](#53-optimize-rls-policies-for-performance)
|
- 5.3 [Use Advisory Locks for Application-Level Locking](#53-use-advisory-locks-for-application-level-locking)
|
||||||
|
- 5.4 [Use SKIP LOCKED for Non-Blocking Queue Processing](#54-use-skip-locked-for-non-blocking-queue-processing)
|
||||||
|
|
||||||
6. [Data Access Patterns](#data-access-patterns) - **MEDIUM**
|
6. [Data Access Patterns](#data-access-patterns) - **MEDIUM**
|
||||||
- 6.1 [Batch INSERT Statements for Bulk Data](#61-batch-insert-statements-for-bulk-data)
|
- 6.1 [Batch INSERT Statements for Bulk Data](#61-batch-insert-statements-for-bulk-data)
|
||||||
@@ -433,7 +433,155 @@ Reference: https://supabase.com/docs/guides/database/connecting-to-postgres#conn
|
|||||||
|
|
||||||
Row-Level Security policies, privilege management, and authentication patterns.
|
Row-Level Security policies, privilege management, and authentication patterns.
|
||||||
|
|
||||||
### 3.1 Choose Appropriate Data Types
|
### 3.1 Apply Principle of Least Privilege
|
||||||
|
|
||||||
|
**Impact: MEDIUM (Reduced attack surface, better audit trail)**
|
||||||
|
|
||||||
|
Grant only the minimum permissions required. Never use superuser for application queries.
|
||||||
|
|
||||||
|
**Incorrect (overly broad permissions):**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Application uses superuser connection
|
||||||
|
-- Or grants ALL to application role
|
||||||
|
grant all privileges on all tables in schema public to app_user;
|
||||||
|
grant all privileges on all sequences in schema public to app_user;
|
||||||
|
|
||||||
|
-- Any SQL injection becomes catastrophic
|
||||||
|
-- drop table users; cascades to everything
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (minimal, specific grants):**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Create role with no default privileges
|
||||||
|
create role app_readonly nologin;
|
||||||
|
|
||||||
|
-- Grant only SELECT on specific tables
|
||||||
|
grant usage on schema public to app_readonly;
|
||||||
|
grant select on public.products, public.categories to app_readonly;
|
||||||
|
|
||||||
|
-- Create role for writes with limited scope
|
||||||
|
create role app_writer nologin;
|
||||||
|
grant usage on schema public to app_writer;
|
||||||
|
grant select, insert, update on public.orders to app_writer;
|
||||||
|
grant usage on sequence orders_id_seq to app_writer;
|
||||||
|
-- No DELETE permission
|
||||||
|
|
||||||
|
-- Login role inherits from these
|
||||||
|
create role app_user login password 'xxx';
|
||||||
|
grant app_writer to app_user;
|
||||||
|
-- Revoke default public access
|
||||||
|
revoke all on schema public from public;
|
||||||
|
revoke all on all tables in schema public from public;
|
||||||
|
```
|
||||||
|
|
||||||
|
Revoke public defaults:
|
||||||
|
|
||||||
|
Reference: https://supabase.com/blog/postgres-roles-and-privileges
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 Enable Row Level Security for Multi-Tenant Data
|
||||||
|
|
||||||
|
**Impact: CRITICAL (Database-enforced tenant isolation, prevent data leaks)**
|
||||||
|
|
||||||
|
Row Level Security (RLS) enforces data access at the database level, ensuring users only see their own data.
|
||||||
|
|
||||||
|
**Incorrect (application-level filtering only):**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Relying only on application to filter
|
||||||
|
select * from orders where user_id = $current_user_id;
|
||||||
|
|
||||||
|
-- Bug or bypass means all data is exposed!
|
||||||
|
select * from orders; -- Returns ALL orders
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (database-enforced RLS):**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Enable RLS on the table
|
||||||
|
alter table orders enable row level security;
|
||||||
|
|
||||||
|
-- Create policy for users to see only their orders
|
||||||
|
create policy orders_user_policy on orders
|
||||||
|
for all
|
||||||
|
using (user_id = current_setting('app.current_user_id')::bigint);
|
||||||
|
|
||||||
|
-- Force RLS even for table owners
|
||||||
|
alter table orders force row level security;
|
||||||
|
|
||||||
|
-- Set user context and query
|
||||||
|
set app.current_user_id = '123';
|
||||||
|
select * from orders; -- Only returns orders for user 123
|
||||||
|
create policy orders_user_policy on orders
|
||||||
|
for all
|
||||||
|
to authenticated
|
||||||
|
using (user_id = auth.uid());
|
||||||
|
```
|
||||||
|
|
||||||
|
Policy for authenticated role:
|
||||||
|
|
||||||
|
Reference: https://supabase.com/docs/guides/database/postgres/row-level-security
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 Optimize RLS Policies for Performance
|
||||||
|
|
||||||
|
**Impact: HIGH (5-10x faster RLS queries with proper patterns)**
|
||||||
|
|
||||||
|
Poorly written RLS policies can cause severe performance issues. Use subqueries and indexes strategically.
|
||||||
|
|
||||||
|
**Incorrect (function called for every row):**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
create policy orders_policy on orders
|
||||||
|
using (auth.uid() = user_id); -- auth.uid() called per row!
|
||||||
|
|
||||||
|
-- With 1M rows, auth.uid() is called 1M times
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (wrap functions in SELECT):**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
create policy orders_policy on orders
|
||||||
|
using ((select auth.uid()) = user_id); -- Called once, cached
|
||||||
|
|
||||||
|
-- 100x+ faster on large tables
|
||||||
|
-- Create helper function (runs as definer, bypasses RLS)
|
||||||
|
create or replace function is_team_member(team_id bigint)
|
||||||
|
returns boolean
|
||||||
|
language sql
|
||||||
|
security definer
|
||||||
|
set search_path = ''
|
||||||
|
as $$
|
||||||
|
select exists (
|
||||||
|
select 1 from public.team_members
|
||||||
|
where team_id = $1 and user_id = (select auth.uid())
|
||||||
|
);
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Use in policy (indexed lookup, not per-row check)
|
||||||
|
create policy team_orders_policy on orders
|
||||||
|
using ((select is_team_member(team_id)));
|
||||||
|
create index orders_user_id_idx on orders (user_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
Use security definer functions for complex checks:
|
||||||
|
Always add indexes on columns used in RLS policies:
|
||||||
|
|
||||||
|
Reference: https://supabase.com/docs/guides/database/postgres/row-level-security#rls-performance-recommendations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Schema Design
|
||||||
|
|
||||||
|
**Impact: HIGH**
|
||||||
|
|
||||||
|
Table design, index strategies, partitioning, and data type selection. Foundation for long-term performance.
|
||||||
|
|
||||||
|
### 4.1 Choose Appropriate Data Types
|
||||||
|
|
||||||
**Impact: HIGH (50% storage reduction, faster comparisons)**
|
**Impact: HIGH (50% storage reduction, faster comparisons)**
|
||||||
|
|
||||||
@@ -474,7 +622,7 @@ Reference: https://www.postgresql.org/docs/current/datatype.html
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.2 Index Foreign Key Columns
|
### 4.2 Index Foreign Key Columns
|
||||||
|
|
||||||
**Impact: HIGH (10-100x faster JOINs and CASCADE operations)**
|
**Impact: HIGH (10-100x faster JOINs and CASCADE operations)**
|
||||||
|
|
||||||
@@ -528,7 +676,7 @@ Reference: https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONS
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.3 Partition Large Tables for Better Performance
|
### 4.3 Partition Large Tables for Better Performance
|
||||||
|
|
||||||
**Impact: MEDIUM-HIGH (5-20x faster queries and maintenance on large tables)**
|
**Impact: MEDIUM-HIGH (5-20x faster queries and maintenance on large tables)**
|
||||||
|
|
||||||
@@ -580,7 +728,7 @@ Reference: https://www.postgresql.org/docs/current/ddl-partitioning.html
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.4 Select Optimal Primary Key Strategy
|
### 4.4 Select Optimal Primary Key Strategy
|
||||||
|
|
||||||
**Impact: HIGH (Better index locality, reduced fragmentation)**
|
**Impact: HIGH (Better index locality, reduced fragmentation)**
|
||||||
|
|
||||||
@@ -636,7 +784,7 @@ Guidelines:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.5 Use Lowercase Identifiers for Compatibility
|
### 4.5 Use Lowercase Identifiers for Compatibility
|
||||||
|
|
||||||
**Impact: MEDIUM (Avoid case-sensitivity bugs with tools, ORMs, and AI assistants)**
|
**Impact: MEDIUM (Avoid case-sensitivity bugs with tools, ORMs, and AI assistants)**
|
||||||
|
|
||||||
@@ -686,13 +834,13 @@ Reference: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-S
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Schema Design
|
## 5. Concurrency & Locking
|
||||||
|
|
||||||
**Impact: HIGH**
|
**Impact: MEDIUM-HIGH**
|
||||||
|
|
||||||
Table design, index strategies, partitioning, and data type selection. Foundation for long-term performance.
|
Transaction management, isolation levels, deadlock prevention, and lock contention patterns.
|
||||||
|
|
||||||
### 4.1 Keep Transactions Short to Reduce Lock Contention
|
### 5.1 Keep Transactions Short to Reduce Lock Contention
|
||||||
|
|
||||||
**Impact: MEDIUM-HIGH (3-5x throughput improvement, fewer deadlocks)**
|
**Impact: MEDIUM-HIGH (3-5x throughput improvement, fewer deadlocks)**
|
||||||
|
|
||||||
@@ -737,7 +885,7 @@ Reference: https://www.postgresql.org/docs/current/tutorial-transactions.html
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4.2 Prevent Deadlocks with Consistent Lock Ordering
|
### 5.2 Prevent Deadlocks with Consistent Lock Ordering
|
||||||
|
|
||||||
**Impact: MEDIUM-HIGH (Eliminate deadlock errors, improve reliability)**
|
**Impact: MEDIUM-HIGH (Eliminate deadlock errors, improve reliability)**
|
||||||
|
|
||||||
@@ -794,7 +942,7 @@ Detect deadlocks in logs:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4.3 Use Advisory Locks for Application-Level Locking
|
### 5.3 Use Advisory Locks for Application-Level Locking
|
||||||
|
|
||||||
**Impact: MEDIUM (Efficient coordination without row-level lock overhead)**
|
**Impact: MEDIUM (Efficient coordination without row-level lock overhead)**
|
||||||
|
|
||||||
@@ -845,7 +993,7 @@ Reference: https://www.postgresql.org/docs/current/explicit-locking.html#ADVISOR
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4.4 Use SKIP LOCKED for Non-Blocking Queue Processing
|
### 5.4 Use SKIP LOCKED for Non-Blocking Queue Processing
|
||||||
|
|
||||||
**Impact: MEDIUM-HIGH (10x throughput for worker queues)**
|
**Impact: MEDIUM-HIGH (10x throughput for worker queues)**
|
||||||
|
|
||||||
@@ -894,154 +1042,6 @@ Reference: https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDAT
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Concurrency & Locking
|
|
||||||
|
|
||||||
**Impact: MEDIUM-HIGH**
|
|
||||||
|
|
||||||
Transaction management, isolation levels, deadlock prevention, and lock contention patterns.
|
|
||||||
|
|
||||||
### 5.1 Apply Principle of Least Privilege
|
|
||||||
|
|
||||||
**Impact: MEDIUM (Reduced attack surface, better audit trail)**
|
|
||||||
|
|
||||||
Grant only the minimum permissions required. Never use superuser for application queries.
|
|
||||||
|
|
||||||
**Incorrect (overly broad permissions):**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Application uses superuser connection
|
|
||||||
-- Or grants ALL to application role
|
|
||||||
grant all privileges on all tables in schema public to app_user;
|
|
||||||
grant all privileges on all sequences in schema public to app_user;
|
|
||||||
|
|
||||||
-- Any SQL injection becomes catastrophic
|
|
||||||
-- drop table users; cascades to everything
|
|
||||||
```
|
|
||||||
|
|
||||||
**Correct (minimal, specific grants):**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Create role with no default privileges
|
|
||||||
create role app_readonly nologin;
|
|
||||||
|
|
||||||
-- Grant only SELECT on specific tables
|
|
||||||
grant usage on schema public to app_readonly;
|
|
||||||
grant select on public.products, public.categories to app_readonly;
|
|
||||||
|
|
||||||
-- Create role for writes with limited scope
|
|
||||||
create role app_writer nologin;
|
|
||||||
grant usage on schema public to app_writer;
|
|
||||||
grant select, insert, update on public.orders to app_writer;
|
|
||||||
grant usage on sequence orders_id_seq to app_writer;
|
|
||||||
-- No DELETE permission
|
|
||||||
|
|
||||||
-- Login role inherits from these
|
|
||||||
create role app_user login password 'xxx';
|
|
||||||
grant app_writer to app_user;
|
|
||||||
-- Revoke default public access
|
|
||||||
revoke all on schema public from public;
|
|
||||||
revoke all on all tables in schema public from public;
|
|
||||||
```
|
|
||||||
|
|
||||||
Revoke public defaults:
|
|
||||||
|
|
||||||
Reference: https://supabase.com/blog/postgres-roles-and-privileges
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5.2 Enable Row Level Security for Multi-Tenant Data
|
|
||||||
|
|
||||||
**Impact: CRITICAL (Database-enforced tenant isolation, prevent data leaks)**
|
|
||||||
|
|
||||||
Row Level Security (RLS) enforces data access at the database level, ensuring users only see their own data.
|
|
||||||
|
|
||||||
**Incorrect (application-level filtering only):**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Relying only on application to filter
|
|
||||||
select * from orders where user_id = $current_user_id;
|
|
||||||
|
|
||||||
-- Bug or bypass means all data is exposed!
|
|
||||||
select * from orders; -- Returns ALL orders
|
|
||||||
```
|
|
||||||
|
|
||||||
**Correct (database-enforced RLS):**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Enable RLS on the table
|
|
||||||
alter table orders enable row level security;
|
|
||||||
|
|
||||||
-- Create policy for users to see only their orders
|
|
||||||
create policy orders_user_policy on orders
|
|
||||||
for all
|
|
||||||
using (user_id = current_setting('app.current_user_id')::bigint);
|
|
||||||
|
|
||||||
-- Force RLS even for table owners
|
|
||||||
alter table orders force row level security;
|
|
||||||
|
|
||||||
-- Set user context and query
|
|
||||||
set app.current_user_id = '123';
|
|
||||||
select * from orders; -- Only returns orders for user 123
|
|
||||||
create policy orders_user_policy on orders
|
|
||||||
for all
|
|
||||||
to authenticated
|
|
||||||
using (user_id = auth.uid());
|
|
||||||
```
|
|
||||||
|
|
||||||
Policy for authenticated role:
|
|
||||||
|
|
||||||
Reference: https://supabase.com/docs/guides/database/postgres/row-level-security
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5.3 Optimize RLS Policies for Performance
|
|
||||||
|
|
||||||
**Impact: HIGH (5-10x faster RLS queries with proper patterns)**
|
|
||||||
|
|
||||||
Poorly written RLS policies can cause severe performance issues. Use subqueries and indexes strategically.
|
|
||||||
|
|
||||||
**Incorrect (function called for every row):**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
create policy orders_policy on orders
|
|
||||||
using (auth.uid() = user_id); -- auth.uid() called per row!
|
|
||||||
|
|
||||||
-- With 1M rows, auth.uid() is called 1M times
|
|
||||||
```
|
|
||||||
|
|
||||||
**Correct (wrap functions in SELECT):**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
create policy orders_policy on orders
|
|
||||||
using ((select auth.uid()) = user_id); -- Called once, cached
|
|
||||||
|
|
||||||
-- 100x+ faster on large tables
|
|
||||||
-- Create helper function (runs as definer, bypasses RLS)
|
|
||||||
create or replace function is_team_member(team_id bigint)
|
|
||||||
returns boolean
|
|
||||||
language sql
|
|
||||||
security definer
|
|
||||||
set search_path = ''
|
|
||||||
as $$
|
|
||||||
select exists (
|
|
||||||
select 1 from public.team_members
|
|
||||||
where team_id = $1 and user_id = (select auth.uid())
|
|
||||||
);
|
|
||||||
$$;
|
|
||||||
|
|
||||||
-- Use in policy (indexed lookup, not per-row check)
|
|
||||||
create policy team_orders_policy on orders
|
|
||||||
using ((select is_team_member(team_id)));
|
|
||||||
create index orders_user_id_idx on orders (user_id);
|
|
||||||
```
|
|
||||||
|
|
||||||
Use security definer functions for complex checks:
|
|
||||||
Always add indexes on columns used in RLS policies:
|
|
||||||
|
|
||||||
Reference: https://supabase.com/docs/guides/database/postgres/row-level-security#rls-performance-recommendations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Data Access Patterns
|
## 6. Data Access Patterns
|
||||||
|
|
||||||
**Impact: MEDIUM**
|
**Impact: MEDIUM**
|
||||||
|
|||||||
Reference in New Issue
Block a user