Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
849 changes: 849 additions & 0 deletions .claude/architecture.md

Large diffs are not rendered by default.

577 changes: 577 additions & 0 deletions .claude/backend_development.md

Large diffs are not rendered by default.

140 changes: 139 additions & 1 deletion .claude/code_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,48 @@ result = (
| Protected | Single underscore | `_protected_method` |
| Magic | Double underscore | `__table_name__`, `__primary_key__` |

### Sync/Async Function Signature Naming Rules

**CRITICAL**: For functions that need to distinguish between synchronous and asynchronous versions (e.g., I/O operations), the naming convention is strictly defined:

1. **Method names must be IDENTICAL** - No `_async` suffix or similar distinctions
2. **Only the `async`/`await` keywords differ** - Everything else in the signature must match
3. **This applies to**:
- Method names
- Parameter names and types
- Return type annotations (except `async` methods return `Coroutine`)

```python
# CORRECT: Sync/Async methods with identical names
class StorageBackend:
def connect(self) -> None:
"""Establish connection to database."""
pass

def execute(self, sql: str, params: Optional[Tuple] = None) -> QueryResult:
"""Execute a SQL query."""
pass

class AsyncStorageBackend:
async def connect(self) -> None:
"""Establish connection to database asynchronously."""
pass

async def execute(self, sql: str, params: Optional[Tuple] = None) -> QueryResult:
"""Execute a SQL query asynchronously."""
pass

# WRONG: Do NOT use _async suffix
class BadExample:
async def connect_async(self): # ❌ WRONG
pass

async def execute_async(self, sql: str): # ❌ WRONG
pass
```

**Rationale**: This convention ensures that sync and async APIs are strictly equivalent, making it easier for users to switch between them without learning different method names. The only difference users need to be aware of is the `async`/`await` keywords.

### Special Naming Patterns

```python
Expand Down Expand Up @@ -271,6 +313,10 @@ def utility_function():
__all__ = ['MainClass', 'utility_function']
```

#### Grouping Functionalities and File Splitting
- If a single file contains several functionalities and the file is not long, it is recommended to group and arrange these functionalities together, and add section comments (e.g., `# region ... # endregion`).
- If the file is too long, e.g., over 1000 lines, and a single group also exceeds 400 lines, it is recommended to split it into separate files under a directory package.

### Class Organization

```python
Expand Down Expand Up @@ -566,6 +612,94 @@ class TimestampMixin:
self.updated_at = datetime.now()
```

## Expression-Dialect System Coding Standards

### Expression Class Guidelines

1. **Proper Inheritance**:
- All expression classes must inherit from `BaseExpression`
- Value expressions inherit from `SQLValueExpression`
- Predicate expressions inherit from `SQLPredicate`
- Use appropriate mixins for functionality (e.g., `ArithmeticMixin`, `ComparisonMixin`)

2. **Protocol Implementation**:
- Implement `ToSQLProtocol` with proper `to_sql()` method
- Return `SQLQueryAndParams` type (SQL string and parameter tuple)
- Never directly concatenate SQL strings in expression classes

3. **Dialect Delegation**:
- Always delegate formatting to dialect methods
- Use `self.dialect.format_*()` methods for all SQL formatting
- Maintain separation between query structure and SQL generation

4. **Explicit Control**:
- Expression classes should not maintain internal state
- No hidden behaviors or automatic operations
- Keep expressions stateless and pure
- Unlike systems with complex object state management, our expressions are simple and predictable

### Dialect Class Guidelines

1. **Base Class Inheritance**:
- All dialect classes must inherit from `SQLDialectBase`
- Implement all required abstract methods

2. **Formatting Methods**:
- Implement `format_*` methods for all SQL formatting needs
- Handle identifier quoting appropriately
- Properly handle parameter binding

3. **Database-Specific Logic**:
- Include database-specific syntax variations
- Handle version-specific features
- Implement feature detection where needed

### Expression System Module Organization

Follow the established module structure:
- `bases.py`: Abstract base classes and protocols
- `core.py`: Core expressions (columns, literals, functions)
- `mixins.py`: Operator overloading capabilities
- `operators.py`: SQL operations
- `predicates.py`: Predicate expressions
- `query_parts.py`: Query clauses
- `statements.py`: DML/DDL statements
- `functions.py`: Factory functions
- `aggregates.py`: Aggregation expressions
- `advanced_functions.py`: Advanced SQL features
- `query_sources.py`: Data source expressions
- `graph.py`: Graph query expressions

### Example Expression Class

```python
from typing import TYPE_CHECKING
from . import bases, mixins

if TYPE_CHECKING: # pragma: no cover
from ..dialect import SQLDialectBase

class MyExpression(mixins.ArithmeticMixin, mixins.ComparisonMixin, bases.SQLValueExpression):
"""Represents a custom SQL expression."""
def __init__(self, dialect: "SQLDialectBase", value: str):
super().__init__(dialect)
self.value = value

def to_sql(self) -> 'bases.SQLQueryAndParams':
# Delegate formatting to dialect
formatted_value = self.dialect.format_string_literal(self.value)
return formatted_value, (self.value,)
```

### Example Dialect Method

```python
def format_identifier(self, identifier: str) -> str:
"""Format identifier with proper quoting."""
# Database-specific identifier formatting
return f'"{identifier}"' # Example for standard SQL
```

## Code Review Checklist

- [ ] Path comment at file start matches actual location
Expand All @@ -577,4 +711,8 @@ class TimestampMixin:
- [ ] Exception messages are informative
- [ ] Test coverage for new code
- [ ] Security considerations addressed
- [ ] Performance implications considered
- [ ] Performance implications considered
- [ ] Expression classes properly delegate to dialect
- [ ] Dialect methods handle formatting correctly
- [ ] Expression-dialect separation maintained
- [ ] Expression system module organization followed
25 changes: 25 additions & 0 deletions .claude/commands/check-sync-async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Check sync/async API parity

Check that sync and async APIs are properly paired following the parity rules.

## Parity Rules to Verify

1. **Class Names**: Async version adds Async prefix
- BaseActiveRecord → AsyncBaseActiveRecord
- ActiveQuery → AsyncActiveQuery

2. **Method Names**: Remain IDENTICAL (no _async suffix)
- def save(self) → async def save(self)
- NOT: async def save_async(self)

3. **Docstrings**: Async version notes "asynchronously" in first sentence

4. **Field Order**: Declaration order must match exactly

5. **Testing Parity**:
- Fixtures: order_fixtures → async_order_fixtures
- Test classes: TestQuery → TestAsyncQuery
- Test methods: identical names
- Schema files: shared between sync/async

Scan the codebase for violations and report any issues found.
148 changes: 148 additions & 0 deletions .claude/commands/gen-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
description: Generate database migration SQL for models
agent: build
---

Generate SQL migration for creating or modifying database tables.

## Migration Configuration

Ask the user for the following information:

1. **Migration Type**:
- `create` - Create new table
- `alter` - Modify existing table
- `drop` - Drop table
- `index` - Add index
2. **Target Model** (e.g., `User`, `Order`)
3. **Database Type** (sqlite, postgresql, mysql - affects SQL syntax)
4. **Fields** (for create/alter: `name:type:constraints`)
5. **Foreign Keys** (optional references to other tables)

## Generated SQL Examples

### Create Table

```sql
-- SQLite / PostgreSQL / MySQL
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- SQLite
-- id SERIAL PRIMARY KEY, -- PostgreSQL
-- id INT AUTO_INCREMENT PRIMARY KEY, -- MySQL
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
age INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Indexes
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_status ON users(status);
```

### Alter Table

```sql
-- Add column
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- Modify column (PostgreSQL)
ALTER TABLE users ALTER COLUMN age SET DEFAULT 18;

-- SQLite workaround for ALTER (limited support)
-- Requires table recreation
```

### Foreign Keys

```sql
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
total DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

-- Foreign key constraint
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Index for foreign key
CREATE INDEX idx_orders_user_id ON orders(user_id);
```

### Join Table (Many-to-Many)

```sql
CREATE TABLE user_roles (
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);

CREATE INDEX idx_user_roles_user_id ON user_roles(user_id);
CREATE INDEX idx_user_roles_role_id ON user_roles(role_id);
```

## Field Type Mapping

| Python Type | SQLite | PostgreSQL | MySQL |
|-------------|--------|------------|-------|
| `int` | INTEGER | INTEGER | INT |
| `str` | TEXT/VARCHAR | VARCHAR | VARCHAR |
| `float` | REAL | DOUBLE PRECISION | DOUBLE |
| `bool` | INTEGER | BOOLEAN | BOOLEAN |
| `datetime` | TIMESTAMP | TIMESTAMP | TIMESTAMP |
| `date` | DATE | DATE | DATE |
| `Decimal` | DECIMAL | DECIMAL | DECIMAL |
| `Optional[T]` | NULL | NULL | NULL |

## Constraints

```sql
-- Primary Key (auto-increment)
id INTEGER PRIMARY KEY AUTOINCREMENT -- SQLite
id SERIAL PRIMARY KEY -- PostgreSQL
id INT AUTO_INCREMENT PRIMARY KEY -- MySQL

-- Unique
email VARCHAR(255) UNIQUE

-- Not Null
name VARCHAR(100) NOT NULL

-- Default
status VARCHAR(20) DEFAULT 'active'

-- Check (PostgreSQL/MySQL)
age INTEGER CHECK (age >= 0)

-- Foreign Key with actions
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
```

## Migration File Naming

```
migrations/
├── 001_create_users.sql
├── 002_create_orders.sql
├── 003_add_user_indexes.sql
└── 004_add_order_status.sql
```

## Best Practices

1. **Always create indexes** on foreign keys and frequently queried columns
2. **Use CASCADE carefully** - understand data deletion impact
3. **Add timestamps** (created_at, updated_at) for audit trails
4. **Use appropriate field sizes** - don't use TEXT for short strings
5. **Test migrations** on a copy of production data before deploying
Loading
Loading