Skip to content

feat: db pull implementation#268

Merged
ymc9 merged 83 commits intozenstackhq:devfrom
svetch:main
Feb 8, 2026
Merged

feat: db pull implementation#268
ymc9 merged 83 commits intozenstackhq:devfrom
svetch:main

Conversation

@svetch
Copy link
Contributor

@svetch svetch commented Sep 23, 2025

Summary

This PR introduces db pull support to zenstack_v3, similar to Prisma’s db pull.
The command enables introspection of the database schema and generates corresponding models within the project.

Current Status

  • Initial implementation of db pull command
  • Relation resolving logic
  • Implement diff update on db pull
    • Multi-schema file update(handle schema imports and update model in their schema file)
    • Add/Remove fields on update
    • Add/Remove Models
    • Update field
    • Update model attributes
  • Make all generation option configurable from cli
    • Option to always add @map for fields/models
    • Name casing strategy
    • quote, indent
  • improve logging
  • Handle the database type mapping per database provider(@db.\<Raw databse type\>, ie: @db.Real)
  • Test on multiple database provider with all zenstack features

Notes

  • I appreciate any feedback, suggestions, or requests for changes 🙏

Summary by CodeRabbit

  • New Features

    • CLI: Added db pull to introspect MySQL/Postgres/SQLite and generate ZenStack models with output, casing, mapping, quoting, and indent options.
  • New API

    • Fluent factory API for programmatic construction of language ASTs and model generation.
  • Bug Fixes

    • Safer schema loading with optional import merging and optional access to parsing services.
  • Tests

    • Extensive provider-aware pull/push tests; many tests converted to async and test utilities improved.
  • Chores

    • Added DB providers, casing utilities, packaging exports, and codegen/formatting improvements.

@coderabbitai
Copy link

coderabbitai bot commented Sep 23, 2025

📝 Walkthrough

Walkthrough

Adds a DB "pull" workflow with introspection providers (MySQL, PostgreSQL, SQLite), provider registry, sync helpers to convert introspected schemas to ZModel, a new CLI db pull command and tests; introduces a fluent AST factory library and exports, document load option to return services, codegen updates, validator tweak, package/tsup updates, and multiple test/tooling adjustments.

Changes

