Skip to content

Latest commit

 

History

History
392 lines (310 loc) · 18.2 KB

File metadata and controls

392 lines (310 loc) · 18.2 KB

FEATURE_PARITY.md - Implementation Status

This document tracks feature parity between Python SQLModel and Rust SQLModel.

Last Updated: 2026-02-10 (Relationships: cascade delete/orphan tracking incl composite keys + composite many-to-many link tables)


Summary

Category Implemented Total Coverage
Core Model 12 12 100%
Field Options 14 16 88%
Query Building 22 22 100%
Expression Operators 20 20 100%
Session/Connection 8 8 100%
Transactions 6 6 100%
Schema/DDL 7 8 88%
Validation 5 6 83%
Relationships 6 6 100%
Serialization 4 4 100%
Database Drivers 3 3 100%
Connection Pooling 8 8 100%
TOTAL 115 119 97%

1. Core Model Features

Feature Python Rust Status
Define model as class/struct class Hero(SQLModel) #[derive(Model)] ✅ Complete
Table name override __tablename__ #[sqlmodel(table = "...")] ✅ Complete
Auto-derive table name class name → lowercase struct name → lowercase ✅ Complete
Field metadata Field() #[sqlmodel(...)] ✅ Complete
Convert struct to row .model_dump() .to_row() ✅ Complete
Convert row to struct Model.model_validate() Model::from_row() ✅ Complete
Primary key access Automatic .primary_key_value() ✅ Complete
Is new (unsaved) Session tracking .is_new() ✅ Complete
Field info metadata model_fields Model::FIELDS ✅ Complete
Column names __table__.columns Model::COLUMN_NAMES ✅ Complete
SQL type inference get_sqlalchemy_type() Proc macro inference ✅ Complete
Skip field in SQL N/A (Pydantic only) #[sqlmodel(skip)] ✅ Complete

2. Field Options

Feature Python Rust Status
Primary key Field(primary_key=True) #[sqlmodel(primary_key)] ✅ Complete
Auto increment Automatic for int PKs #[sqlmodel(auto_increment)] ✅ Complete
Foreign key Field(foreign_key="...") #[sqlmodel(foreign_key = "...")] ✅ Complete
On delete action Field(ondelete="CASCADE") #[sqlmodel(on_delete = "CASCADE")] ✅ Complete
On update action Field(onupdate="...") #[sqlmodel(on_update = "...")] ✅ Complete
Unique constraint Field(unique=True) #[sqlmodel(unique)] ✅ Complete
Nullable Field(nullable=True) Option<T> ✅ Complete
Index Field(index=True) #[sqlmodel(index = "...")] ✅ Complete
Default value Field(default=...) #[sqlmodel(default = "...")] ✅ Complete
Default factory Field(default_factory=...) Default trait ✅ Complete
Column name override sa_column(name=...) #[sqlmodel(column = "...")] ✅ Complete
SQL type override Field(sa_type=...) #[sqlmodel(sql_type = "...")] ✅ Complete
Max length Field(max_length=N) #[sqlmodel(max_length = N)] ✅ Complete
Decimal precision Field(max_digits=N) #[sqlmodel(max_digits = N)] ✅ Complete
Decimal scale Field(decimal_places=N) #[sqlmodel(decimal_places = N)] ✅ Complete

3. Query Building

Feature Python Rust Status
SELECT all columns select(Model) select!(Model) ✅ Complete
SELECT specific columns select(Model.col) .columns(&["..."]) ✅ Complete
WHERE equals .where(col == val) .filter(Expr::col("").eq()) ✅ Complete
WHERE comparison <, <=, >, >= .lt(), .le(), .gt(), .ge() ✅ Complete
WHERE LIKE .contains(), .startswith() .like(), .contains(), .starts_with(), .ends_with() ✅ Complete
WHERE IN .in_([...]) .in_list() ✅ Complete
WHERE BETWEEN between(a, b) .between() ✅ Complete
WHERE IS NULL == None .is_null() ✅ Complete
AND conditions Multiple .where() .filter() chain / .and() ✅ Complete
OR conditions or_(...) .or_filter() / .or() ✅ Complete
ORDER BY .order_by(col) .order_by() ✅ Complete
ORDER BY DESC .order_by(col.desc()) .order_by_desc() ✅ Complete
LIMIT .limit(N) .limit(N) ✅ Complete
OFFSET .offset(N) .offset(N) ✅ Complete
JOIN .join(Model) .join() ✅ Complete
GROUP BY .group_by(col) .group_by() ✅ Complete
HAVING .having(...) .having() ✅ Complete
DISTINCT .distinct() .distinct() ✅ Complete
FOR UPDATE .with_for_update() .for_update() ✅ Complete
COUNT func.count() Expr::count() ✅ Complete
SUM/AVG/MIN/MAX func.sum() etc Expr::sum() etc ✅ Complete

