Skip to content

Prisma Importer

Manech Dubreil edited this page Oct 16, 2026 · 1 revision

Prisma Importer

The Prisma importer (crates/core/src/importer/prisma/, exposing PrismaImporter) turns a .prisma file into a pivot::Schema. It implements the shared Importer trait.

Pipeline

Flowchart: the Prisma importer pipeline in three phases — PEG parse via grammar.pest into pest Pairs, then parser.rs builds the raw AST, then resolver.rs and mappers.rs resolve it into a pivot::Schema

File Role
grammar.pest The PEG grammar for Prisma Schema Language — defines schema, block (datasource/generator/model/enum/view/type alias), field_decl, attribute syntax (@id, @@map(...)), and value literals.
ast.rs The raw, untyped intermediate structs the parser produces: RawAttr { path, args }, RawArg::{Named, Positional}, RawValue::{Str, Int, Float, Ident, Array, Call}, RawField, RawModel, RawTypeAlias. These are pub(super) — never exposed outside the prisma module.
parser.rs Walks pest::Pairs<Rule> and builds the ast.rs structures. Exposes PrismaParser (the pest-generated parser struct), extract_database_kind, parse_model_block, parse_type_alias, parse_attribute, parse_enum_block. This stage only understands syntax — it has no notion yet of what String or @db.VarChar(255) mean semantically.
resolver.rs Turns the raw AST into pivot types. resolve_models builds Tables (columns, primary keys, indexes, foreign keys, relations, inheritance, behaviors) from RawModels, cross-referencing model and enum names to distinguish scalar fields from relation fields and to detect implicit many-to-many relations. resolve_views does the analogous work for view blocks. Unrecognized attributes are routed into OrmMetadata.unknown_features here (via a shared push_unknown_attrs helper) rather than dropped.
mappers.rs Small, focused translation functions used by resolver.rs: map_scalar_type (Prisma type name → ScalarType), map_db_hint (@db.* attribute → DbHint), map_default_value (@default(...) → DefaultValue), map_index_type, extract_index_columns, parse_referential_action, plus raw_value_to_json/raw_args_to_json used to serialize unrecognized attribute arguments into UnknownFeature.value.
behaviors.rs detect_behaviors — recognizes the conventional createdAt / deletedAt / version columns and turns them into Behaviors. See Behavior detection.
tests.rs Unit tests for the importer (see Testing).

The entry point tying all three phases together is parse_schema in mod.rs:

pub fn parse_schema(input: &str) -> Result<Schema, ImportError> {
    let pairs = PrismaParser::parse(Rule::schema, input)?;   // Phase 1
    // ...walk top-level blocks, collecting RawModel/RawView/Enum/RawTypeAlias...   Phase 2
    let tables = resolver::resolve_models(raw_models, &model_names, &enum_names, &type_aliases)?; // Phase 3
    let views  = resolver::resolve_views(raw_views, &enum_names, &type_aliases)?;
    // ...assemble into a Schema
}

PrismaImporter::import (the Importer trait impl) just delegates to parse_schema.

Behavior detection

Prisma has a dedicated attribute for only one behavior (@updatedAt). The other three have no Prisma syntax, so the importer infers them from conventional column patterns:

Prisma field Pivot behavior Rule
x DateTime @updatedAt Behavior::UpdatedAt Any field carrying @updatedAt (directly or through a type alias), whatever its name.
createdAt DateTime @default(now()) Behavior::CreatedAt Name is exactly createdAt, type DateTime, required, not a list, default is now().
deletedAt DateTime? Behavior::SoftDelete Name is exactly deletedAt, type DateTime, optional, not a list.
version Int Behavior::Versioning Name is exactly version, type Int, required, not a list.

The type check uses the type behind a type alias, so createdAt Timestamp @default(now()) with type Timestamp = DateTime is still detected. The column stays in Table.columns as usual — the behavior only adds the ORM-level meaning on top.

Known limitations

  • Versioning is a name-based heuristic. Prisma has no notion of optimistic locking, so any required version Int field is detected as Behavior::Versioning, even if it is really a business value (an API version, a document revision number shown to users...). If that is not what you want, rename the field (e.g. apiVersion) and keep the database name with @map("version").

  • Only exact camelCase field names are matched. created_at, CreatedAt, deleted_at, removedAt, rowVersion... are not detected. This follows Prisma's naming convention: the field name is camelCase and the database name is set with @map. Detection looks at the field name, not the @map name, so this works:

    createdAt DateTime  @default(now()) @map("created_at")   // detected
    deletedAt DateTime?                 @map("deleted_at")   // detected

    whereas a field literally named created_at is not.

  • A wrong shape silently disables detection. createdAt DateTime without @default(now()), a required deletedAt DateTime, or a version BigInt are imported as plain columns with no behavior and no warning.

  • No configuration yet. Custom column names cannot be declared as behaviors; this would require a config option on the importer.

Design choices worth knowing

  • Type aliases are inlined, not preserved as a separate concept. Prisma's type Foo = Int @id @default(autoincrement()) has no equivalent pivot type — resolver.rs expands each use of Foo back to its underlying ScalarType + attributes at resolve time, so the alias is invisible to the rest of the pipeline.
  • Unrecognized attributes never cause a hard error. Any @ or @@ attribute the resolver doesn't have an explicit case for is captured as an OrmMetadata.unknown_features entry (namespaced "prisma.<name>") instead of being dropped or aborting the import — see Pivot IR. This is what lets the importer stay lossless against future Prisma releases without being updated for every new attribute immediately.
  • Views are parsed but their SQL definition is not available. Prisma Schema Language's view blocks only declare the column list, not the underlying SELECT — so View.definition is always an empty string for Prisma-sourced views.
  • ImportError::Parse vs. ImportError::Schema. Parse fires automatically via a From<pest::error::Error<Rule>> impl whenever the input isn't valid Prisma Schema Language syntax at all (the PEG parse itself fails). Schema covers content that parses syntactically but is locally invalid — e.g. a numeric literal token that doesn't actually parse as an integer or float. It is not used for semantic issues like a field referencing a model or type that doesn't exist: those degrade gracefully to ScalarType::Unsupported in resolver.rs instead of raising an error.

Clone this wiki locally