init
This commit is contained in:
231
.opencode/skills/databases/stacks/bigquery.md
Normal file
231
.opencode/skills/databases/stacks/bigquery.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# BigQuery Rules
|
||||
|
||||
Guidelines for designing schemas specific to Google BigQuery.
|
||||
|
||||
---
|
||||
|
||||
## Data Types
|
||||
|
||||
| Use case | Type | Notes |
|
||||
|----------|------|-------|
|
||||
| Integer | `INT64` | |
|
||||
| Decimal | `NUMERIC` or `BIGNUMERIC` | |
|
||||
| Float | `FLOAT64` | |
|
||||
| Boolean | `BOOL` | |
|
||||
| String | `STRING` | |
|
||||
| Timestamp | `TIMESTAMP` | |
|
||||
| Date | `DATE` | |
|
||||
| Time | `TIME` | |
|
||||
| DateTime | `DATETIME` | No timezone |
|
||||
| JSON | `JSON` | |
|
||||
| Bytes | `BYTES` | |
|
||||
| Nested | `STRUCT` | Record type |
|
||||
| Repeated | `ARRAY` | |
|
||||
|
||||
---
|
||||
|
||||
## Description (NO metadata tables needed)
|
||||
|
||||
BigQuery supports description via `OPTIONS`:
|
||||
|
||||
### Table description
|
||||
```sql
|
||||
CREATE TABLE orders (
|
||||
...
|
||||
) OPTIONS(description='Table storing customer orders');
|
||||
```
|
||||
|
||||
### Column descriptions
|
||||
```sql
|
||||
CREATE TABLE orders (
|
||||
id INT64 NOT NULL OPTIONS(description='Primary key'),
|
||||
order_number STRING NOT NULL OPTIONS(description='Unique order code'),
|
||||
status STRING NOT NULL OPTIONS(description='pending|confirmed|shipped|cancelled'),
|
||||
total_amount NUMERIC OPTIONS(description='Total value')
|
||||
);
|
||||
```
|
||||
|
||||
### Query descriptions
|
||||
```sql
|
||||
SELECT column_name, description
|
||||
FROM `project.dataset.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS`
|
||||
WHERE table_name = 'orders';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partitioning & Clustering
|
||||
|
||||
BigQuery **does not have traditional indexes**. Instead, use partitioning and clustering:
|
||||
|
||||
### Partitioning (required for large tables)
|
||||
|
||||
```sql
|
||||
-- Partition by date column
|
||||
CREATE TABLE fact_orders (
|
||||
order_date DATE,
|
||||
customer_id INT64,
|
||||
order_id INT64,
|
||||
amount NUMERIC
|
||||
)
|
||||
PARTITION BY order_date;
|
||||
|
||||
-- Partition by timestamp (daily)
|
||||
CREATE TABLE events (
|
||||
event_time TIMESTAMP,
|
||||
...
|
||||
)
|
||||
PARTITION BY DATE(event_time);
|
||||
|
||||
-- Integer range partitioning
|
||||
CREATE TABLE logs (
|
||||
log_id INT64,
|
||||
...
|
||||
)
|
||||
PARTITION BY RANGE_BUCKET(log_id, GENERATE_ARRAY(0, 1000000, 10000));
|
||||
```
|
||||
|
||||
### Clustering (optimize filter/group)
|
||||
|
||||
```sql
|
||||
CREATE TABLE fact_orders (
|
||||
order_date DATE,
|
||||
customer_id INT64,
|
||||
product_id INT64,
|
||||
store_id INT64,
|
||||
amount NUMERIC
|
||||
)
|
||||
PARTITION BY order_date
|
||||
CLUSTER BY customer_id, product_id;
|
||||
-- Can cluster up to 4 columns
|
||||
```
|
||||
|
||||
### When to use what?
|
||||
- **Partition**: Filter by date range → reduces data scan
|
||||
- **Cluster**: Filter/Group by specific columns → data is organized closer together
|
||||
|
||||
---
|
||||
|
||||
## Nested & Repeated Fields (Denormalization)
|
||||
|
||||
BigQuery encourages denormalization with STRUCT and ARRAY:
|
||||
|
||||
```sql
|
||||
CREATE TABLE orders (
|
||||
order_id INT64,
|
||||
order_date DATE,
|
||||
customer STRUCT<
|
||||
id INT64,
|
||||
name STRING,
|
||||
email STRING
|
||||
>,
|
||||
items ARRAY<STRUCT<
|
||||
product_id INT64,
|
||||
product_name STRING,
|
||||
quantity INT64,
|
||||
unit_price NUMERIC
|
||||
>>
|
||||
);
|
||||
|
||||
-- Query nested fields
|
||||
SELECT
|
||||
order_id,
|
||||
customer.name,
|
||||
item.product_name,
|
||||
item.quantity
|
||||
FROM orders, UNNEST(items) AS item;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Query optimization
|
||||
- **Avoid `SELECT *`** - only select needed columns
|
||||
- **Filter early** - use partition column in WHERE
|
||||
- **Avoid cross-join** with large tables
|
||||
|
||||
### Table design
|
||||
- Partition tables > 1GB
|
||||
- Cluster by frequently filtered/grouped columns
|
||||
- Denormalize with STRUCT/ARRAY when appropriate
|
||||
- Avoid too many small tables
|
||||
|
||||
### Cost control
|
||||
- Partition pruning: always filter by partition column
|
||||
- Use `--dry_run` to estimate cost
|
||||
- Set up query quotas
|
||||
|
||||
---
|
||||
|
||||
## Example DDL
|
||||
|
||||
```sql
|
||||
CREATE TABLE `project.dataset.fact_orders` (
|
||||
-- Keys
|
||||
order_id INT64 NOT NULL OPTIONS(description='PK from source'),
|
||||
order_date DATE NOT NULL OPTIONS(description='Order date'),
|
||||
|
||||
-- Dimensions (denormalized)
|
||||
customer_id INT64 NOT NULL,
|
||||
customer_name STRING,
|
||||
customer_segment STRING OPTIONS(description='VIP|Regular|New'),
|
||||
|
||||
-- Measures
|
||||
item_count INT64 NOT NULL,
|
||||
gross_amount NUMERIC NOT NULL,
|
||||
discount_amount NUMERIC DEFAULT 0,
|
||||
net_amount NUMERIC NOT NULL,
|
||||
|
||||
-- Nested items
|
||||
items ARRAY<STRUCT<
|
||||
product_id INT64,
|
||||
product_name STRING,
|
||||
quantity INT64,
|
||||
unit_price NUMERIC
|
||||
>> OPTIONS(description='Product details in order'),
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
|
||||
)
|
||||
PARTITION BY order_date
|
||||
CLUSTER BY customer_id
|
||||
OPTIONS(
|
||||
description='Fact table for orders, partitioned by date, clustered by customer'
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scheduled Queries / Materialized Views
|
||||
|
||||
### Materialized View
|
||||
```sql
|
||||
CREATE MATERIALIZED VIEW `project.dataset.mv_daily_sales`
|
||||
PARTITION BY report_date
|
||||
CLUSTER BY store_id
|
||||
AS
|
||||
SELECT
|
||||
DATE(order_date) as report_date,
|
||||
store_id,
|
||||
COUNT(*) as order_count,
|
||||
SUM(net_amount) as revenue
|
||||
FROM `project.dataset.fact_orders`
|
||||
GROUP BY 1, 2;
|
||||
```
|
||||
|
||||
### Scheduled Query (ETL)
|
||||
Create via BigQuery Console or API with schedule expression.
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Partition large tables by date column
|
||||
- [ ] Cluster by frequently filtered/grouped columns (max 4)
|
||||
- [ ] `OPTIONS(description=...)` for tables and columns
|
||||
- [ ] Denormalize with STRUCT/ARRAY when appropriate
|
||||
- [ ] Avoid `SELECT *` and cross-join large tables
|
||||
- [ ] Filter by partition column in queries
|
||||
- [ ] Consider Materialized Views for frequently used aggregations
|
||||
137
.opencode/skills/databases/stacks/d1_cloudflare.md
Normal file
137
.opencode/skills/databases/stacks/d1_cloudflare.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# D1 Cloudflare Rules
|
||||
|
||||
D1 is a SQLite-based database running on Cloudflare edge. Inherits from [sqlite.md](sqlite.md) with its own specifics.
|
||||
|
||||
---
|
||||
|
||||
## Important Limitations
|
||||
|
||||
| Limit | Value |
|
||||
|-------|-------|
|
||||
| Max database size | **10 GB** |
|
||||
| Concurrent queries | Single-threaded (1 query/time) |
|
||||
| Batch operations | No more than 100k rows/query |
|
||||
| Max tables | No hard limit, but recommend < 100 |
|
||||
|
||||
**Throughput**: ~1000 queries/s if avg query = 1ms. Query 100ms = only 10 queries/s.
|
||||
|
||||
---
|
||||
|
||||
## Data Types
|
||||
|
||||
Same as SQLite:
|
||||
|
||||
| Storage Class | Use case |
|
||||
|---------------|----------|
|
||||
| `INTEGER` | PK, FK, counts, booleans |
|
||||
| `REAL` | Floats, decimals |
|
||||
| `TEXT` | Strings, dates, JSON |
|
||||
| `BLOB` | Binary |
|
||||
|
||||
**Date/Time**: Store as `TEXT` with ISO format: `datetime('now')`
|
||||
|
||||
---
|
||||
|
||||
## Metadata Tables (REQUIRED)
|
||||
|
||||
D1 has no native comments. Need to create metadata tables:
|
||||
|
||||
```sql
|
||||
CREATE TABLE metadata_tables (
|
||||
table_name TEXT PRIMARY KEY,
|
||||
description TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE metadata_columns (
|
||||
table_name TEXT NOT NULL,
|
||||
column_name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
data_type TEXT,
|
||||
PRIMARY KEY (table_name, column_name),
|
||||
FOREIGN KEY (table_name) REFERENCES metadata_tables(table_name) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- After CREATE TABLE
|
||||
INSERT INTO metadata_tables VALUES ('orders', 'Orders table', datetime('now'));
|
||||
INSERT INTO metadata_columns VALUES
|
||||
('orders', 'id', 'PK', 'INTEGER'),
|
||||
('orders', 'status', 'pending|confirmed|shipped', 'TEXT');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Foreign Keys
|
||||
|
||||
```sql
|
||||
-- Enable FK (ON by default in D1, but should be explicit)
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE order_items (
|
||||
id INTEGER PRIMARY KEY,
|
||||
order_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX idx_order_items_order ON order_items(order_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Performance
|
||||
- **Use indexes** - since single-threaded, queries must be fast
|
||||
- **Batch writes** to reduce latency
|
||||
- **Avoid large transactions** (>100k rows)
|
||||
- **Pagination** with LIMIT/OFFSET or cursor-based
|
||||
|
||||
### Monitoring
|
||||
- Track query duration
|
||||
- Monitor database size
|
||||
- Alert when approaching 10GB limit
|
||||
|
||||
---
|
||||
|
||||
## Example DDL
|
||||
|
||||
```sql
|
||||
-- Enable FK
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_number TEXT NOT NULL UNIQUE,
|
||||
user_id INTEGER NOT NULL,
|
||||
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'confirmed', 'shipped', 'cancelled')),
|
||||
total_amount REAL DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_orders_user ON orders(user_id);
|
||||
CREATE INDEX idx_orders_status ON orders(status);
|
||||
CREATE INDEX idx_orders_updated ON orders(updated_at);
|
||||
|
||||
-- Metadata
|
||||
INSERT INTO metadata_tables (table_name, description)
|
||||
VALUES ('orders', 'Orders table');
|
||||
|
||||
INSERT INTO metadata_columns (table_name, column_name, description, data_type)
|
||||
VALUES
|
||||
('orders', 'id', 'PK', 'INTEGER'),
|
||||
('orders', 'order_number', 'Unique order code', 'TEXT'),
|
||||
('orders', 'status', 'pending|confirmed|shipped|cancelled', 'TEXT');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Database size < 10GB
|
||||
- [ ] Metadata tables for documentation
|
||||
- [ ] Index for every query (single-threaded!)
|
||||
- [ ] FK has index
|
||||
- [ ] Batch operations < 100k rows
|
||||
- [ ] Monitor size and query performance
|
||||
216
.opencode/skills/databases/stacks/mysql.md
Normal file
216
.opencode/skills/databases/stacks/mysql.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# MySQL Rules
|
||||
|
||||
Guidelines for designing schemas specific to MySQL / MariaDB.
|
||||
|
||||
---
|
||||
|
||||
## Data Types
|
||||
|
||||
| Use case | Type | Notes |
|
||||
|----------|------|-------|
|
||||
| Primary key | `BIGINT UNSIGNED AUTO_INCREMENT` | For large tables |
|
||||
| Primary key (small) | `INT UNSIGNED AUTO_INCREMENT` | Tables < 2 billion rows |
|
||||
| Foreign key | Match with PK type | |
|
||||
| Money/Price | `DECIMAL(18,2)` | **Do NOT use FLOAT/DOUBLE** |
|
||||
| Quantity | `INT` or `DECIMAL(18,4)` | |
|
||||
| Boolean | `TINYINT(1)` or `BOOLEAN` | |
|
||||
| Short text | `VARCHAR(n)` | n ≤ 255 for indexing |
|
||||
| Long text | `TEXT` | Cannot index directly |
|
||||
| JSON | `JSON` | MySQL 5.7+ |
|
||||
| Timestamp | `DATETIME` or `TIMESTAMP` | |
|
||||
| Date | `DATE` | |
|
||||
| UUID | `CHAR(36)` or `BINARY(16)` | |
|
||||
|
||||
### TIMESTAMP vs DATETIME
|
||||
- `TIMESTAMP`: Auto-converts timezone, range 1970-2038
|
||||
- `DATETIME`: No timezone conversion, range 1000-9999
|
||||
|
||||
---
|
||||
|
||||
## Comments (NO metadata tables needed)
|
||||
|
||||
MySQL supports comments directly in DDL:
|
||||
|
||||
### Table comment
|
||||
```sql
|
||||
CREATE TABLE orders (
|
||||
...
|
||||
) COMMENT='Table storing customer orders';
|
||||
```
|
||||
|
||||
### Column comment
|
||||
```sql
|
||||
CREATE TABLE orders (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT
|
||||
COMMENT 'Primary key, auto increment',
|
||||
order_number VARCHAR(50) NOT NULL
|
||||
COMMENT 'Unique order code, format: ORD-YYYYMMDD-XXXXX',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'pending'
|
||||
COMMENT 'Status: pending|confirmed|shipped|cancelled',
|
||||
total_amount DECIMAL(18,2) NOT NULL
|
||||
COMMENT 'Total value including tax'
|
||||
);
|
||||
```
|
||||
|
||||
### Query comments
|
||||
```sql
|
||||
-- Table comment
|
||||
SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'orders';
|
||||
|
||||
-- Column comments
|
||||
SELECT COLUMN_NAME, COLUMN_COMMENT FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'orders';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Index Types
|
||||
|
||||
### B-Tree (default)
|
||||
```sql
|
||||
CREATE INDEX idx_orders_status ON orders(status);
|
||||
CREATE INDEX idx_orders_user_status_created ON orders(user_id, status, created_at DESC);
|
||||
CREATE UNIQUE INDEX idx_orders_number ON orders(order_number);
|
||||
```
|
||||
|
||||
### FULLTEXT (text search)
|
||||
```sql
|
||||
CREATE FULLTEXT INDEX idx_products_search ON products(name, description);
|
||||
|
||||
-- Query
|
||||
SELECT * FROM products
|
||||
WHERE MATCH(name, description) AGAINST('gaming laptop' IN NATURAL LANGUAGE MODE);
|
||||
```
|
||||
|
||||
### Notes
|
||||
- **Partial index**: Not natively supported in MySQL
|
||||
- Index prefix for TEXT: `CREATE INDEX idx ON table(col(255))`
|
||||
|
||||
---
|
||||
|
||||
## Auto Increment
|
||||
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT
|
||||
);
|
||||
|
||||
-- Set starting value
|
||||
ALTER TABLE users AUTO_INCREMENT = 1000;
|
||||
|
||||
-- Get last inserted ID
|
||||
SELECT LAST_INSERT_ID();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Auto update timestamp
|
||||
|
||||
```sql
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ENUM vs VARCHAR
|
||||
|
||||
### Use ENUM when values are fixed and rarely change
|
||||
```sql
|
||||
status ENUM('pending', 'confirmed', 'shipped', 'cancelled') NOT NULL DEFAULT 'pending'
|
||||
```
|
||||
|
||||
### Use VARCHAR + CHECK when flexibility needed
|
||||
```sql
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||||
CONSTRAINT chk_orders_status CHECK (status IN ('pending', 'confirmed', 'shipped'))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Foreign Keys
|
||||
|
||||
```sql
|
||||
CREATE TABLE order_items (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
product_id BIGINT UNSIGNED NOT NULL,
|
||||
|
||||
CONSTRAINT fk_orderitems_order
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
|
||||
CONSTRAINT fk_orderitems_product
|
||||
FOREIGN KEY (product_id) REFERENCES products(id)
|
||||
ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
-- IMPORTANT: Index for FK
|
||||
CREATE INDEX idx_orderitems_order ON order_items(order_id);
|
||||
CREATE INDEX idx_orderitems_product ON order_items(product_id);
|
||||
```
|
||||
|
||||
**Note:** FK only works with **InnoDB** engine.
|
||||
|
||||
---
|
||||
|
||||
## Partitioning
|
||||
|
||||
### Range partitioning (by date)
|
||||
```sql
|
||||
CREATE TABLE fact_orders (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
order_date DATE NOT NULL,
|
||||
...
|
||||
PRIMARY KEY (id, order_date)
|
||||
) PARTITION BY RANGE (YEAR(order_date)) (
|
||||
PARTITION p2023 VALUES LESS THAN (2024),
|
||||
PARTITION p2024 VALUES LESS THAN (2025),
|
||||
PARTITION pmax VALUES LESS THAN MAXVALUE
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example DDL
|
||||
|
||||
```sql
|
||||
CREATE TABLE orders (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT
|
||||
COMMENT 'Primary key',
|
||||
order_number VARCHAR(50) NOT NULL
|
||||
COMMENT 'Unique order code',
|
||||
user_id BIGINT UNSIGNED NOT NULL
|
||||
COMMENT 'FK to users.id',
|
||||
status ENUM('pending', 'confirmed', 'shipped', 'cancelled', 'completed')
|
||||
NOT NULL DEFAULT 'pending'
|
||||
COMMENT 'Order status',
|
||||
subtotal DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
discount_amount DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
total_amount DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE KEY uk_orders_number (order_number),
|
||||
KEY idx_orders_user (user_id),
|
||||
KEY idx_orders_status (status),
|
||||
KEY idx_orders_user_status_created (user_id, status, created_at DESC),
|
||||
|
||||
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_unicode_ci
|
||||
COMMENT='Table storing order information';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Engine: `InnoDB` (required for FK)
|
||||
- [ ] Charset: `utf8mb4` + `utf8mb4_unicode_ci`
|
||||
- [ ] Comments for tables and columns
|
||||
- [ ] All FKs have indexes
|
||||
- [ ] Use `DECIMAL` for money, not `FLOAT`
|
||||
- [ ] `ON UPDATE CURRENT_TIMESTAMP` for `updated_at`
|
||||
235
.opencode/skills/databases/stacks/postgres.md
Normal file
235
.opencode/skills/databases/stacks/postgres.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# PostgreSQL Rules
|
||||
|
||||
Guidelines for designing schemas specific to PostgreSQL.
|
||||
|
||||
---
|
||||
|
||||
## Data Types
|
||||
|
||||
| Use case | Type | Notes |
|
||||
|----------|------|-------|
|
||||
| Primary key | `BIGSERIAL` | Auto-increment BIGINT |
|
||||
| Primary key (small) | `SERIAL` | Auto-increment INT |
|
||||
| Foreign key | Match with PK type | |
|
||||
| Money/Price | `NUMERIC(18,2)` | Precise |
|
||||
| Boolean | `BOOLEAN` | TRUE/FALSE/NULL |
|
||||
| Short text | `VARCHAR(n)` or `TEXT` | TEXT has no limit |
|
||||
| JSON | `JSONB` | **Queryable**, prefer over JSON |
|
||||
| Timestamp | `TIMESTAMPTZ` | **Always use with timezone** |
|
||||
| Date | `DATE` | |
|
||||
| UUID | `UUID` | Native type |
|
||||
| Array | `TEXT[]`, `INT[]` | |
|
||||
| IP Address | `INET` | |
|
||||
|
||||
### TIMESTAMPTZ vs TIMESTAMP
|
||||
**IMPORTANT**: Always use `TIMESTAMPTZ` to avoid timezone issues.
|
||||
|
||||
---
|
||||
|
||||
## Comments (NO metadata tables needed)
|
||||
|
||||
PostgreSQL supports `COMMENT ON`:
|
||||
|
||||
### Table comment
|
||||
```sql
|
||||
COMMENT ON TABLE orders IS 'Table storing customer orders';
|
||||
```
|
||||
|
||||
### Column comments
|
||||
```sql
|
||||
COMMENT ON COLUMN orders.id IS 'Primary key, auto increment';
|
||||
COMMENT ON COLUMN orders.order_number IS 'Unique order code, format: ORD-YYYYMMDD-XXXXX';
|
||||
COMMENT ON COLUMN orders.status IS 'Status: pending|confirmed|shipped|cancelled';
|
||||
```
|
||||
|
||||
### Query comments
|
||||
```sql
|
||||
-- Table comment
|
||||
SELECT obj_description('orders'::regclass);
|
||||
|
||||
-- Column comments
|
||||
SELECT
|
||||
c.column_name,
|
||||
pgd.description
|
||||
FROM information_schema.columns c
|
||||
LEFT JOIN pg_catalog.pg_statio_all_tables st
|
||||
ON c.table_schema = st.schemaname AND c.table_name = st.relname
|
||||
LEFT JOIN pg_catalog.pg_description pgd
|
||||
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
||||
WHERE c.table_name = 'orders';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Index Types
|
||||
|
||||
### B-Tree (default)
|
||||
```sql
|
||||
CREATE INDEX idx_orders_status ON orders(status);
|
||||
CREATE INDEX idx_orders_user_status_created ON orders(user_id, status, created_at DESC);
|
||||
```
|
||||
|
||||
### Partial Index (very useful)
|
||||
```sql
|
||||
-- Only index non-deleted records
|
||||
CREATE INDEX idx_orders_active ON orders(user_id, status)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- Only index pending orders
|
||||
CREATE INDEX idx_orders_pending ON orders(created_at)
|
||||
WHERE status = 'pending';
|
||||
```
|
||||
|
||||
### GIN (for JSONB, Arrays, Full-text)
|
||||
```sql
|
||||
CREATE INDEX idx_orders_metadata ON orders USING GIN(metadata);
|
||||
CREATE INDEX idx_products_tags ON products USING GIN(tags);
|
||||
|
||||
-- Full-text search
|
||||
CREATE INDEX idx_products_search ON products
|
||||
USING GIN(to_tsvector('english', name || ' ' || description));
|
||||
```
|
||||
|
||||
### BRIN (for large tables with ordered data)
|
||||
```sql
|
||||
CREATE INDEX idx_logs_created ON logs USING BRIN(created_at);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sequences (Auto Increment)
|
||||
|
||||
```sql
|
||||
-- SERIAL/BIGSERIAL auto-creates sequence
|
||||
CREATE TABLE users (id BIGSERIAL PRIMARY KEY);
|
||||
|
||||
-- Identity columns (PostgreSQL 10+)
|
||||
CREATE TABLE users (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
|
||||
);
|
||||
|
||||
-- Custom sequence
|
||||
CREATE SEQUENCE order_number_seq START 1000;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Auto update timestamp (requires trigger)
|
||||
|
||||
PostgreSQL **does not have** `ON UPDATE CURRENT_TIMESTAMP`. Need to create trigger:
|
||||
|
||||
```sql
|
||||
-- Function
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Apply to table
|
||||
CREATE TRIGGER trg_orders_updated_at
|
||||
BEFORE UPDATE ON orders
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ENUM Type
|
||||
|
||||
```sql
|
||||
-- Create enum type
|
||||
CREATE TYPE order_status AS ENUM (
|
||||
'pending', 'confirmed', 'shipped', 'cancelled', 'completed'
|
||||
);
|
||||
|
||||
-- Use in table
|
||||
CREATE TABLE orders (
|
||||
status order_status NOT NULL DEFAULT 'pending'
|
||||
);
|
||||
|
||||
-- Add new value
|
||||
ALTER TYPE order_status ADD VALUE 'refunded' AFTER 'completed';
|
||||
```
|
||||
|
||||
**Note:** Cannot remove values from ENUM.
|
||||
|
||||
---
|
||||
|
||||
## Partitioning
|
||||
|
||||
### Range partitioning
|
||||
```sql
|
||||
CREATE TABLE fact_orders (
|
||||
id BIGSERIAL,
|
||||
order_date DATE NOT NULL,
|
||||
...
|
||||
) PARTITION BY RANGE (order_date);
|
||||
|
||||
CREATE TABLE fact_orders_2024 PARTITION OF fact_orders
|
||||
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
|
||||
|
||||
CREATE TABLE fact_orders_default PARTITION OF fact_orders DEFAULT;
|
||||
```
|
||||
|
||||
### Hash partitioning
|
||||
```sql
|
||||
CREATE TABLE logs PARTITION BY HASH (user_id);
|
||||
CREATE TABLE logs_0 PARTITION OF logs FOR VALUES WITH (MODULUS 4, REMAINDER 0);
|
||||
CREATE TABLE logs_1 PARTITION OF logs FOR VALUES WITH (MODULUS 4, REMAINDER 1);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example DDL
|
||||
|
||||
```sql
|
||||
-- Create enum
|
||||
CREATE TYPE order_status AS ENUM ('pending', 'confirmed', 'shipped', 'cancelled');
|
||||
|
||||
-- Create table
|
||||
CREATE TABLE orders (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
order_number VARCHAR(50) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
status order_status NOT NULL DEFAULT 'pending',
|
||||
subtotal NUMERIC(18,2) NOT NULL DEFAULT 0,
|
||||
discount_amount NUMERIC(18,2) NOT NULL DEFAULT 0,
|
||||
total_amount NUMERIC(18,2) NOT NULL DEFAULT 0,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT uk_orders_number UNIQUE (order_number),
|
||||
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_orders_user ON orders(user_id);
|
||||
CREATE INDEX idx_orders_status ON orders(status);
|
||||
CREATE INDEX idx_orders_user_status_created ON orders(user_id, status, created_at DESC);
|
||||
CREATE INDEX idx_orders_metadata ON orders USING GIN(metadata);
|
||||
|
||||
-- Comments
|
||||
COMMENT ON TABLE orders IS 'Table storing order information';
|
||||
COMMENT ON COLUMN orders.order_number IS 'Unique order code';
|
||||
COMMENT ON COLUMN orders.status IS 'Order status';
|
||||
|
||||
-- Trigger for updated_at
|
||||
CREATE TRIGGER trg_orders_updated_at
|
||||
BEFORE UPDATE ON orders
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Use `TIMESTAMPTZ` instead of `TIMESTAMP`
|
||||
- [ ] Use `JSONB` instead of `JSON`
|
||||
- [ ] `COMMENT ON` for tables and columns
|
||||
- [ ] Trigger for `updated_at`
|
||||
- [ ] Partial indexes for soft delete / filtered queries
|
||||
- [ ] GIN index for JSONB columns
|
||||
244
.opencode/skills/databases/stacks/sqlite.md
Normal file
244
.opencode/skills/databases/stacks/sqlite.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# SQLite Rules
|
||||
|
||||
Guidelines for designing schemas specific to SQLite.
|
||||
|
||||
---
|
||||
|
||||
## Data Types
|
||||
|
||||
SQLite has **dynamic typing**, with only 5 storage classes:
|
||||
|
||||
| Storage Class | Use case | Notes |
|
||||
|---------------|----------|-------|
|
||||
| `INTEGER` | PK, FK, counts, booleans | |
|
||||
| `REAL` | Floats, decimals | |
|
||||
| `TEXT` | Strings, dates, JSON, enums | |
|
||||
| `BLOB` | Binary data | |
|
||||
| `NULL` | Null values | |
|
||||
|
||||
### Type affinity
|
||||
SQLite does not strictly enforce types. Type declaration is just a "hint":
|
||||
```sql
|
||||
-- All of these will be stored
|
||||
CREATE TABLE test (price REAL);
|
||||
INSERT INTO test VALUES (100); -- Stored as INTEGER
|
||||
INSERT INTO test VALUES (99.99); -- Stored as REAL
|
||||
INSERT INTO test VALUES ('free'); -- Stored as TEXT (!)
|
||||
```
|
||||
|
||||
### Date/Time
|
||||
SQLite **does not have** DATE/TIME type. Store as TEXT with ISO format:
|
||||
```sql
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
-- Format: YYYY-MM-DD HH:MM:SS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Metadata Tables (REQUIRED)
|
||||
|
||||
SQLite **does not support comments** on tables and columns. Need to create metadata tables:
|
||||
|
||||
### Create metadata tables
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS metadata_tables (
|
||||
table_name TEXT PRIMARY KEY,
|
||||
description TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS metadata_columns (
|
||||
table_name TEXT NOT NULL,
|
||||
column_name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
data_type TEXT,
|
||||
is_nullable INTEGER DEFAULT 1, -- 0=NOT NULL, 1=NULL
|
||||
default_value TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (table_name, column_name),
|
||||
FOREIGN KEY (table_name) REFERENCES metadata_tables(table_name) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_metadata_columns_table ON metadata_columns(table_name);
|
||||
```
|
||||
|
||||
### Insert metadata after creating table
|
||||
|
||||
```sql
|
||||
-- Table metadata
|
||||
INSERT INTO metadata_tables (table_name, description)
|
||||
VALUES ('orders', 'Table storing customer order information');
|
||||
|
||||
-- Column metadata
|
||||
INSERT INTO metadata_columns (table_name, column_name, description, data_type, is_nullable)
|
||||
VALUES
|
||||
('orders', 'id', 'Primary key, auto increment', 'INTEGER', 0),
|
||||
('orders', 'order_number', 'Unique order code', 'TEXT', 0),
|
||||
('orders', 'user_id', 'FK to users.id', 'INTEGER', 0),
|
||||
('orders', 'status', 'pending|confirmed|shipped|cancelled', 'TEXT', 0),
|
||||
('orders', 'total_amount', 'Total value', 'REAL', 0);
|
||||
```
|
||||
|
||||
### Query metadata
|
||||
|
||||
```sql
|
||||
-- View schema with descriptions
|
||||
SELECT
|
||||
t.table_name,
|
||||
t.description AS table_description,
|
||||
c.column_name,
|
||||
c.description AS column_description,
|
||||
c.data_type
|
||||
FROM metadata_tables t
|
||||
LEFT JOIN metadata_columns c ON t.table_name = c.table_name
|
||||
ORDER BY t.table_name, c.column_name;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Auto Increment
|
||||
|
||||
```sql
|
||||
-- Option 1: INTEGER PRIMARY KEY (recommended)
|
||||
-- SQLite auto-increments INTEGER PRIMARY KEY automatically
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT
|
||||
);
|
||||
|
||||
-- Option 2: AUTOINCREMENT keyword
|
||||
-- Ensures deleted IDs are not reused (slower)
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Foreign Keys
|
||||
|
||||
**IMPORTANT**: FK is disabled by default in SQLite!
|
||||
|
||||
```sql
|
||||
-- Enable FK for each connection
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
-- Create table with FK
|
||||
CREATE TABLE order_items (
|
||||
id INTEGER PRIMARY KEY,
|
||||
order_id INTEGER NOT NULL,
|
||||
product_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
-- Create index for FK
|
||||
CREATE INDEX idx_order_items_order ON order_items(order_id);
|
||||
CREATE INDEX idx_order_items_product ON order_items(product_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ENUM substitute
|
||||
|
||||
SQLite does not have ENUM. Use CHECK constraint:
|
||||
|
||||
```sql
|
||||
CREATE TABLE orders (
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'confirmed', 'shipped', 'cancelled'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Index Types
|
||||
|
||||
SQLite only supports B-Tree indexes:
|
||||
|
||||
```sql
|
||||
-- Standard index
|
||||
CREATE INDEX idx_orders_status ON orders(status);
|
||||
|
||||
-- Composite index
|
||||
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
|
||||
|
||||
-- Unique index
|
||||
CREATE UNIQUE INDEX idx_orders_number ON orders(order_number);
|
||||
|
||||
-- Partial index (SQLite 3.8.0+)
|
||||
CREATE INDEX idx_orders_active ON orders(user_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Limitations
|
||||
|
||||
| Feature | Status | Workaround |
|
||||
|---------|--------|------------|
|
||||
| `ALTER COLUMN` | ❌ Not supported | Recreate table |
|
||||
| `DROP COLUMN` | ✅ SQLite 3.35+ | Recreate table (older) |
|
||||
| Comments | ❌ Not supported | Metadata tables |
|
||||
| ENUM | ❌ Not supported | CHECK constraint |
|
||||
| Stored procedures | ❌ Not supported | App logic |
|
||||
| Concurrent writes | ⚠️ Limited | Single writer |
|
||||
|
||||
---
|
||||
|
||||
## Example DDL
|
||||
|
||||
```sql
|
||||
-- Enable foreign keys
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
-- Create table
|
||||
CREATE TABLE orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_number TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'confirmed', 'shipped', 'cancelled')),
|
||||
subtotal REAL NOT NULL DEFAULT 0,
|
||||
discount_amount REAL NOT NULL DEFAULT 0,
|
||||
total_amount REAL NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
deleted_at TEXT,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE UNIQUE INDEX idx_orders_number ON orders(order_number);
|
||||
CREATE INDEX idx_orders_user ON orders(user_id);
|
||||
CREATE INDEX idx_orders_status ON orders(status);
|
||||
CREATE INDEX idx_orders_updated ON orders(updated_at);
|
||||
|
||||
-- Metadata
|
||||
INSERT INTO metadata_tables (table_name, description)
|
||||
VALUES ('orders', 'Table storing order information');
|
||||
|
||||
INSERT INTO metadata_columns (table_name, column_name, description, data_type, is_nullable)
|
||||
VALUES
|
||||
('orders', 'id', 'Primary key', 'INTEGER', 0),
|
||||
('orders', 'order_number', 'Unique order code', 'TEXT', 0),
|
||||
('orders', 'user_id', 'FK to users.id', 'INTEGER', 0),
|
||||
('orders', 'status', 'pending|confirmed|shipped|cancelled', 'TEXT', 0),
|
||||
('orders', 'total_amount', 'Total payment', 'REAL', 0);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] `PRAGMA foreign_keys = ON` at start of each connection
|
||||
- [ ] Metadata tables created
|
||||
- [ ] INSERT metadata after each CREATE TABLE
|
||||
- [ ] Use `TEXT` for dates (ISO format: YYYY-MM-DD HH:MM:SS)
|
||||
- [ ] CHECK constraint instead of ENUM
|
||||
- [ ] Index for FK columns
|
||||
- [ ] Update metadata when ALTER/DROP table
|
||||
Reference in New Issue
Block a user