4. INSERT/UPDATE/DELETE

Feature Python Rust Status
Insert single session.add(obj) insert!(obj).execute() ✅ Complete
Insert bulk session.add_all([...]) insert_many!([...]) ✅ Complete
Insert returning .returning(...) .returning() ✅ Complete
Upsert (conflict) Custom SQL .on_conflict_do_nothing() ✅ Complete
Upsert update Custom SQL .on_conflict_do_update() ✅ Complete
Update by object session.add(obj) update!(obj).execute() ✅ Complete
Update bulk update(Model).values() .set() builder ✅ Complete
Delete by object session.delete(obj) N/A (use filter) ✅ Different
Delete bulk delete(Model).where() delete!(Model).filter() ✅ Complete

5. Session/Connection Operations

Feature Python Rust Status
Execute query session.exec(stmt) conn.query() ✅ Complete
Get all results .all() .all() ✅ Complete
Get first result .first() .first() ✅ Complete
Get exactly one .one() .one() (errors on 0 or >1 rows) ✅ Complete
Get one or none .one_or_none() .one_or_none() ✅ Complete
Execute non-query session.execute() conn.execute() ✅ Complete
Raw SQL query text("...") raw_query!() ✅ Complete
Raw SQL execute text("...") raw_execute!() ✅ Complete

6. Transactions

Feature Python Rust Status
Auto transaction with Session(...) Explicit ✅ Different
Begin transaction session.begin() conn.begin() ✅ Complete
Commit session.commit() tx.commit() ✅ Complete
Rollback session.rollback() tx.rollback() ✅ Complete
Savepoints session.begin_nested() tx.savepoint() ✅ Complete
Isolation levels Engine config IsolationLevel enum ✅ Complete

7. Schema/DDL Operations

Feature Python Rust Status
CREATE TABLE metadata.create_all() create_table::<M>() ✅ Complete
CREATE IF NOT EXISTS Automatic .if_not_exists() ✅ Complete
DROP TABLE metadata.drop_all() drop_table() ✅ Complete
Primary key constraint Automatic Automatic ✅ Complete
Foreign key constraint Automatic Automatic ✅ Complete
Unique constraint Automatic Automatic ✅ Complete
Migration tracking Alembic MigrationRunner ✅ Implemented
Auto-generate migrations Alembic schema_diff + MigrationWriter ✅ Implemented
Database introspection inspect(engine) Partial (tables/columns/FKs/indexes + CHECK constraints + table comments across dialects; pg/mysql driver-backed integration coverage) ⚠️ Partial

8. Validation

Feature Python Rust Status
Field validator @field_validator #[derive(Validate)] ✅ Complete
Model validator @model_validator #[validate(model = \"fn_name\")] ✅ Complete (explicit, compile-time wired)
Numeric range Field(gt=, ge=, lt=, le=) #[validate(min=, max=)] ✅ Complete
String length Field(min_length=, max_length=) #[validate(min_length=, max_length=)] ✅ Complete
Regex pattern Field(regex=) #[validate(pattern=)] ✅ Complete
Custom validators Python functions #[validate(custom="fn_name")] ✅ Complete

9. Relationships

Feature Python Rust Status
One-to-many Relationship() RelatedMany<T> + Session::load_one_to_many ✅ Implemented (explicit batch load)
Many-to-one Relationship() Lazy<T> / Related<T> + Session::{load_lazy,load_many} ✅ Implemented (explicit load/batch-load)
Many-to-many Relationship(link_model=) RelatedMany<T> + Session::load_many_to_many + flush_related_many ✅ Implemented
Back populates back_populates= Session::{relate_to_one,unrelate_from_one} helpers + metadata ✅ Implemented (explicit sync helper)
Cascade delete cascade_delete=True Session::flush uses RelationshipInfo to plan explicit dependent DELETEs (Active) and detach loaded children after parent delete (Passive), including composite FK tuples and composite many-to-many link-table deletes ✅ Implemented (Active emits explicit child/link DELETEs; Passive detaches loaded children)
Lazy loading Automatic Lazy<T> ✅ Implemented (explicit, cancel-correct)