Cohort / File(s) Summary
CLI package metadata
packages/cli/package.json
Added workspace dependency @zenstackhq/schema.
CLI entry & DB action
packages/cli/src/index.ts, packages/cli/src/actions/db.ts, packages/cli/src/actions/action-utils.ts
Added db pull subcommand and options; introduced runPull and PullOptions; updated run/runPush typing; extended loadSchemaDocument overloads to accept mergeImports and optionally return services.
Pull core & sync helpers
packages/cli/src/actions/pull/index.ts, packages/cli/src/actions/pull/utils.ts, packages/cli/src/actions/pull/casing.ts
Added syncEnums, syncTable, syncRelation, exported Relation type; casing utilities; datasource/attribute resolution, DB-name and default-normalization helpers; change-tracking and model sync logic.
Providers: contracts & registry
packages/cli/src/actions/pull/provider/provider.ts, packages/cli/src/actions/pull/provider/index.ts
Introduced IntrospectionProvider API, Introspected* and DatabaseFeature/Cascade types, and a providers registry mapping mysql/postgresql/sqlite.
Providers: implementations
packages/cli/src/actions/pull/provider/mysql.ts, .../postgresql.ts, .../sqlite.ts
Added MySQL, PostgreSQL, and SQLite introspection providers implementing type mapping, default parsing, field attributes, enums/indexes/foreign-keys introspection, and returning IntrospectedSchema. Uses dynamic imports and provider-specific SQL/PRAGMA logic.
Pull orchestration
packages/cli/src/actions/pull/..., packages/cli/src/actions/db.ts
Orchestration for DB introspection, syncing enums/tables/relations, computing change sets, generating ZModel code, and writing outputs; integrated into CLI flow via runPull.
Tests & test utilities
packages/cli/test/utils.ts, packages/cli/test/*, packages/cli/test/db/*
Made tests provider-aware and async; added getDefaultPrelude, deterministic DB naming and provider URL helpers; converted many tests to async; added db push and extensive db pull tests; added casing tests and other test updates.
Language: document API
packages/language/src/document.ts
loadDocument gained mergeImports parameter and now returns services in the success payload when requested.
Language: AST factory library
packages/language/src/factory/*, packages/language/src/factory/index.ts, packages/language/package.json, packages/language/tsup.config.ts
Added fluent AST factory modules (ast-factory, primitives, expression, declaration, attribute) with builders, re-exported via factory index, added package export ./factory and tsup entry for factory.
Language: codegen & validators
packages/language/src/zmodel-code-generator.ts, packages/language/src/validators/datamodel-validator.ts, packages/language/res/stdlib.zmodel
Generator: ModelImport support, configurable quoting, import rendering and formatting tweaks. Validator: respect @@ignore to skip unique-field requirement. Stdlib: comment rewrapping.
Language packaging & build
packages/language/package.json, packages/language/tsup.config.ts
Exported ./factory in package exports and added factory entry to tsup config.
Factory index barrel
packages/language/src/factory/index.ts
Re-exported factory modules to expose the new factory API surface.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 I hopped through tables, enums, and names,
I stitched relation threads and chased casing games,
Factories sprout nodes with a cheerful thump,
I pulled schemas home and saved each dump,
Tests nibble carrots — commit, hop, and jump!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: db pull implementation' clearly and concisely describes the main feature added in this pull request—a database pull command that introspects database schemas and generates ZenStack models.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@svetch svetch force-pushed the main branch 2 times, most recently from 85bbb6e to 08006cf Compare October 22, 2025 20:13
@ymc9
Copy link
Member

ymc9 commented Oct 24, 2025

Just a heads up, I'm renaming "@zenstackhq/runtime" package to "@zenstackhq/orm".

svetch added 13 commits February 6, 2026 15:02
Prevents foreign key indexes created automatically by MySQL from appearing in the introspected schema. This ensures the output reflects manually defined indexes and avoids redundancy in schema definitions.
Enhances the test utility helpers to allow passing extra datasource properties, such as multi-schema configurations for PostgreSQL.

Refactors existing database pull tests to use these extra properties, ensuring the generated ZModel schema correctly reflects multi-schema environments while simplifying assertions.
Enhances database introspection to correctly handle composite foreign keys by mapping columns by position rather than name alone.

Improves MySQL introspection by checking statistics tables for single-column unique indexes, ensuring accurate model generation even when column keys are ambiguous.

Ensures MySQL synthetic enum names respect requested model casing to prevent unnecessary schema mapping.

Adds comprehensive tests for composite relations and database-specific uniqueness detection.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/cli/src/actions/db.ts`:
- Around line 626-663: The log in the docs loop uses uri.path but the file is
written to uri.fsPath, causing odd output on Windows; update the console message
inside the else branch (the for...of over docs where
parseResult.value/documentModel is processed and zmodelSchema is written using
fs.writeFileSync(uri.fsPath, ...)) to log the actual OS path (use uri.fsPath or
a resolved path) instead of uri.path so logged paths match the written paths.

In `@packages/cli/src/actions/pull/utils.ts`:
- Around line 75-84: The code casts the result of getStringLiteral(...) for the
datasource provider to DataSourceProviderType without checking for undefined,
which can mask missing provider fields; update the logic around
datasource.fields.find((f) => f.name === 'provider')?.value and getStringLiteral
to explicitly validate the provider (in the function building the returned
object where name/provider/url/defaultSchema/schemas/allSchemas are assembled),
and if getStringLiteral returns undefined either throw a clear error indicating
the datasource is missing a provider or supply a safe default; ensure the thrown
error message names the datasource (datasource.name) and the provider field so
failures are clear and traceable.

In `@packages/cli/test/db/pull.test.ts`:
- Around line 736-768: The test skips when getTestDbProvider() isn't mysql or
postgresql but calls createProject without specifying the provider and calls
getDefaultPrelude() with no args, which can produce a mismatched datasource;
modify the test to pass the active provider into createProject (e.g.,
createProject(..., { provider })) and pass the provider into getDefaultPrelude
(e.g., getDefaultPrelude(provider)) so the created project and the zeroed
prelude use the same SQL provider; update the calls to createProject and
getDefaultPrelude referenced in this test to include the provider variable.
🧹 Nitpick comments (14)
packages/cli/src/actions/pull/provider/sqlite.ts (1)

370-379: Convention-based @updatedAt heuristic is reasonable but worth documenting.

This silently adds @updatedAt to any DateTime field named updatedAt/updated_at, which may surprise users who happen to use those names for non-update-tracking columns. Consider adding a log message (like the existing casing mappings do) so users are aware the attribute was inferred.

packages/cli/src/actions/db.ts (2)

82-84: runPull could benefit from early spinner cleanup on non-introspection errors.

The spinner is created at line 83 and started at line 111. If an error occurs between lines 85–110 (schema loading, provider resolution), spinner.fail() in the catch block (line 667) will display a failure message even though the spinner was never started, resulting in a confusing "Pull failed" message with no preceding "Introspecting database..." line.

Consider moving spinner.start() earlier or guarding spinner.fail() with a check on whether the spinner is actually spinning.


403-438: @default attribute diffing via serialization is pragmatic but fragile.

The serializeArgs helper at line 408 handles known AST node types but falls back to arg.value?.$type ?? 'unknown' for unrecognized types (line 422). If two different default expressions both serialize to 'unknown', the diff won't detect a change. This is a low-risk edge case for now, but consider logging when the fallback is hit so it doesn't silently swallow mismatches in the future.

packages/cli/test/utils.ts (2)

9-21: TEST_PG_CONFIG and TEST_MYSQL_CONFIG are duplicated from @zenstackhq/testtools.

These constants are identical to those in packages/testtools/src/client.ts (which is already a devDependency). Importing them would avoid drift between the two copies.

#!/bin/bash
# Verify the testtools exports match
rg -n "TEST_PG_CONFIG|TEST_MYSQL_CONFIG" --type ts -g '*/testtools/*'

23-43: SQLite test DB name is static — parallel test runs would collide.

getTestDbName('sqlite') always returns './test.db' (line 25). If multiple SQLite test suites run in parallel (e.g., via Vitest's default worker pool), they'd share the same file in each workDir. Since createProject creates a fresh workDir per test, this is likely safe in practice — just noting it for awareness.

packages/cli/src/actions/pull/provider/mysql.ts (2)

143-155: Inconsistent indentation in column sorting/mapping block.

Lines 143–155 use 2-space indentation for the const sortedColumns block while the surrounding code uses 4-space (or 8-space within the method body). This looks like an accidental formatting inconsistency.


263-294: getFieldAttributes handles Unsupported fieldType unsafely.

this.getDefaultDatabaseType(fieldType as BuiltinType) on line 276 casts fieldType which could be 'Unsupported'. While getDefaultDatabaseType returns undefined for unrecognized types (due to the exhaustive switch with no default), the cast itself is misleading. An early return for Unsupported would be cleaner and safer.

Suggested improvement
     getFieldAttributes({ fieldName, fieldType, datatype, length, precision, services }) {
         const factories: DataFieldAttributeFactory[] = [];
+
+        if (fieldType === 'Unsupported') {
+            return factories;
+        }

         // Add `@updatedAt` for DateTime fields named updatedAt or updated_at
packages/language/src/factory/attribute.ts (1)

17-79: DataFieldAttributeFactory and DataModelAttributeFactory are nearly identical — consider extracting a shared base.

Both classes have identical setDecl and addArg implementations. Extracting a parameterized base class (e.g., BaseAttributeFactory<T extends DataFieldAttribute | DataModelAttribute>) would eliminate the duplication and reduce the maintenance surface.

packages/cli/test/db/pull.test.ts (1)

296-307: Comprehensive "all features" test with informative TODO.

The schema covers a wide variety of features (enums, relations, mappings, indexes, defaults, multiple types). The TODO on line 302 about MySQL enum handling is a known limitation — good to track.

Would you like me to open an issue to track the MySQL enum detection improvement mentioned in the TODO?

packages/cli/src/actions/pull/index.ts (2)

397-401: tableMapAttribute is unused in syncRelation.

The variable on line 401 is fetched but never referenced in the function body. The null-check on line 405 also validates it unnecessarily.

Suggested fix
     const idAttribute = getAttributeRef('@id', services);
     const uniqueAttribute = getAttributeRef('@unique', services);
     const relationAttribute = getAttributeRef('@relation', services);
     const fieldMapAttribute = getAttributeRef('@map', services);
-    const tableMapAttribute = getAttributeRef('@@map', services);
 
-    const includeRelationName = selfRelation || similarRelations > 0;
-
-    if (!idAttribute || !uniqueAttribute || !relationAttribute || !fieldMapAttribute || !tableMapAttribute) {
+    if (!idAttribute || !uniqueAttribute || !relationAttribute || !fieldMapAttribute) {
         throw new CliError('Cannot find required attributes in the model.');
     }

440-463: Complex naming logic is difficult to follow in single expressions.

Lines 446 and 454–458 pack multiple conditional naming rules into very long ternary/template expressions. Consider extracting helper functions (e.g., deriveSourceFieldName) to improve readability and make the naming heuristics easier to reason about and test independently.

packages/cli/src/actions/pull/provider/provider.ts (1)

7-48: Optional: consider extracting inline column/index types for reuse.

The columns and indexes inline types within IntrospectedTable are quite large. Extracting them as named types (e.g., IntrospectedColumn, IntrospectedIndex) would improve readability and enable reuse in provider implementations and tests.

packages/cli/src/actions/pull/utils.ts (2)

138-146: Minor duplication with getDbName's schema extraction logic.

Both getDbSchemaName and the schema extraction block in getDbName (lines 90–97) perform the same @@schema lookup with a 'public' default. Consider extracting a shared helper to avoid divergence if the default or lookup logic changes.


181-189: The decimal branch (lines 185–186) is identical to the fallback (line 188).

Both return ab.NumberLiteral.setValue(val) unchanged. The explicit decimal match is arguably intentional for readability/symmetry with normalizeDecimalDefault, so this is just a nit.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/cli/src/actions/pull/provider/sqlite.ts`:
- Around line 237-254: The current extraction only captures the first column
from the FOREIGN KEY(...) clause; update the logic around fkRegex /
fkConstraintNames in sqlite.ts to capture the full parenthesized list (e.g.,
change the regex to capture the whole group inside FOREIGN KEY\s*\(([^)]+)\) or
add a second regex) and then split that captured group on commas, normalizing
quotes/backticks and whitespace, and for each column add
fkConstraintNames.set(columnName, constraintName); alternatively you can map
constraint names by the FK identifier from PRAGMA foreign_key_list (use the FK
id to associate all seq rows) — target the fkRegex / fkConstraintNames handling
and t.definition parsing to implement one of these fixes.
- Around line 97-113: In the default branch of the SQLite type mapping (the
block that inspects variable t and returns { type: ..., isArray }), handle empty
or null declarations by treating an empty t as BLOB affinity: if t is falsy or
an empty string, return { type: 'Bytes', isArray } before the other includes()
checks; also ensure t is normalized (lowercased) earlier where t is derived so
the includes() checks remain correct. This will ensure columns declared without
a type (e.g., CREATE TABLE t(x)) map to Bytes instead of Unsupported.
🧹 Nitpick comments (4)
packages/cli/src/actions/pull/provider/sqlite.ts (3)

8-21: isSupportedFeature returns false for all features, including the default case.

This is a safe, conservative approach for SQLite. However, if new features are added to the provider interface in the future that SQLite does support, this default will silently mark them as unsupported. Consider whether the default branch should return true to opt in to new features by default (matching the typical pattern where only known-unsupported features are excluded).


268-277: as any casts on fk.on_update and fk.on_delete bypass type safety.

PRAGMA foreign_key_list returns string values like 'CASCADE', 'SET NULL', 'NO ACTION', etc. The Cascade type in IntrospectedTable likely expects specific string literals. Using as any silently allows mismatches. Consider mapping these to the expected Cascade union type or at least validating the values.


370-379: Heuristic @updatedAt inference is reasonable but worth documenting the assumption.

The convention of inferring @updatedAt from field names like updated_at / updatedAt is pragmatic for db pull, but users with differently-purposed fields matching these names could get unexpected behavior. A small inline note or doc comment about this heuristic would help future maintainers.

packages/cli/src/actions/pull/utils.ts (1)

22-29: getAttribute casts model.$document.references without type narrowing.

Line 25 casts model.$document.references to Reference<AbstractDeclaration>[]. If the Langium references property has a different runtime shape, this could silently produce incorrect results. Consider adding a guard or documenting the assumption.

…ite FKs

Ensures columns with no declared type are correctly mapped to Bytes following SQLite affinity rules, preventing them from being marked as Unsupported.

Updates the DDL parser to correctly identify and map constraint names for composite foreign keys. This ensures that multi-column relations are properly restored during the pull process.

Adds regression tests for both untyped columns and composite foreign key restoration.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/cli/src/actions/pull/provider/sqlite.ts`:
- Around line 292-326: The computed flag only checks for hidden === 2 (virtual
generated columns) so STORED generated columns (hidden === 3) are treated as
regular fields; update the logic in the columnsInfo loop (where columns are
built and computed is set) to mark computed when hidden === 2 OR hidden === 3
(i.e., computed: hidden === 2 || hidden === 3) so both virtual and stored
generated columns are flagged as computed and excluded from writable schema
operations.
🧹 Nitpick comments (1)
packages/cli/test/db/pull.test.ts (1)

810-842: Consider testing enum restoration independently per provider.

This test runs for both MySQL and PostgreSQL but the comment at line 301–303 in the comprehensive test notes that MySQL enum handling is different (overrides vs. detects). If the enum restoration behavior diverges between providers, a single shared test with toEqual could mask provider-specific regressions. Consider splitting or adding provider-specific assertions if MySQL enum handling evolves.

Improves database introspection by identifying generated columns in MySQL, PostgreSQL, and SQLite. These columns are now pulled as `Unsupported` types containing their full DDL definition, preventing issues where read-only database fields were incorrectly treated as writable application-level fields.

Includes normalization for expression formatting and a fix for string literal escaping in the code generator to ensure stable schema output.

Relates to ZModel introspection consistency.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/cli/src/actions/pull/index.ts`:
- Around line 466-470: The collision fallback currently uses
relation.references.columns[0] which can be null per the Relation type, so
update the logic around the block that sets sourceFieldName (the check using
sourceModel.fields.find and the assignment to
`${sourceFieldName}To${lowerCaseFirst(targetModel.name)}_${relation.references.columns[0]}`)
to guard against a null column: either assert non-null with a comment (e.g.,
ensure relation.references.columns[0]!) if you know it cannot be null here, or
compute a safe fallback string (like using relation.references.columns[0] ??
'ref') before interpolating so the generated name never contains "null". Ensure
the change is applied where sourceFieldName is reassigned.

In `@packages/cli/src/actions/pull/provider/postgresql.ts`:
- Around line 225-229: The enum default extraction currently strips all
apostrophes from val which corrupts escaped quotes; in the enum-handling block
(where datatype === 'enum' and enumDef is found, affecting variable enumValue)
change the logic to remove only the surrounding single quotes (if present)
rather than globally replacing all apostrophes, then split on '::' and trim; use
a regex or string-slice approach that captures the inner value between the outer
quotes so escaped apostrophes remain intact.
- Around line 334-339: The conditional and argument building in
DataFieldAttributeFactory currently use truthy checks and logical OR which treat
0 as falsy; in the block around dbAttrFactory (DataFieldAttributeFactory,
addArg, NumberLiteral) replace the truthy checks and the length! || precision!
usage with nullish-coalescing semantics so a valid 0 is preserved (e.g., check
for (length ?? precision) being defined instead of (length || precision), and
pass the selected value using length ?? precision when calling
NumberLiteral.setValue).
🧹 Nitpick comments (3)
packages/cli/src/actions/pull/provider/postgresql.ts (1)

318-318: Unsafe fieldType as BuiltinType cast when fieldType is 'Unsupported'.

getDefaultDatabaseType has no Unsupported case and returns undefined, so the subsequent defaultDatabaseType && check prevents runtime errors. But the cast is a type-safety gap. Consider guarding explicitly.

Proposed fix
+        if (fieldType === 'Unsupported') {
+            return factories;
+        }
         const defaultDatabaseType = this.getDefaultDatabaseType(fieldType as BuiltinType);

Based on learnings, production code in this repo should avoid type-safety workarounds; use explicit type annotations or guards instead.

packages/cli/src/actions/pull/provider/mysql.ts (1)

296-296: Same fieldType as BuiltinType cast issue as in the PostgreSQL provider.

When fieldType is 'Unsupported', this cast is incorrect. An early return guard would be safer and consistent.

Proposed fix
+        if (fieldType === 'Unsupported') {
+            return factories;
+        }
         const defaultDatabaseType = this.getDefaultDatabaseType(fieldType as BuiltinType);
packages/cli/test/utils.ts (1)

58-75: getDefaultPrelude duplicates URL construction already in getTestDbUrl.

The switch statement at lines 63–75 rebuilds the same URL that getTestDbUrl produces. This can be simplified.

Proposed refactor
 export function getDefaultPrelude(options?: { provider?: 'sqlite' | 'postgresql' | 'mysql', datasourceFields?: Record<string, string | string[]> }) {
     const provider = (options?.provider || getTestDbProvider()) ?? 'sqlite';
     const dbName = getTestDbName(provider);
-    let dbUrl: string;
-
-    switch (provider) {
-        case 'sqlite':
-            dbUrl = `file:${dbName}`;
-            break;
-        case 'postgresql':
-            dbUrl = `postgres://${TEST_PG_CONFIG.user}:${TEST_PG_CONFIG.password}@${TEST_PG_CONFIG.host}:${TEST_PG_CONFIG.port}/${dbName}`;
-            break;
-        case 'mysql':
-            dbUrl = `mysql://${TEST_MYSQL_CONFIG.user}:${TEST_MYSQL_CONFIG.password}@${TEST_MYSQL_CONFIG.host}:${TEST_MYSQL_CONFIG.port}/${dbName}`;
-            break;
-        default:
-            throw new Error(`Unsupported provider: ${provider}`);
-    }
+    const dbUrl = getTestDbUrl(provider, dbName);

Copy link
Member

@ymc9 ymc9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding some final review comments 😄

Switches from template literal interpolation to parameterized queries in MySQL introspection functions. This improves security by preventing potential SQL injection and ensures better handling of database names containing special characters.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/cli/src/actions/pull/provider/mysql.ts`:
- Around line 298-310: The precision comparison uses the wrong nullish check:
inside the conditional that compares defaultDatabaseType.precision to (length ||
precision) replace the falsy-coalescing `||` with the nullish-coalescing `??` so
that a legitimate 0 length isn't discarded; update the conditional around
dbAttr/defaultDatabaseType (the block that constructs a new
DataFieldAttributeFactory and pushes to factories) to use `(length ??
precision)` consistently (note the later use already uses `length ??
precision`), leaving the DataFieldAttributeFactory and addArg logic unchanged.
🧹 Nitpick comments (2)
packages/cli/src/actions/pull/provider/mysql.ts (2)

286-289: @updatedAt heuristic is name-based and could produce false positives.

Any DateTime column named updated_at or updatedat (case-insensitive) will get @updatedAt applied, even if the column is manually managed and not auto-updated by the application. This is a reasonable convention, but consider documenting this behavior or making it opt-in via a CLI flag so users aren't surprised.


156-174: Inconsistent indentation.

Lines 158–162 use 14-space indentation while surrounding code uses 16 spaces (4-level nesting at 4 spaces each). Minor, but worth aligning for consistency.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/cli/src/actions/pull/provider/postgresql.ts`:
- Line 617: The WHERE clause currently uses a substring regex (!~
'_prisma_migrations') on "cls"."relname" which can wrongly exclude tables
containing that substring; update the condition to match the exact table name by
replacing it with either an equality check ("cls"."relname" !=
'_prisma_migrations') or an anchored regex ("cls"."relname" !~
'^_prisma_migrations$') in the SQL that builds the query (look for the clause
referencing "cls"."relname" and '_prisma_migrations' in postgresql.ts) so only
the exact _prisma_migrations table is excluded.

Copy link
Member

@ymc9 ymc9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making this amazing contribution @svetch ! I'm merging it and will make it part of v3.4.0 as an experimental feature.

@ymc9 ymc9 merged commit e7a3588 into zenstackhq:dev Feb 8, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants