ormbridge 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +19 -18
  2. package/dist/cliRunner.js +4 -4
  3. package/dist/cliRunner.js.map +1 -1
  4. package/dist/config.d.ts +2 -2
  5. package/dist/convert.js +30 -1
  6. package/dist/convert.js.map +1 -1
  7. package/dist/emitters/cockroach.d.ts +33 -0
  8. package/dist/emitters/cockroach.js +81 -0
  9. package/dist/emitters/cockroach.js.map +1 -0
  10. package/dist/emitters/doctrine.js +11 -0
  11. package/dist/emitters/doctrine.js.map +1 -1
  12. package/dist/emitters/drizzle.js +39 -4
  13. package/dist/emitters/drizzle.js.map +1 -1
  14. package/dist/emitters/efcore.js +18 -1
  15. package/dist/emitters/efcore.js.map +1 -1
  16. package/dist/emitters/efcoreColumns.js +23 -2
  17. package/dist/emitters/efcoreColumns.js.map +1 -1
  18. package/dist/emitters/gorm.js +20 -1
  19. package/dist/emitters/gorm.js.map +1 -1
  20. package/dist/emitters/jpa.d.ts +36 -0
  21. package/dist/emitters/jpa.js +262 -0
  22. package/dist/emitters/jpa.js.map +1 -0
  23. package/dist/emitters/jpaColumns.d.ts +27 -0
  24. package/dist/emitters/jpaColumns.js +342 -0
  25. package/dist/emitters/jpaColumns.js.map +1 -0
  26. package/dist/emitters/jpaDefaults.d.ts +24 -0
  27. package/dist/emitters/jpaDefaults.js +223 -0
  28. package/dist/emitters/jpaDefaults.js.map +1 -0
  29. package/dist/emitters/jpaEnums.d.ts +11 -0
  30. package/dist/emitters/jpaEnums.js +134 -0
  31. package/dist/emitters/jpaEnums.js.map +1 -0
  32. package/dist/emitters/jpaFormat.d.ts +16 -0
  33. package/dist/emitters/jpaFormat.js +73 -0
  34. package/dist/emitters/jpaFormat.js.map +1 -0
  35. package/dist/emitters/jpaKeys.d.ts +25 -0
  36. package/dist/emitters/jpaKeys.js +110 -0
  37. package/dist/emitters/jpaKeys.js.map +1 -0
  38. package/dist/emitters/jpaNames.d.ts +26 -0
  39. package/dist/emitters/jpaNames.js +234 -0
  40. package/dist/emitters/jpaNames.js.map +1 -0
  41. package/dist/emitters/jpaRelations.d.ts +41 -0
  42. package/dist/emitters/jpaRelations.js +304 -0
  43. package/dist/emitters/jpaRelations.js.map +1 -0
  44. package/dist/emitters/jpaRender.d.ts +10 -0
  45. package/dist/emitters/jpaRender.js +81 -0
  46. package/dist/emitters/jpaRender.js.map +1 -0
  47. package/dist/emitters/jpaScalars.d.ts +32 -0
  48. package/dist/emitters/jpaScalars.js +201 -0
  49. package/dist/emitters/jpaScalars.js.map +1 -0
  50. package/dist/emitters/jpaTable.d.ts +10 -0
  51. package/dist/emitters/jpaTable.js +109 -0
  52. package/dist/emitters/jpaTable.js.map +1 -0
  53. package/dist/emitters/jpaTypes.d.ts +85 -0
  54. package/dist/emitters/jpaTypes.js +36 -0
  55. package/dist/emitters/jpaTypes.js.map +1 -0
  56. package/dist/emitters/laravel.js +11 -0
  57. package/dist/emitters/laravel.js.map +1 -1
  58. package/dist/emitters/prisma.d.ts +1 -1
  59. package/dist/emitters/prisma.js +47 -5
  60. package/dist/emitters/prisma.js.map +1 -1
  61. package/dist/emitters/sequelize.js +82 -6
  62. package/dist/emitters/sequelize.js.map +1 -1
  63. package/dist/emitters/sqlClickhouse.d.ts +43 -0
  64. package/dist/emitters/sqlClickhouse.js +265 -0
  65. package/dist/emitters/sqlClickhouse.js.map +1 -0
  66. package/dist/emitters/sqlClickhouseIndexes.d.ts +3 -0
  67. package/dist/emitters/sqlClickhouseIndexes.js +98 -0
  68. package/dist/emitters/sqlClickhouseIndexes.js.map +1 -0
  69. package/dist/emitters/sqlClickhouseTypes.d.ts +39 -0
  70. package/dist/emitters/sqlClickhouseTypes.js +360 -0
  71. package/dist/emitters/sqlClickhouseTypes.js.map +1 -0
  72. package/dist/emitters/sqlDdl.d.ts +29 -1
  73. package/dist/emitters/sqlDdl.js +141 -21
  74. package/dist/emitters/sqlDdl.js.map +1 -1
  75. package/dist/emitters/sqlalchemy.d.ts +7 -1
  76. package/dist/emitters/sqlalchemy.js +39 -0
  77. package/dist/emitters/sqlalchemy.js.map +1 -1
  78. package/dist/emitters/typeorm.js +44 -9
  79. package/dist/emitters/typeorm.js.map +1 -1
  80. package/dist/formats.d.ts +5 -2
  81. package/dist/formats.js +36 -1
  82. package/dist/formats.js.map +1 -1
  83. package/dist/index.d.ts +4 -0
  84. package/dist/index.js +2 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/io.js +27 -1
  87. package/dist/io.js.map +1 -1
  88. package/dist/parsers/javaSyntax.d.ts +120 -0
  89. package/dist/parsers/javaSyntax.js +304 -0
  90. package/dist/parsers/javaSyntax.js.map +1 -0
  91. package/dist/parsers/javaValues.d.ts +5 -0
  92. package/dist/parsers/javaValues.js +187 -0
  93. package/dist/parsers/javaValues.js.map +1 -0
  94. package/dist/parsers/jpa.d.ts +17 -0
  95. package/dist/parsers/jpa.js +131 -0
  96. package/dist/parsers/jpa.js.map +1 -0
  97. package/dist/parsers/jpaAnnotations.d.ts +46 -0
  98. package/dist/parsers/jpaAnnotations.js +156 -0
  99. package/dist/parsers/jpaAnnotations.js.map +1 -0
  100. package/dist/parsers/jpaBuild.d.ts +4 -0
  101. package/dist/parsers/jpaBuild.js +297 -0
  102. package/dist/parsers/jpaBuild.js.map +1 -0
  103. package/dist/parsers/jpaClasses.d.ts +27 -0
  104. package/dist/parsers/jpaClasses.js +257 -0
  105. package/dist/parsers/jpaClasses.js.map +1 -0
  106. package/dist/parsers/jpaCollections.d.ts +12 -0
  107. package/dist/parsers/jpaCollections.js +106 -0
  108. package/dist/parsers/jpaCollections.js.map +1 -0
  109. package/dist/parsers/jpaColumns.d.ts +26 -0
  110. package/dist/parsers/jpaColumns.js +361 -0
  111. package/dist/parsers/jpaColumns.js.map +1 -0
  112. package/dist/parsers/jpaEntities.d.ts +9 -0
  113. package/dist/parsers/jpaEntities.js +181 -0
  114. package/dist/parsers/jpaEntities.js.map +1 -0
  115. package/dist/parsers/jpaIndexes.d.ts +12 -0
  116. package/dist/parsers/jpaIndexes.js +87 -0
  117. package/dist/parsers/jpaIndexes.js.map +1 -0
  118. package/dist/parsers/jpaJoins.d.ts +68 -0
  119. package/dist/parsers/jpaJoins.js +131 -0
  120. package/dist/parsers/jpaJoins.js.map +1 -0
  121. package/dist/parsers/jpaModel.d.ts +73 -0
  122. package/dist/parsers/jpaModel.js +2 -0
  123. package/dist/parsers/jpaModel.js.map +1 -0
  124. package/dist/parsers/jpaNotes.d.ts +20 -0
  125. package/dist/parsers/jpaNotes.js +138 -0
  126. package/dist/parsers/jpaNotes.js.map +1 -0
  127. package/dist/parsers/jpaRelations.d.ts +4 -0
  128. package/dist/parsers/jpaRelations.js +242 -0
  129. package/dist/parsers/jpaRelations.js.map +1 -0
  130. package/dist/parsers/jpaTypes.d.ts +23 -0
  131. package/dist/parsers/jpaTypes.js +232 -0
  132. package/dist/parsers/jpaTypes.js.map +1 -0
  133. package/dist/parsers/prisma.js +9 -1
  134. package/dist/parsers/prisma.js.map +1 -1
  135. package/dist/parsers/sqlDdl.js +40 -2
  136. package/dist/parsers/sqlDdl.js.map +1 -1
  137. package/dist/parsers/sqlSyntax.js +15 -1
  138. package/dist/parsers/sqlSyntax.js.map +1 -1
  139. package/dist/parsers/sqlTypes.js +37 -2
  140. package/dist/parsers/sqlTypes.js.map +1 -1
  141. package/dist/parsers/typeorm.js +2 -0
  142. package/dist/parsers/typeorm.js.map +1 -1
  143. package/dist/styles.d.ts +3 -2
  144. package/dist/styles.js +4 -1
  145. package/dist/styles.js.map +1 -1
  146. package/package.json +8 -1