Note: Relationships are handled differently in Rust: prefer explicit load/batch-load (Lazy<T>, Session::load_many_to_many) or explicit JOIN queries rather than implicit N+1 behavior.


10. Serialization (via Serde)

Feature Python Rust Status
To dict/struct model_dump() Native struct ✅ N/A
To JSON model_dump_json() serde_json::to_string() ✅ Complete
From dict Model(**dict) Model { ... } ✅ N/A
From JSON model_validate_json() serde_json::from_str() ✅ Complete
Exclude fields exclude= #[serde(skip)] ✅ Complete
Rename fields alias= #[serde(rename = "...")] ✅ Complete

11. Database Drivers

Driver Python Rust Status
SQLite Via SQLAlchemy sqlmodel-sqlite ✅ Complete
MySQL Via SQLAlchemy sqlmodel-mysql ✅ Complete
PostgreSQL Via SQLAlchemy sqlmodel-postgres ✅ Complete
Prepared statements Automatic Binary protocol ✅ Complete
TLS/SSL Engine config Feature-gated (rustls) ✅ Complete
Connection string URL parsing Config struct ✅ Complete

MySQL Driver Details (sqlmodel-mysql)

Feature Status Notes
Wire protocol ✅ Complete Full MySQL protocol implementation
Authentication ✅ Complete mysql_native_password, caching_sha2_password
TLS/SSL ✅ Complete Via rustls (SslMode::Disable/Preferred/Required/VerifyCa/VerifyIdentity)
Prepared statements ✅ Complete Binary protocol with COM_STMT_PREPARE/EXECUTE
Async connection ✅ Complete Via asupersync TCP primitives
Packet fragmentation ✅ Complete Handles TCP packet splitting
Connection pooling ✅ Complete Via sqlmodel-pool

PostgreSQL Driver Details (sqlmodel-postgres)

Feature Status Notes
Wire protocol ✅ Complete Full Postgres protocol
Authentication ✅ Complete MD5, SCRAM-SHA-256
TLS/SSL ✅ Complete Via rustls
Prepared statements ✅ Complete Named statements
Transactions ✅ Complete All isolation levels
Type conversion ✅ Complete All major types

SQLite Driver Details (sqlmodel-sqlite)

Feature Status Notes
In-memory DB ✅ Complete :memory: support
File DB ✅ Complete File path support
Transactions ✅ Complete Via sqlite3
Concurrent access ✅ Complete Via mutex

12. Connection Pooling

Feature Python Rust Status
Pool creation create_engine(pool_size=) Pool::new(config) ✅ Complete
Min/max connections Engine config PoolConfig ✅ Complete
Acquire connection Automatic pool.acquire() ✅ Complete
Release connection Automatic RAII (drop) ✅ Complete
Health checks Optional test_on_checkout ✅ Complete
Idle timeout Engine config idle_timeout ✅ Complete
Max lifetime Engine config max_lifetime ✅ Complete
Pool statistics N/A pool.stats() ✅ Complete

Critical Missing Features

Priority 1 (Should Implement)

