SeaQL/sea-orm
github.com/SeaQL/sea-orm ↗ · 1593 files · 6.8 MB
SeaORM is a powerful ORM for building web services in Rust Support us with a ⭐ !
Overview
SeaORM is a Rust ORM for applications that need typed relational queries, partial updates, nested persistence, and database migrations. At commit db40a19a7bde7be8e1c4b346b1b70d72e87f0245, its async library supports SQLx-backed MySQL/MariaDB, PostgreSQL, and SQLite, alongside a separate synchronous implementation. Entity derives, schema discovery and generation, optional RBAC, and Arrow conversion extend the core library; the web servers and frontend integrations are example applications.
System map
The repository centers on a reusable Rust library, with companion tooling and independently runnable examples. Compile-time entity generation, runtime query execution, and migration execution occupy distinct boundaries. Cargo features select drivers and optional capabilities, while separate workspaces require package-specific validation.
- The root package is sea-orm 2.0.4, exposes the sea_orm library through src/lib.rs, and declares edition 2024 and Rust 1.94.0; its workspace contains the root, macros, codegen, and Arrow packages.
- src/lib.rs exports entity, query, schema, dynamic, and value APIs, re-exports database and executor APIs, and gates the rbac module behind its feature.
- DatabaseConnection wraps DatabaseConnectionType, whose feature-gated variants hold SQLx pools, a rusqlite shared connection, mock or proxy connections, or a disconnected sentinel.
- The async storage integration uses SeaQuery for dialect-specific statements and optional SQLx 0.9 dependencies; default features enable derives and common value types but do not select a SQLx driver or runtime.
- sea-orm-macros supplies entity, model, relation, value, and query-result derives; sea-orm-codegen converts discovered table statements into entities and generated Rust files.
- The CLI is a separate workspace with sea and sea-orm-cli binaries sharing src/bin/main.rs; migration binaries call sea_orm_migration::cli::run_cli with an application-defined migrator.
- sea-orm-sync is a separate package and workspace exposing the same sea_orm library name, depending on sea-query-rusqlite; make-sync.sh derives its source and tests from the async tree by removing async constructs.
- Distribution includes Rust library packages and tagged CLI binaries for Linux x86_64/aarch64, macOS aarch64, and Windows x86_64; CI defines workspace tests plus separate CLI and migration checks.
Feature catalog
The developer-facing surface spans entity modeling, query execution, persistence, schema management, and code generation. Optional features add access control and analytical data conversion. Examples demonstrate HTTP, JSON-RPC, OpenAPI, synchronous database use, and checkpointed computation; this catalog describes inspected capabilities rather than an exhaustive inventory.
- ActiveModel supports insert, update, save, and delete with lifecycle hooks; nested relation state distinguishes untouched relations, additive child writes, and replacement that deletes omitted children.
- Select APIs offer optional or required single-row results, vectors, and streams; pagination supports LIMIT/OFFSET pages and keyset cursors with before/after boundaries and key-arity validation.
- Relation APIs cover has-one, has-many, junction-table many-to-many, and self-reference; LoaderTrait batches related queries, while raw_sql! constructs a Statement containing separate SQL and values for three supported dialects.
- SchemaBuilder offers initial apply and additive experimental sync; migration commands include init, generate, fresh, refresh, reset, status, up with optional steps, and down defaulting to one step.
- generate entity exposes table selection, hidden-table inclusion, schema selection, Serde controls, custom derives and attributes, Seaography helpers, experimental preservation, and optional Mermaid output; EntityWriter supports compact, expanded, frontend, and dense formats.
- Optional RBAC loads permission data into an engine and produces user-bound restricted connections; authorization considers inherited roles, explicit user overrides, exact grants, and wildcard permissions/resources.
- Arrow-enabled entities convert ActiveModels to and from RecordBatch; the synchronous Parquet example writes and reads a file before inserting restored rows into SQLite, and pi_spigot exposes digit count, checkpoint interval, and database-path options.
- Inspected examples provide Actix post-list/create/edit/delete pages, JSON-RPC Post.List/Insert/Update/Delete methods, and Rocket OpenAPI documentation through Swagger UI and RapiDoc; README also enumerates Axum, GraphQL, Loco, Poem, Salvo, Tonic, and Seaography integrations.
Data flow
Typed entity metadata becomes SeaQuery statements, then backend SQL and bound values, then driver results decoded into models. Connections own database handles, while selectors and ActiveModels own query and mutation state. Tooling follows separate pipelines from live schema to generated files and from migration definitions to recorded database versions.
- Database::connect converts its input into ConnectOptions, validates the URL, dispatches by enabled backend prefix, and returns a connection error when parsing or driver selection fails.
- ConnectionTrait::execute/query_one/query_all lower StatementBuilder inputs through DbBackend::build; PostgreSQL sqlx_query passes Statement.sql and its Values separately to sqlx::query_with.
- Selector::one adds LIMIT 1 and decodes an optional QueryResult; Selector::all decodes every returned row through SelectorTrait::from_raw_query_result, propagating conversion errors.
- ActiveValue state controls writes: Set carries values to persist, Unchanged is omitted from UPDATE, and NotSet is omitted from INSERT and UPDATE; save selects insertion or update based on whether all primary-key columns are present.
- ActiveModel::from_json merges defaults for deserialization, tracks which JSON keys were supplied, and converts absent fields to NotSet; set_from_json backs up and restores primary-key values around replacement.
- Schema discovery filters selected, hidden, ignored, and generated columns before producing table statements; EntityTransformer builds columns, primary keys, enums, and relations, pruning references to tables outside the selected set.
- Migration status compares registered migration names with stored seaql_migrations versions; applying a migration records its name and timestamp, while rollback removes its version record.
- RestrictedConnection routes structured statements through an audit that yields action/schema/table requests; RbacEngineMount reads its shared engine and requires every request to be permitted before forwarding execution.
Behavior traces
The inspected traces follow application startup, database writes and reads, CLI generation, migrations, authorization, and file-backed conversion. Each sequence identifies its relevant functions and branches. These are source-derived execution paths, not observed runtime traces.
- Actix startup proceeds from the binary main to actix_example_api::main, start, environment loading, Database::connect, Migrator::up, Tera loading, AppState creation, route registration, listener adoption or bind, and server.run.
- POST / calls create, extracts post::Model, and invokes Mutation::create_post; the service sets title/text and calls save, which takes the missing-primary-key insert branch, runs before_save, executes insertion with model retrieval, runs after_save, and returns an HTTP redirect.
- GET / calls list, defaults page to 1 and page size to 5, then find_posts_in_page builds an ID-ordered paginator, counts pages, and fetches page-1; fetch_page applies LIMIT/OFFSET, queries and decodes rows, and the handler renders index.html.tera.
- Entity generation runs binary main → CLI main/Cli::parse → run_generate_command → backend sqlx_connect/SchemaDiscovery → EntityTransformer::transform → EntityWriter::generate → file writes → rustfmt; preservation fallback can make the final command return an error after writing output.
- sea migrate up launches cargo run against the migration manifest; the template calls run_cli, which constructs the connection and dispatches up. MigratorTrait::up creates SchemaManager, exec_up installs tracking and selects pending migrations, and exec_up_with runs each migration and records its version, optionally within a transaction.
- Nested ActiveModelEx::save calls generated action(Save), begins a transaction, performs prerequisite relation actions, persists the parent when changed or genuinely new, performs subsequent relation actions, and commits; unchanged existing parents skip the scalar write.
- RBAC use proceeds through load_rbac → load_rbac_from → RbacEngine::load_from → replace, then restricted_for(user); structured execution calls user_can_run and audits every request, while raw SQL calls return RbacError without forwarding.
- The synchronous Parquet example converts generated ActiveModels with to_arrow, writes ArrowWriter output, reads Parquet RecordBatches, restores models with from_arrow, compares rows, recreates a SQLite target, applies the entity schema, and executes insert_many.
Testing signals
Tests and CI were inspected, not executed; their existence does not establish passing results. The sampled suites combine Rust unit tests, async adapters, mock SQL assertions, live database integration, generation fixtures, and conversion tests. Feature gates and conditional CI jobs materially affect coverage, and this investigation does not establish exhaustive coverage or security validation.
- Integration tests using #[sea_orm_macros::test] become ordinary Rust tests gated on SQLx drivers in async mode or rusqlite in sync mode; the async wrapper executes through runtime-specific block_on macros.
- MockDatabase queues execution/query results and errors and records statements; sampled smol_potat cursor tests assert result reversal and exact PostgreSQL SQL/values, which verifies construction rather than a live database executing that SQL.
- The SQLite basic integration flow checks initial save, update through save, count, primary-key lookup, deletion, and final absence; common integration setup creates and drops named MySQL/PostgreSQL databases.
- Codegen writer tests compare parsed generated token streams against checked-in expanded fixtures, including schema-qualified variants; root rustfmt configuration excludes codegen tests_cfg because formatting changes test expectations.
- CLI generation tests cover URL database-name extraction and panic expectations for malformed or incomplete URLs; the inspected tests do not establish end-to-end schema discovery, output recovery, or identifier-escaping correctness.
- Migration tests use #[tokio::test] and DATABASE_URL, exercise multiple migrators/schema names and transaction tests, and assert that read-only status APIs leave a missing tracking table absent before and after queries.
- RBAC integration tests configure role inheritance and verify permitted versus denied inserts on restricted connections and transactions; Arrow unit tests assert primitive values and nullable cells restored from RecordBatch.
- Inspected CI defines workspace, RBAC, registry, CLI, SQLite, sync-rusqlite, and PostgreSQL test commands, plus compile-only combinations and example jobs; commit keywords select partial backend runs, and --no-run stages are compilation rather than test execution. None of these jobs was executed or its outcome retrieved during this investigation.
Risk map
The following risks are source observations or explicitly labeled hypotheses, not confirmed vulnerabilities. Sensitive boundaries include database configuration, SQL construction, authorization snapshots, filesystem generation, and subprocess execution. Migration and synchronous-generation utilities have broad side effects that deserve particular attention during future authorized work.
- Hypothesis: caller-controlled PostgreSQL schema strings could cross a SQL trust boundary because CLI sqlx_connect interpolates schema into SET search_path and marks the resulting string AssertSqlSafe; the runtime connector also constructs search-path SQL directly. Exploitability was not tested. (inferred)
- Connection diagnostics can contain sensitive configuration: Database::connect includes the supplied URL in parsing/driver errors, ConnectOptions derives Debug with URL and SQLCipher fields, and metric callbacks receive statements with parameters.
- Entity generation writes directly into a caller-selected output directory before rustfmt and final fallback checks; preservation excludes mod.rs, lib.rs, prelude.rs, and active-enum files, while parse fallback preserves old source as comments rather than active code.
- Migration tooling launches cargo with a selected manifest and inherited/overridden database environment; update_migrator rewrites Rust through timestamp-module and vec! regexes, truncates the destination after backing it up, and removes the backup after writing.
- Hypothesis: application code retaining ordinary DatabaseConnection handles can bypass optional RBAC wrappers, and policy changes require explicit engine replacement to affect the loaded snapshot. RestrictedConnection checks structured statements, whereas the underlying connection remains available. (inferred)
- Migration atomicity is conditional: defaults wrap PostgreSQL migrations but not MySQL/SQLite, with per-migration overrides; the nontransactional branch executes migration.up before inserting its tracking record. fresh invokes database-wide table/type dropping within its discovered scope.
- ProxyDatabaseTrait defaults transaction hooks to no-ops and ping to success; the GlueSQL example supports limited value/payload variants, contains panic/todo branches, and returns fixed last_insert_id and rows_affected values.
- make-sync.sh deletes and recopies the synchronous source/test trees, then applies broad textual transformations to async syntax, trait bounds, streams, and locks; its blast radius includes synchronized tests and examples, so isolated mirror edits can be overwritten.
Extension points
The main extension mechanisms are Rust traits, derive attributes, callback factories, and entity registration. Tooling adds command enums and generation backends, while examples expose framework-specific route tables. Extensions should preserve the common connection boundary and account for optional features and synchronous parity.
- ConnectionTrait is the shared execution interface accepted by ORM methods, allowing pool, transaction, mock, and custom implementations to support the same mutation and select APIs; StreamTrait adds a separate associated stream contract.
- ProxyDatabaseTrait is the custom backend adapter contract for query and execute results, with overridable transaction/ping hooks; Database::connect_proxy accepts an Arc<Box<dyn ProxyDatabaseTrait>> and a dialect.
- ActiveModelBehavior provides new, before_save, after_save, before_delete, and after_delete hooks; save hooks receive the insert/update distinction, and before hooks can abort by returning DbErr.
- EntityRegistry uses inventory::collect and a re-exported submit macro; each registration carries module_path and a schema-info factory, and build_schema selects registrations through normalized module-prefix matching.
- MigrationTrait supplies up/down and transaction-policy hooks; MigratorTrait supplies the ordered boxed migration list and overridable tracking-table name, while run_cli_with_connection accepts a connection factory.
- Codegen changes plug into EntityFormat, EntityWriterContext, and format-specific writer branches; context supports custom model/enum/column derives, attributes, serialization policy, date/time representation, and schema names.
- ConnectOptions stores backend option and pool-option callbacks plus an after-connect callback; PostgreSQL applies these during pool construction, and query metrics expose elapsed time, statement, and failure state to an optional callback.
- Dynamic Entity::from_entity exports typed entity metadata into runtime columns and ModelType fields; application integrations add endpoints through Actix init registrations, the PostRpc method trait, or Rocket merged endpoint/documentation mounts.
Contributor handoff
Begin with manifests, the library exports, and the exact feature-specific implementation involved in the task. Tests and CI were inspected, not executed, and this bounded brief supplies no passing validation result. Future authorized edits should respect generation boundaries, isolated database targets, and distinct contracts for schema sync, nested persistence, and authorization.
- Read Cargo.toml, src/lib.rs, CLAUDE.md, and CONTRIBUTING.md first: the repository explicitly documents SeaORM 2.0 model relations, typed COLUMN access, builder-style ActiveModels, and changed raw execution APIs.
- Choose package-specific checks: CONTRIBUTING documents cargo test --workspace and a separate CLI manifest command; CI additionally defines separate migration checks. These commands were inspected, not executed, and require separate execution authorization in a read-only investigation.
- For live database validation, select the appropriate driver/runtime features and an isolated target: documented integration commands depend on DATABASE_URL, and common setup issues DROP DATABASE and CREATE DATABASE statements.
- Before modifying persistence, read ActiveModelTrait together with insert execution and generated ActiveModelEx actions; preserve primary-key branching, lifecycle hooks, RETURNING fallback lookup, and relation-before/after transaction ordering.
- Treat generation fixtures as behavior specifications: writer tests compare expected token streams, rustfmt excludes tests_cfg, and experimental regeneration preservation has exclusions and fallback writes that must be reviewed before use.
- Check synchronous parity for shared ORM changes, but do not casually run make-sync.sh: the script deletes mirror trees and transforms async source/tests, and sea-orm-sync has a separate workspace and dependency boundary.
- Preserve capability limits: schema sync adds missing objects and leaves mismatches untouched, nested relation mutation is experimental, Replace can delete omitted children, and restricted connections reject raw SQL while auditing structured statements.
- Review example behavior before reusing it in applications: Actix handlers unwrap query parsing and expect database success, and Rocket run_migrations discards migration errors. Example compilation or generic CI definitions do not establish request-path correctness or successful startup migrations.
Languages
| Language | Files | Share of source |
|---|---|---|
| Rust | 1228 | 90% |
| TOML | 103 | 8% |
| Markdown | 94 | docs/config |
| YAML | 23 | docs/config |
| CSS | 17 | docs/config |
| Shell | 10 | 1% |
| JSON | 8 | docs/config |
| TypeScript | 7 | 1% |
Start reading here
Probable source entrypoint: Cargo.toml — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (148 files)
Repository structure
| Top-level path | Files |
|---|---|
examples/ | 526 |
sea-orm-sync/ | 318 |
sea-orm-codegen/ | 202 |
tests/ | 157 |
src/ | 148 |
issues/ | 77 |
sea-orm-macros/ | 43 |
sea-orm-migration/ | 31 |
Docs found: CLAUDE.mdCONTRIBUTING.mdREADME.md
Package
Cargo.toml — sea_orm v2.0.4, 40 declared dependencies.
], optionalasync-streamasync-traitbigdecimalchronoderive-wherederive_moredotenvfutures-utilinventoryipnetworkitertools
CI & config entrypoints
README.md— Repository readmeCLAUDE.md— Claude instructionssrc/lib.rs— Rust library entrypointsea-orm-sync/examples/quickstart/src/main.rs— Rust binary entrypointsea-orm-sync/examples/pi_spigot/src/main.rs— Rust binary entrypointsea-orm-sync/examples/parquet_example/src/main.rs— Rust binary entrypointsea-orm-cli/template/migration/src/main.rs— Rust binary entrypointexamples/actix_example/migration/src/main.rs— Rust binary entrypointexamples/actix_example/src/main.rs— Rust binary entrypointexamples/proxy_gluesql_example/src/main.rs— Rust binary entrypoint
Structural health signals
Average score 5.7/10 across 96 analyzed source files; 81 hotspots surfaced.
sea-orm-sync/src/driver/sqlx_postgres.rs— 938 lines, score 4 (Large file, Deep nesting)sea-orm-cli/src/commands/generate.rs— 890 lines, score 4.5 (Large file, Deep nesting)sea-orm-sync/src/query/helper.rs— 922 lines, score 4.5 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
db40a19a7bde2026-09-27 — Upgrade arrow to 60 (sea-orm-arrow 2.0.0-rc.5)436f6f6882b42026-09-27 — Type the metadata map explicitly in DeriveArrowSchema39085031f0bf2026-09-27 — update examples2420757038b22026-09-27 — 2.0.448e1809067bd2026-09-27 — Fix 2.0.4 changelog index after #3200 revert