@@ -1,6 +1,8 @@
1
1
  import { findModel } from '../ir.js';
2
2
  import { limitIdentifier, toSnakeCase } from '../naming.js';
3
3
  import { expandManyToMany, normalizeSchema, } from '../transforms.js';
4
+ import { COCKROACH_PREFIX, cockroachMissingType, cockroachSequentialWarning, } from './cockroach.js';
5
+ import { emitClickhouseDdl } from './sqlClickhouse.js';
4
6
  /** The dialect a provider is written in. MongoDB has no SQL; CockroachDB speaks PostgreSQL. */
5
7
  export function sqlDialectOf(provider) {
6
8
  switch (provider) {
@@ -91,6 +93,10 @@ export function quoteIdentifier(dialect, name) {
91
93
  }
92
94
  return quoteAlways(dialect, name);
93
95
  }
96
+ /** The name of the database in messages: CockroachDB shares the PostgreSQL dialect but not its limits. */
97
+ function labelOf(context) {
98
+ return context.cockroach ? 'CockroachDB' : DIALECT_LABELS[context.dialect];
99
+ }
94
100
  function quoteAlways(dialect, name) {
95
101
  switch (dialect) {
96
102
  case 'mysql':
@@ -260,14 +266,19 @@ const UUID_DEFAULTS = {
260
266
  // Entry point
261
267
  // ---------------------------------------------------------------------------
262
268
  export function emitSqlDdl(schema, options) {
269
+ if (options.provider === 'clickhouse') {
270
+ return emitClickhouseDdl(schema, options.naming);
271
+ }
263
272
  // The join table of a many-to-many field is an ordinary table in SQL.
264
273
  const expanded = expandManyToMany(schema);
265
274
  const prepared = options.naming === 'normalize' ? normalizeSchema(expanded) : expanded;
266
275
  const dialect = sqlDialectOf(options.provider);
267
276
  const context = {
268
277
  dialect,
278
+ cockroach: options.provider === 'cockroachdb',
269
279
  schema: prepared,
270
280
  warnings: [],
281
+ sequentialKeys: [],
271
282
  enums: new Map(),
272
283
  names: new Set(),
273
284
  unexpressedAutoUpdates: [],
@@ -296,8 +307,11 @@ export function emitSqlDdl(schema, options) {
296
307
  for (const view of views) {
297
308
  context.warnings.push(`${view.name}: this is a database view; the schema has no SQL definition for it, so only a commented placeholder was written.`);
298
309
  }
310
+ if (context.sequentialKeys.length > 0) {
311
+ context.warnings.push(cockroachSequentialWarning(context.sequentialKeys, 'written as GENERATED BY DEFAULT AS IDENTITY, which CockroachDB backs with a sequence (SERIAL would be unique_rowid(): 64-bit and not consecutive)'));
312
+ }
299
313
  if (context.unexpressedAutoUpdates.length > 0) {
300
- context.warnings.push(`${DIALECT_LABELS[dialect]} cannot refresh a column on update from the DDL (${context.unexpressedAutoUpdates.join(', ')}); the application or a trigger has to set it.`);
314
+ context.warnings.push(`${labelOf(context)} cannot refresh a column on update from the DDL (${context.unexpressedAutoUpdates.join(', ')}); the application or a trigger has to set it.`);
301
315
  }
302
316
  return { text: render(context, ordered, views), warnings: context.warnings };
303
317
  }
@@ -306,7 +320,7 @@ function warnAboutProvider(context, provider) {
306
320
  context.warnings.push('Provider "mongodb": MongoDB has no SQL schema; PostgreSQL DDL was written.');
307
321
  }
308
322
  else if (provider === 'cockroachdb') {
309
- context.warnings.push('Provider "cockroachdb": PostgreSQL DDL was written, which CockroachDB accepts; check the types it maps differently.');
323
+ context.warnings.push(`${COCKROACH_PREFIX} PostgreSQL-compatible DDL was written for CockroachDB: integers are sized explicitly (INT4, INT2, INT8) because a bare INT is 64-bit there, and PostgreSQL-only types and index methods are replaced and reported below.`);
310
324
  }
311
325
  }
312
326
  function warnAboutIgnoredConstructs(context) {
@@ -348,7 +362,7 @@ function generatedName(context, table, columns, suffix) {
348
362
  function checkIdentifierLength(context, label, name) {
349
363
  const max = MAX_IDENTIFIER[context.dialect];
350
364
  if (context.dialect !== 'sqlite' && name.length > max) {
351
- context.warnings.push(`${label}: the name "${name}" is longer than the ${max} characters ${DIALECT_LABELS[context.dialect]} allows in an identifier.`);
365
+ context.warnings.push(`${label}: the name "${name}" is longer than the ${max} characters ${labelOf(context)} allows in an identifier.`);
352
366
  }
353
367
  }
354
368
  function resolveEnums(context, naming) {
@@ -408,6 +422,29 @@ function nativeTypeSql(dialect, field) {
408
422
  const [first, ...rest] = known.split(' ');
409
423
  return `${first ?? known}(${args.join(', ')})${rest.length > 0 ? ` ${rest.join(' ')}` : ''}`;
410
424
  }
425
+ /**
426
+ * CockroachDB's reading of a Prisma `@db.*` native type, for the types that differ from PostgreSQL:
427
+ * `Integer` is spelled INT4 (a bare INTEGER is 64-bit), `Money` and `Xml` do not exist, and `Json` is
428
+ * stored as JSONB.
429
+ */
430
+ function cockroachNativeType(context, label, field) {
431
+ const name = field.nativeType?.name.toLowerCase();
432
+ switch (name) {
433
+ case 'integer':
434
+ return plainType('INT4', field);
435
+ case 'money':
436
+ context.warnings.push(`${label}: CockroachDB has no MONEY type; the column was written as NUMERIC(19, 4).`);
437
+ return plainType('NUMERIC(19, 4)', field);
438
+ case 'xml':
439
+ context.warnings.push(`${label}: CockroachDB has no XML type; the column was written as TEXT.`);
440
+ return { ...plainType('TEXT', field), textual: true, storage: 'text' };
441
+ case 'json':
442
+ context.warnings.push(`${label}: CockroachDB stores JSON as JSONB (key order and duplicate keys are not kept); the column was written as JSONB.`);
443
+ return { ...plainType('JSONB', field), textual: true };
444
+ default:
445
+ return undefined;
446
+ }
447
+ }
411
448
  function storageOf(type) {
412
449
  switch (type) {
413
450
  case 'text':
@@ -437,6 +474,10 @@ function columnType(context, label, field, columnSql) {
437
474
  if (depth === 0) {
438
475
  return element;
439
476
  }
477
+ if (context.cockroach && (depth > 1 || element.storage === 'json')) {
478
+ context.warnings.push(`${label}: CockroachDB has no ${depth > 1 ? 'multi-dimensional arrays' : 'arrays of JSON'}; the column was written as JSONB.`);
479
+ return jsonColumnType(context, columnSql, 'an array stored as JSON');
480
+ }
440
481
  if (context.dialect === 'postgresql') {
441
482
  return {
442
483
  ...element,
@@ -444,7 +485,7 @@ function columnType(context, label, field, columnSql) {
444
485
  storage: 'other',
445
486
  };
446
487
  }
447
- const label2 = DIALECT_LABELS[context.dialect];
488
+ const label2 = labelOf(context);
448
489
  context.warnings.push(`${label}: ${label2} has no array column type; the array was written as a JSON ${context.dialect === 'sqlite' ? 'text ' : ''}column.`);
449
490
  return jsonColumnType(context, columnSql, 'an array stored as JSON');
450
491
  }
@@ -498,6 +539,12 @@ function scalarColumnType(context, label, field, columnSql, isArrayElement) {
498
539
  if (enumInfo !== undefined) {
499
540
  return enumColumnType(context, enumInfo, columnSql, isArrayElement);
500
541
  }
542
+ if (context.cockroach) {
543
+ const cockroachNative = cockroachNativeType(context, label, field);
544
+ if (cockroachNative !== undefined) {
545
+ return cockroachNative;
546
+ }
547
+ }
501
548
  const native = nativeTypeSql(dialect, field);
502
549
  if (native !== undefined) {
503
550
  return {
@@ -534,7 +581,12 @@ function scalarColumnType(context, label, field, columnSql, isArrayElement) {
534
581
  textual: true,
535
582
  };
536
583
  case 'int':
537
- return plainType(dialect === 'mysql' || dialect === 'sqlserver' ? 'INT' : 'INTEGER', field);
584
+ // A bare INT (INTEGER) is 64-bit on CockroachDB, so the 32-bit IR type is spelled INT4.
585
+ return plainType(context.cockroach
586
+ ? 'INT4'
587
+ : dialect === 'mysql' || dialect === 'sqlserver'
588
+ ? 'INT'
589
+ : 'INTEGER', field);
538
590
  case 'bigInt':
539
591
  return plainType('BIGINT', field);
540
592
  case 'float':
@@ -581,7 +633,7 @@ function scalarColumnType(context, label, field, columnSql, isArrayElement) {
581
633
  if (dialect === 'postgresql') {
582
634
  return plainType('INTERVAL', field);
583
635
  }
584
- context.warnings.push(`${label}: ${DIALECT_LABELS[dialect]} has no interval type; the duration was written as an integer (microseconds).`);
636
+ context.warnings.push(`${label}: ${labelOf(context)} has no interval type; the duration was written as an integer (microseconds).`);
585
637
  return {
586
638
  ...plainType(dialect === 'sqlite' ? 'INTEGER' : 'BIGINT', field),
587
639
  comment: 'duration in microseconds',
@@ -598,6 +650,10 @@ function scalarColumnType(context, label, field, columnSql, isArrayElement) {
598
650
  textual: true,
599
651
  };
600
652
  case 'hstore':
653
+ if (context.cockroach) {
654
+ context.warnings.push(`${label}: CockroachDB has no hstore type; the column was written as JSONB.`);
655
+ return jsonColumnType(context, columnSql, 'hstore stored as JSONB');
656
+ }
601
657
  if (dialect === 'postgresql') {
602
658
  context.needsHstore = true;
603
659
  return { ...plainType('HSTORE', field), textual: true };
@@ -666,7 +722,7 @@ function decimalType(context, label, field) {
666
722
  }
667
723
  const limit = dialect === 'mysql' ? 65 : dialect === 'sqlserver' ? 38 : undefined;
668
724
  if (precision !== undefined && limit !== undefined && precision > limit) {
669
- context.warnings.push(`${label}: ${DIALECT_LABELS[dialect]} allows at most ${limit} digits in a decimal; ${precision} was reduced to ${limit}.`);
725
+ context.warnings.push(`${label}: ${labelOf(context)} allows at most ${limit} digits in a decimal; ${precision} was reduced to ${limit}.`);
670
726
  precision = limit;
671
727
  if (scale !== undefined && scale > limit) {
672
728
  scale = limit;
@@ -689,6 +745,14 @@ function decimalType(context, label, field) {
689
745
  ]);
690
746
  }
691
747
  function rangeColumnType(context, label, field) {
748
+ if (context.cockroach) {
749
+ context.warnings.push(`${label}: CockroachDB has no range types (int4range, tstzrange, ...); the column was written as text.`);
750
+ return {
751
+ ...plainType('TEXT', field),
752
+ comment: 'range stored as text',
753
+ textual: true,
754
+ };
755
+ }
692
756
  if (context.dialect === 'postgresql') {
693
757
  return {
694
758
  ...plainType(POSTGRES_RANGE_TYPES[field.rangeOf ?? 'int'] ?? 'INT4RANGE', field),
@@ -711,7 +775,18 @@ function rangeColumnType(context, label, field) {
711
775
  }
712
776
  function unsupportedColumnType(context, label, field) {
713
777
  if (field.unsupportedType !== undefined && field.unsupportedType !== '') {
714
- context.warnings.push(`${label}: the database type "${field.unsupportedType}" (Prisma Unsupported) was written as it is; check that ${DIALECT_LABELS[context.dialect]} has it.`);
778
+ const missing = context.cockroach
779
+ ? cockroachMissingType(field.unsupportedType)
780
+ : undefined;
781
+ if (missing !== undefined) {
782
+ context.warnings.push(`${label}: CockroachDB has no ${missing} type; "${field.unsupportedType}" was written as it is, so CREATE TABLE fails until the column type is changed.`);
783
+ return {
784
+ ...plainType(field.unsupportedType, field),
785
+ storage: 'other',
786
+ textual: false,
787
+ };
788
+ }
789
+ context.warnings.push(`${label}: the database type "${field.unsupportedType}" (Prisma Unsupported) was written as it is; check that ${labelOf(context)} has it.`);
715
790
  return {
716
791
  ...plainType(field.unsupportedType, field),
717
792
  storage: 'other',
@@ -885,11 +960,15 @@ function defaultSql(context, label, field, type) {
885
960
  if (value.isFunction === true) {
886
961
  return dropped(`the ${value.expression} default function has no SQL equivalent`);
887
962
  }
963
+ if (context.cockroach &&
964
+ value.expression.replace(/\s+/g, '').toLowerCase() === 'unique_rowid()') {
965
+ return 'unique_rowid()';
966
+ }
888
967
  const known = knownExpression(dialect, field, value.expression, type);
889
968
  if (known !== undefined) {
890
969
  return known;
891
970
  }
892
- context.warnings.push(`${label}: the database default expression ${value.expression} was written as it is; check that ${DIALECT_LABELS[dialect]} accepts it.`);
971
+ context.warnings.push(`${label}: the database default expression ${value.expression} was written as it is; check that ${labelOf(context)} accepts it.`);
893
972
  const expression = value.expression.trim();
894
973
  return expression.startsWith('(') ? expression : `(${expression})`;
895
974
  }
@@ -979,6 +1058,12 @@ function literalDefault(context, label, field, value, type) {
979
1058
  case 'bytes':
980
1059
  return dropped('a literal default on a binary column cannot be written');
981
1060
  case 'hstore': {
1061
+ if (context.cockroach) {
1062
+ const asJson = parseJson(text);
1063
+ return asJson.ok
1064
+ ? `${sqlStringLiteral(dialect, JSON.stringify(asJson.value))}::jsonb`
1065
+ : dropped(`the value ${text} is not JSON`);
1066
+ }
982
1067
  if (dialect === 'postgresql') {
983
1068
  return text === '{}'
984
1069
  ? "''::hstore"
@@ -1002,7 +1087,7 @@ function isIntegerKey(field) {
1002
1087
  return field.type === 'int' || field.type === 'bigInt';
1003
1088
  }
1004
1089
  /** Resolves the column a foreign key points at: `toField`, otherwise the target's single-column key. */
1005
- function resolveTarget(context, target, toField, depth = 0) {
1090
+ export function resolveTarget(context, target, toField, depth = 0) {
1006
1091
  if (depth > 5) {
1007
1092
  return undefined;
1008
1093
  }
@@ -1053,7 +1138,7 @@ function resolveRelationColumn(context, owner, relation, depth) {
1053
1138
  // ---------------------------------------------------------------------------
1054
1139
  // Tables
1055
1140
  // ---------------------------------------------------------------------------
1056
- function columnNameOf(model, name) {
1141
+ export function columnNameOf(model, name) {
1057
1142
  const relation = model.relations.find((candidate) => candidate.kind !== 'manyToMany' && candidate.name === name) ??
1058
1143
  model.relations.find((candidate) => candidate.kind !== 'manyToMany' && candidate.columnName === name);
1059
1144
  const field = model.fields.find((candidate) => candidate.name === name) ??
@@ -1061,8 +1146,15 @@ function columnNameOf(model, name) {
1061
1146
  // A field wins over a relation of the same name: the field is the column the key is written on.
1062
1147
  return field?.columnName ?? relation?.columnName;
1063
1148
  }
1064
- function isAutoIncrement(field) {
1065
- return field.default?.kind === 'autoIncrement';
1149
+ function isAutoIncrement(context, field) {
1150
+ if (field.default?.kind === 'autoIncrement') {
1151
+ return true;
1152
+ }
1153
+ // Prisma's CockroachDB `sequence()` default is the SERIAL of that database.
1154
+ return (context.cockroach &&
1155
+ field.default?.kind === 'dbExpression' &&
1156
+ field.default.isFunction === true &&
1157
+ /^sequence\(/i.test(field.default.expression));
1066
1158
  }
1067
1159
  function buildTable(context, model) {
1068
1160
  const dialect = context.dialect;
@@ -1226,7 +1318,7 @@ function buildColumn(context, label, field, columnName, inKey, singleColumnKey)
1226
1318
  notes.push('generated column written as a regular column');
1227
1319
  }
1228
1320
  let autoIncrement = false;
1229
- if (isAutoIncrement(field)) {
1321
+ if (isAutoIncrement(context, field)) {
1230
1322
  if (!isIntegerKey(field)) {
1231
1323
  context.warnings.push(`${label}: an auto-increment default on a ${field.type} column cannot be written; it was dropped.`);
1232
1324
  }
@@ -1238,6 +1330,9 @@ function buildColumn(context, label, field, columnName, inKey, singleColumnKey)
1238
1330
  }
1239
1331
  else {
1240
1332
  autoIncrement = true;
1333
+ if (context.cockroach) {
1334
+ context.sequentialKeys.push(label);
1335
+ }
1241
1336
  }
1242
1337
  }
1243
1338
  let sqlType = type.sql;
@@ -1254,6 +1349,11 @@ function buildColumn(context, label, field, columnName, inKey, singleColumnKey)
1254
1349
  onUpdate = mysqlCurrentTimestamp(type.sql);
1255
1350
  defaultText ??= onUpdate;
1256
1351
  }
1352
+ else if (context.cockroach && field.type === 'dateTime') {
1353
+ // CockroachDB (unlike PostgreSQL) has column-level ON UPDATE.
1354
+ onUpdate = NOW_DEFAULTS[dialect].dateTime;
1355
+ defaultText ??= onUpdate;
1356
+ }
1257
1357
  else {
1258
1358
  context.unexpressedAutoUpdates.push(label);
1259
1359
  }
@@ -1442,7 +1542,10 @@ function indexColumnSql(context, table, label, column, options) {
1442
1542
  else if (options?.length !== undefined) {
1443
1543
  context.warnings.push(`${label}: the prefix length of "${column}" is only supported by MySQL and was dropped.`);
1444
1544
  }
1445
- if (context.dialect === 'postgresql' && options?.ops !== undefined) {
1545
+ if (context.cockroach && options?.ops !== undefined) {
1546
+ context.warnings.push(`${label}: CockroachDB supports only trigram operator classes on inverted indexes; the operator class of "${column}" was dropped.`);
1547
+ }
1548
+ else if (context.dialect === 'postgresql' && options?.ops !== undefined) {
1446
1549
  const raw = /^raw\(\s*["'](.*)["']\s*\)$/.exec(options.ops);
1447
1550
  text += ` ${raw?.[1] ?? toSnakeCase(options.ops)}`;
1448
1551
  }
@@ -1476,7 +1579,7 @@ function buildIndexes(context, table) {
1476
1579
  const blocked = columns.find((column) => table.columns.find((candidate) => candidate.name === column)
1477
1580
  ?.unindexable === true);
1478
1581
  if (blocked !== undefined) {
1479
- context.warnings.push(`${label}: ${DIALECT_LABELS[dialect]} cannot index the column "${blocked}" (its type is unbounded or JSON); the index was skipped.`);
1582
+ context.warnings.push(`${label}: ${labelOf(context)} cannot index the column "${blocked}" (its type is unbounded or JSON); the index was skipped.`);
1480
1583
  continue;
1481
1584
  }
1482
1585
  const name = claimName(context, index.name ??
@@ -1499,7 +1602,7 @@ function buildIndexes(context, table) {
1499
1602
  const blocked = unique.columns.find((column) => table.columns.find((candidate) => candidate.name === column)
1500
1603
  ?.unindexable === true);
1501
1604
  if (blocked !== undefined) {
1502
- context.warnings.push(`${model.name}.${blocked}: ${DIALECT_LABELS[dialect]} cannot index the column "${blocked}" (its type is unbounded or JSON); the unique constraint was skipped.`);
1605
+ context.warnings.push(`${model.name}.${blocked}: ${labelOf(context)} cannot index the column "${blocked}" (its type is unbounded or JSON); the unique constraint was skipped.`);
1503
1606
  }
1504
1607
  return blocked === undefined;
1505
1608
  });
@@ -1532,12 +1635,13 @@ function indexStatement(context, table, label, index, name, columns, entries) {
1532
1635
  return `CREATE FULLTEXT INDEX ${nameSql} ON ${tableSql} (${list});`;
1533
1636
  }
1534
1637
  if (dialect === 'postgresql') {
1638
+ // CockroachDB builds the same inverted index over to_tsvector() (checked on v26.3).
1535
1639
  const vector = columns
1536
1640
  .map((column) => `coalesce(${quote(context, column)}::text, '')`)
1537
1641
  .join(" || ' ' || ");
1538
1642
  return `CREATE INDEX ${nameSql} ON ${tableSql} USING gin (to_tsvector('simple', ${vector}));`;
1539
1643
  }
1540
- context.warnings.push(`${label}: ${DIALECT_LABELS[dialect]} full-text search needs a separate mechanism (a virtual table or a catalog); it was written as an ordinary index.`);
1644
+ context.warnings.push(`${label}: ${labelOf(context)} full-text search needs a separate mechanism (a virtual table or a catalog); it was written as an ordinary index.`);
1541
1645
  }
1542
1646
  const parts = ['CREATE'];
1543
1647
  if (index.isUnique) {
@@ -1555,7 +1659,23 @@ function indexStatement(context, table, label, index, name, columns, entries) {
1555
1659
  let trailing = '';
1556
1660
  if (index.method !== undefined) {
1557
1661
  const method = index.method.toUpperCase();
1558
- if (dialect === 'postgresql') {
1662
+ if (context.cockroach) {
1663
+ const last = columns[columns.length - 1];
1664
+ const lastType = table.columns.find((candidate) => candidate.name === last)
1665
+ ?.sqlType ?? '';
1666
+ if (method === 'GIN') {
1667
+ if (/JSONB|\[\]$|TSVECTOR|GEOMETRY|GEOGRAPHY/i.test(lastType)) {
1668
+ target = `${tableSql} USING gin`;
1669
+ }
1670
+ else {
1671
+ context.warnings.push(`${label}: CockroachDB builds an inverted (GIN) index only over JSONB, array, tsvector or spatial columns; "${last ?? ''}" is ${lastType}, so an ordinary index was written.`);
1672
+ }
1673
+ }
1674
+ else if (method !== 'BTREE') {
1675
+ context.warnings.push(`${label}: CockroachDB has no ${index.method} indexes (only B-tree and inverted GIN); an ordinary index was written${method === 'HASH' ? ' (CREATE INDEX ... USING HASH is a hash-sharded B-tree there, a different feature)' : ''}.`);
1676
+ }
1677
+ }
1678
+ else if (dialect === 'postgresql') {
1559
1679
  target = `${tableSql} USING ${method.toLowerCase()}`;
1560
1680
  }
1561
1681
  else if (dialect === 'mysql' &&
@@ -1564,7 +1684,7 @@ function indexStatement(context, table, label, index, name, columns, entries) {
1564
1684
  }
1565
1685
  else if (method !== 'BTREE') {
1566
1686
  // B-tree is what every database builds anyway.
1567
- context.warnings.push(`${label}: the index type ${index.method} is not supported by ${DIALECT_LABELS[dialect]} and was dropped.`);
1687
+ context.warnings.push(`${label}: the index type ${index.method} is not supported by ${labelOf(context)} and was dropped.`);
1568
1688
  }
1569
1689
  }
1570
1690
  parts.push('INDEX', nameSql, 'ON', target);
@@ -1698,7 +1818,7 @@ function renderView(context, view) {
1698
1818
  function render(context, ordered, views) {
1699
1819
  const dialect = context.dialect;
1700
1820
  const blocks = [
1701
- `-- SQL DDL for ${DIALECT_LABELS[dialect]}, generated by ormbridge.`,
1821
+ `-- SQL DDL for ${labelOf(context)}, generated by ormbridge.`,
1702
1822
  ];
1703
1823
  if (dialect === 'sqlite') {
1704
1824
  blocks.push('PRAGMA foreign_keys = ON;');