1. #[derive(Validate)] macro - Generates validation logic at compile timeIMPLEMENTED

  • ✅ Numeric constraints (min, max)
  • ✅ String constraints (min_length, max_length)
  • ✅ Custom validator methods
  • ✅ Full regex patterns (#[validate(pattern = \"...\")]) with compile-time pattern validation

2. on_delete foreign key action - CASCADE, SET NULL, RESTRICTIMPLEMENTED

  • #[sqlmodel(foreign_key = "...", on_delete = "CASCADE")]
  • #[sqlmodel(on_update = "...")] also supported

3. SQL type override - #[sqlmodel(sql_type = "VARCHAR(500)")]IMPLEMENTED

  • ✅ DDL generation uses override when specified
  • effective_sql_type() method handles fallback

Priority 2 (Nice to Have)

1. Decimal precision/scale - For financial applicationsIMPLEMENTED

  • #[sqlmodel(max_digits = 10, decimal_places = 2)] is reflected in DDL via FieldInfo::effective_sql_type()
  • You can still override explicitly via #[sqlmodel(sql_type = "DECIMAL(10,2)")]

2. Full regex validation - Beyond email/url patternsIMPLEMENTED

  • #[validate(pattern = "...")] uses cached runtime compilation + compile-time pattern validation

Priority 3 (No Exclusions)

This project has no exclusions (see bd-162). Items previously listed here are either implemented or explicitly tracked as remaining work:

  • Lazy loading: implemented (Lazy<T>, Session::{load_lazy, load_many}) (bd-3lz)
  • Unit of work + identity map: implemented (sqlmodel-session) (bd-3lz)
  • Generic models: implemented (see crates/sqlmodel/src/lib.rs tests)
  • Computed fields + hybrid properties: implemented (#[sqlmodel(computed)], Hybrid<T>) (bd-1fs)
  • Migration generation: implemented (schema_diff + MigrationWriter); remaining gaps tracked under bd-162

Test Coverage

Crate Unit Tests Integration Tests Coverage
sqlmodel-core - Good
sqlmodel-macros - Good
sqlmodel-query - Good
sqlmodel-schema ⚠️ Via postgres/mysql driver integration suites Improving
sqlmodel-pool - Good
sqlmodel-mysql ✅ 58+ tests Excellent
sqlmodel-sqlite - Good
sqlmodel-postgres Good

Conclusion

The Rust SQLModel implementation covers the core ORM functionality (Model derive, query building, CRUD operations, transactions, connection pooling, validation). Remaining parity work is tracked in Beads under bd-162.

Fully Production-Ready

  1. All 3 database drivers - PostgreSQL, MySQL, SQLite fully functional
  2. Complete query builder - SELECT, INSERT, UPDATE, DELETE with all operators
  3. Full transaction support - Isolation levels, savepoints, auto-rollback
  4. Connection pooling - All configuration options, health checks, statistics
  5. TLS/SSL - Implemented for MySQL and PostgreSQL via rustls
  6. Prepared statements - MySQL binary protocol, PostgreSQL named statements
  7. Validate derive macro - Numeric/string constraints, custom validators
  8. SQL type override - #[sqlmodel(sql_type = "...")] for DDL customization
  9. Referential actions - on_delete and on_update foreign key actions

Remaining Work

All missing/partial features must be represented as explicit Beads tasks under bd-162. This document should not list exclusions.


Appendix: Expression System Details

The Rust implementation includes a complete type-safe expression system:

Supported Expression Types

  • Expr::Column - Column references with optional table qualifier
  • Expr::Literal - Type-safe literal values
  • Expr::Binary - All binary operators (=, <>, <, <=, >, >=, AND, OR, +, -, *, /, %, &, |, ^, ||)
  • Expr::Unary - NOT, -, ~
  • Expr::Function - Aggregate functions (COUNT, SUM, AVG, MIN, MAX) and custom functions
  • Expr::Case - CASE WHEN ... THEN ... ELSE ... END
  • Expr::In - IN / NOT IN lists
  • Expr::Between - BETWEEN / NOT BETWEEN
  • Expr::IsNull - IS NULL / IS NOT NULL
  • Expr::IsDistinctFrom - IS DISTINCT FROM / IS NOT DISTINCT FROM (NULL-safe comparison)
  • Expr::Cast - CAST(expr AS type)
  • Expr::Like - LIKE / ILIKE with dialect fallbacks
  • Expr::Subquery - Subquery expressions
  • Expr::Raw - Raw SQL escape hatch

String Helper Methods

  • .contains(pattern) - LIKE '%pattern%'
  • .starts_with(pattern) - LIKE 'pattern%'
  • .ends_with(pattern) - LIKE '%pattern'
  • .icontains(pattern) - Case-insensitive contains (ILIKE fallback)
  • .istarts_with(pattern) - Case-insensitive starts_with
  • .iends_with(pattern) - Case-insensitive ends_with

String Functions

  • .upper() - UPPER(expr)
  • .lower() - LOWER(expr)
  • .length() - LENGTH(expr)
  • .trim() / .ltrim() / .rtrim() - Whitespace trimming
  • .substr(start, len) - SUBSTR extraction
  • .replace(from, to) - REPLACE function

NULL Handling Functions

  • Expr::coalesce(args) - COALESCE(a, b, c, ...)
  • Expr::nullif(a, b) - NULLIF(a, b)
  • Expr::ifnull(a, b) - IFNULL/COALESCE for two args

Numeric Functions

  • .abs() - ABS(expr)
  • .round(decimals) - ROUND(expr, n)
  • .floor() - FLOOR(expr)
  • .ceil() - CEIL(expr)

Type Casting

  • .cast(type_name) - CAST(expr AS type)

Multi-Dialect Support

  • PostgreSQL - $1, $2, ... placeholders, double-quote identifiers, ILIKE support
  • SQLite - ?1, ?2, ... placeholders, double-quote identifiers
  • MySQL - ?, ?, ... placeholders, backtick identifiers, CONCAT() function

Last verified: 2026-01-27