uql-orm 0.66.0 → 0.67.1

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 (193) hide show
  1. package/dist/browser/querier/httpQuerier.js +1 -8
  2. package/dist/browser/uql-browser.min.js.map +5 -5
  3. package/dist/bunSql/bunSql.util.d.ts +2 -6
  4. package/dist/bunSql/bunSql.util.js +2 -6
  5. package/dist/bunSql/bunSqlQuerier.d.ts +2 -5
  6. package/dist/bunSql/bunSqlQuerier.js +2 -5
  7. package/dist/cockroachdb/cockroachDialect.d.ts +4 -13
  8. package/dist/cockroachdb/cockroachDialect.js +4 -13
  9. package/dist/context/context.browser.js +2 -10
  10. package/dist/context/context.d.ts +4 -17
  11. package/dist/context/context.js +4 -17
  12. package/dist/dialect/abstractDialect.d.ts +4 -19
  13. package/dist/dialect/abstractDialect.js +2 -20
  14. package/dist/dialect/abstractSqlDialect.d.ts +47 -212
  15. package/dist/dialect/abstractSqlDialect.js +68 -222
  16. package/dist/dialect/aliases.d.ts +2 -12
  17. package/dist/dialect/aliases.js +4 -12
  18. package/dist/dialect/hydrateColumn.d.ts +2 -6
  19. package/dist/dialect/hydrateColumn.js +3 -13
  20. package/dist/dialect/jsonArrayElemMatchUtils.d.ts +1 -7
  21. package/dist/dialect/jsonArrayElemMatchUtils.js +1 -7
  22. package/dist/dialect/jsonSql.d.ts +6 -27
  23. package/dist/dialect/jsonSql.js +6 -27
  24. package/dist/dialect/mergeSqlDialect.d.ts +4 -22
  25. package/dist/dialect/mergeSqlDialect.js +4 -22
  26. package/dist/dialect/mysqlLikeSqlDialect.d.ts +11 -37
  27. package/dist/dialect/mysqlLikeSqlDialect.js +35 -51
  28. package/dist/dialect/pgLikeSqlDialect.d.ts +8 -22
  29. package/dist/dialect/pgLikeSqlDialect.js +36 -39
  30. package/dist/dialect/queryContext.d.ts +4 -22
  31. package/dist/dialect/queryContext.js +4 -22
  32. package/dist/dialect/queryJoins.d.ts +3 -12
  33. package/dist/dialect/queryJoins.js +3 -12
  34. package/dist/dialect/vectorCast.d.ts +2 -12
  35. package/dist/dialect/vectorCast.js +3 -19
  36. package/dist/dialect/vectorSqlDialect.d.ts +8 -38
  37. package/dist/dialect/vectorSqlDialect.js +7 -38
  38. package/dist/entity/decorator/bag.d.ts +6 -19
  39. package/dist/entity/decorator/bag.js +6 -22
  40. package/dist/entity/decorator/entity.d.ts +2 -7
  41. package/dist/entity/decorator/entity.js +2 -7
  42. package/dist/entity/decorator/members.d.ts +7 -30
  43. package/dist/entity/decorator/members.js +3 -12
  44. package/dist/entity/metadata/definition.d.ts +2 -18
  45. package/dist/entity/metadata/definition.js +6 -28
  46. package/dist/http/handler.d.ts +2 -14
  47. package/dist/index.d.ts +4 -1
  48. package/dist/index.js +3 -1
  49. package/dist/libsql/libsqlDialect.d.ts +1 -8
  50. package/dist/libsql/libsqlDialect.js +1 -8
  51. package/dist/maria/mariaDialect.d.ts +3 -5
  52. package/dist/maria/mariaDialect.js +5 -5
  53. package/dist/maria/mariadbQuerier.js +2 -2
  54. package/dist/maria/mariadbQuerierPool.js +1 -6
  55. package/dist/migrate/builder/migrationBuilder.js +3 -19
  56. package/dist/migrate/builder/splitSqlStatements.d.ts +1 -14
  57. package/dist/migrate/builder/splitSqlStatements.js +2 -22
  58. package/dist/migrate/builder/types.d.ts +2 -15
  59. package/dist/migrate/cli-config.js +2 -11
  60. package/dist/migrate/cli.js +2 -7
  61. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -15
  62. package/dist/migrate/codegen/entityCodeGenerator.js +15 -44
  63. package/dist/migrate/codegen/fieldOptionsSource.d.ts +1 -8
  64. package/dist/migrate/codegen/fieldOptionsSource.js +3 -22
  65. package/dist/migrate/ddl/indexDdl.d.ts +2 -5
  66. package/dist/migrate/ddl/indexDdl.js +2 -5
  67. package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -13
  68. package/dist/migrate/ddl/pgIndexDdl.js +3 -13
  69. package/dist/migrate/generator/definitionToNode.d.ts +2 -9
  70. package/dist/migrate/generator/definitionToNode.js +3 -17
  71. package/dist/migrate/generator/indexNodeToSchema.d.ts +2 -3
  72. package/dist/migrate/generator/indexNodeToSchema.js +2 -3
  73. package/dist/migrate/generator/mongoCommand.d.ts +1 -8
  74. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -8
  75. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -8
  76. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +6 -26
  77. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +9 -41
  78. package/dist/migrate/introspection/baseSqlIntrospector.js +0 -1
  79. package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
  80. package/dist/migrate/introspection/mongoIntrospector.js +48 -46
  81. package/dist/migrate/introspection/mssqlIntrospector.d.ts +4 -4
  82. package/dist/migrate/introspection/mssqlIntrospector.js +18 -27
  83. package/dist/migrate/introspection/mysqlIntrospector.d.ts +7 -2
  84. package/dist/migrate/introspection/mysqlIntrospector.js +16 -14
  85. package/dist/migrate/introspection/postgresIntrospector.d.ts +24 -9
  86. package/dist/migrate/introspection/postgresIntrospector.js +68 -59
  87. package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -1
  88. package/dist/migrate/introspection/sqliteIntrospector.js +8 -10
  89. package/dist/migrate/migrator.d.ts +9 -53
  90. package/dist/migrate/migrator.js +32 -65
  91. package/dist/migrate/schemaGenerator.d.ts +16 -66
  92. package/dist/migrate/schemaGenerator.js +21 -74
  93. package/dist/mongo/mongoDialect.d.ts +21 -53
  94. package/dist/mongo/mongoDialect.js +25 -70
  95. package/dist/mongo/mongodbQuerier.d.ts +5 -8
  96. package/dist/mongo/mongodbQuerier.js +31 -65
  97. package/dist/mssql/mssqlDialect.d.ts +8 -34
  98. package/dist/mssql/mssqlDialect.js +37 -51
  99. package/dist/mssql/mssqlQuerier.d.ts +37 -4
  100. package/dist/mssql/mssqlQuerier.js +2 -2
  101. package/dist/mssql/mssqlWireTypes.d.ts +2 -14
  102. package/dist/mssql/mssqlWireTypes.js +2 -14
  103. package/dist/nestjs/uqlModule.js +2 -7
  104. package/dist/pglite/pgliteQuerier.d.ts +1 -9
  105. package/dist/pglite/pgliteQuerierPool.d.ts +4 -26
  106. package/dist/pglite/pgliteQuerierPool.js +3 -18
  107. package/dist/postgres/abstractPgQuerierPool.d.ts +1 -8
  108. package/dist/postgres/abstractPgQuerierPool.js +1 -8
  109. package/dist/postgres/pgNumericTypes.d.ts +3 -26
  110. package/dist/postgres/pgNumericTypes.js +3 -26
  111. package/dist/postgres/postgresDialect.d.ts +4 -10
  112. package/dist/postgres/postgresDialect.js +4 -10
  113. package/dist/querier/abstractQuerier.d.ts +35 -103
  114. package/dist/querier/abstractQuerier.js +105 -201
  115. package/dist/querier/abstractSharedHandleQuerierPool.d.ts +3 -17
  116. package/dist/querier/abstractSharedHandleQuerierPool.js +3 -17
  117. package/dist/querier/abstractSqlQuerier.d.ts +15 -36
  118. package/dist/querier/abstractSqlQuerier.js +49 -131
  119. package/dist/schema/canonicalType.d.ts +3 -21
  120. package/dist/schema/canonicalType.js +22 -67
  121. package/dist/schema/dependencyGraph.d.ts +2 -8
  122. package/dist/schema/dependencyGraph.js +2 -32
  123. package/dist/schema/index.d.ts +1 -25
  124. package/dist/schema/index.js +0 -26
  125. package/dist/schema/indexColumns.d.ts +1 -8
  126. package/dist/schema/indexColumns.js +1 -8
  127. package/dist/schema/indexDifferences.d.ts +7 -40
  128. package/dist/schema/indexDifferences.js +6 -31
  129. package/dist/schema/schemaAST.d.ts +8 -175
  130. package/dist/schema/schemaAST.js +13 -365
  131. package/dist/schema/schemaASTBuilder.d.ts +2 -24
  132. package/dist/schema/schemaASTBuilder.js +2 -30
  133. package/dist/schema/schemaASTDiffer.d.ts +6 -46
  134. package/dist/schema/schemaASTDiffer.js +8 -56
  135. package/dist/schema/types.d.ts +5 -61
  136. package/dist/schema/types.js +3 -6
  137. package/dist/sqlite/abstractSqliteQuerier.d.ts +1 -8
  138. package/dist/sqlite/localSqliteQuerierPool.d.ts +1 -7
  139. package/dist/sqlite/localSqliteQuerierPool.js +1 -7
  140. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +2 -7
  141. package/dist/sqlite/nodeSqliteQuerierPool.js +2 -7
  142. package/dist/sqlite/sqliteDialect.d.ts +5 -20
  143. package/dist/sqlite/sqliteDialect.js +29 -35
  144. package/dist/turso/tursoDialect.d.ts +4 -6
  145. package/dist/turso/tursoDialect.js +4 -6
  146. package/dist/turso/tursoLocalQuerierPool.d.ts +1 -7
  147. package/dist/turso/tursoLocalQuerierPool.js +1 -7
  148. package/dist/turso/tursoQuerierPool.d.ts +2 -6
  149. package/dist/turso/tursoQuerierPool.js +2 -6
  150. package/dist/turso/tursoSessionQuerier.d.ts +1 -7
  151. package/dist/turso/tursoSessionQuerier.js +1 -7
  152. package/dist/type/dialect.d.ts +42 -94
  153. package/dist/type/dialect.js +3 -13
  154. package/dist/type/entity.d.ts +163 -534
  155. package/dist/type/entity.js +26 -9
  156. package/dist/type/logger.d.ts +2 -14
  157. package/dist/type/migration.d.ts +9 -38
  158. package/dist/type/querier.d.ts +9 -28
  159. package/dist/type/querierPool.d.ts +4 -26
  160. package/dist/type/query.d.ts +19 -73
  161. package/dist/type/query.js +2 -7
  162. package/dist/type/queryAggregate.d.ts +18 -98
  163. package/dist/type/queryRaw.d.ts +1 -8
  164. package/dist/type/queryRaw.js +1 -8
  165. package/dist/type/queryWhere.d.ts +13 -61
  166. package/dist/type/universalQuerier.d.ts +18 -105
  167. package/dist/type/utility.d.ts +12 -24
  168. package/dist/type/vector.d.ts +8 -38
  169. package/dist/type/vector.js +1 -1
  170. package/dist/type/wire.d.ts +2 -5
  171. package/dist/util/dialect.util.d.ts +9 -27
  172. package/dist/util/dialect.util.js +10 -27
  173. package/dist/util/field.util.d.ts +2 -31
  174. package/dist/util/field.util.js +3 -43
  175. package/dist/util/fieldOption.util.d.ts +7 -15
  176. package/dist/util/fieldOption.util.js +1 -1
  177. package/dist/util/filters.util.d.ts +2 -5
  178. package/dist/util/filters.util.js +2 -5
  179. package/dist/util/logger.d.ts +2 -6
  180. package/dist/util/logger.js +2 -6
  181. package/dist/util/object.util.d.ts +2 -6
  182. package/dist/util/object.util.js +1 -5
  183. package/dist/util/raw.d.ts +3 -23
  184. package/dist/util/relationQuery.util.d.ts +3 -14
  185. package/dist/util/relationQuery.util.js +3 -14
  186. package/dist/util/rowKey.util.d.ts +2 -10
  187. package/dist/util/rowKey.util.js +2 -10
  188. package/dist/util/sql.util.d.ts +6 -37
  189. package/dist/util/sql.util.js +13 -73
  190. package/dist/util/sqlLiteral.d.ts +2 -13
  191. package/dist/util/sqlLiteral.js +8 -13
  192. package/dist/util/string.util.js +0 -2
  193. package/package.json +4 -4
@@ -1,3 +1,4 @@
1
+ import { unescapeMysqlString } from '../../util/sqlLiteral.js';
1
2
  import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.js';
2
3
  /**
3
4
  * MySQL/MariaDB schema introspector.
@@ -23,9 +24,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
23
24
  AND TABLE_NAME = ?
24
25
  `;
25
26
  }
26
- parseTableExistsResult(results) {
27
- // No row, or no count in it, reads as `NaN`, which is not above zero.
28
- return Number(results[0]?.count) > 0;
27
+ parseTableExistsResult([row]) {
28
+ return Number(row.count) > 0;
29
29
  }
30
30
  getColumnsQuery(_tableName) {
31
31
  return /*sql*/ `
@@ -98,7 +98,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
98
98
  nullable: row.is_nullable === 'YES',
99
99
  defaultValue: this.parseDefaultValue(row.column_default),
100
100
  isPrimaryKey: row.column_key === 'PRI',
101
- isAutoIncrement: (row.extra || '').toLowerCase().includes('auto_increment'),
101
+ isAutoIncrement: row.extra.toLowerCase().includes('auto_increment'),
102
102
  isUnique: row.column_key === 'UNI',
103
103
  length: this.toNumber(row.character_maximum_length),
104
104
  precision: this.toNumber(row.numeric_precision),
@@ -112,7 +112,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
112
112
  // A functional or multi-valued key part has no `COLUMN_NAME` - the `COALESCE` above keeps its
113
113
  // place in the list, and it is reported as the expression it is, which is what stops diffing
114
114
  // from comparing an entry list the server cannot state against the entity's own.
115
- entries: (row.columns ?? '').split(',').map((column) => (column ? { column } : { column, expression: true })),
115
+ entries: row.columns.split(',').map((column) => (column ? { column } : { column, expression: true })),
116
116
  unique: Boolean(row.is_unique),
117
117
  }));
118
118
  }
@@ -125,6 +125,11 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
125
125
  onUpdate: this.normalizeReferentialAction(row.update_rule),
126
126
  }));
127
127
  }
128
+ /**
129
+ * MariaDB prints a string default as the literal it is (`'it''s'`). MySQL prints one bare, save an
130
+ * expression default (`DEFAULT ('x')`, what a `TEXT` column takes), which comes with a charset
131
+ * introducer and every quote and backslash escaped once more: `_utf8mb4\'x\'`.
132
+ */
128
133
  parseDefaultValue(defaultValue) {
129
134
  if (defaultValue === null) {
130
135
  return undefined;
@@ -136,16 +141,13 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
136
141
  if (normalized === 'CURRENT_TIMESTAMP' || normalized === 'CURRENT_TIMESTAMP()') {
137
142
  return 'CURRENT_TIMESTAMP';
138
143
  }
139
- if (/^-?\d+$/.test(defaultValue)) {
140
- return Number.parseInt(defaultValue, 10);
144
+ if (/^-?\d+(\.\d+)?$/.test(defaultValue)) {
145
+ return Number(defaultValue);
141
146
  }
142
- if (/^-?\d+\.\d+$/.test(defaultValue)) {
143
- return Number.parseFloat(defaultValue);
144
- }
145
- if (defaultValue?.startsWith("'") && defaultValue?.endsWith("'")) {
146
- return defaultValue.slice(1, -1);
147
- }
148
- return defaultValue;
147
+ const introduced = /^_\w+(\\'.*\\')$/s.exec(defaultValue);
148
+ const literal = introduced ? unescapeMysqlString(introduced[1]) : defaultValue;
149
+ const quoted = /^'(.*)'$/s.exec(literal);
150
+ return quoted ? unescapeMysqlString(quoted[1]) : literal;
149
151
  }
150
152
  }
151
153
  /**
@@ -13,7 +13,7 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
13
13
  readonly indexFacets: ReadonlySet<IndexFacet>;
14
14
  protected getTableNamesQuery(): string;
15
15
  protected tableExistsQuery(): string;
16
- protected parseTableExistsResult(results: RawRow[]): boolean;
16
+ protected parseTableExistsResult([row]: RawRow[]): boolean;
17
17
  protected getColumnsQuery(_tableName: string): string;
18
18
  /**
19
19
  * `attname` where the entry is a column, `pg_get_indexdef` for that one position where it is an
@@ -36,22 +36,29 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
36
36
  * compare. This one decides which indexes are reported at all.
37
37
  */
38
38
  protected readonly constraintIndexTypes: readonly string[];
39
+ /** From `pg_constraint`, whose key arrays keep each column paired with the one it references. */
39
40
  protected getForeignKeysQuery(_tableName: string): string;
40
41
  protected getPrimaryKeyQuery(_tableName: string): string;
41
42
  protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: PostgresColumnRow[]): Promise<ColumnSchema[]>;
42
43
  protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: PostgresIndexRow[]): Promise<IndexSchema[]>;
43
- protected mapForeignKeysResult(_read: TableRowReader, _tableName: string, results: {
44
- constraint_name: string;
45
- columns: string[];
46
- referenced_table: string;
47
- referenced_columns: string[];
48
- delete_rule: string;
49
- update_rule: string;
50
- }[]): Promise<ForeignKeySchema[]>;
44
+ protected mapForeignKeysResult(_read: TableRowReader, _tableName: string, results: PostgresForeignKeyRow[]): Promise<ForeignKeySchema[]>;
51
45
  protected normalizeType(dataType: string, udtName: string): string;
46
+ /**
47
+ * Postgres quotes a negative number (`'-3'::integer`); CockroachDB parenthesizes one (`(-3)`) and
48
+ * writes a quote-bearing string in escape syntax (`e'it\'s'`). Any other cast is dropped, and a
49
+ * function call (`now()`, `nextval(...)`) is returned as written.
50
+ */
52
51
  protected parseDefaultValue(defaultValue: string | null): unknown;
53
52
  protected isAutoIncrement(columnDefault: string | null, isIdentity: string): boolean;
54
53
  }
54
+ /** `pg_constraint`'s one-letter spelling of each action. */
55
+ declare const FOREIGN_KEY_ACTION_CODES: {
56
+ readonly a: 'NO ACTION';
57
+ readonly r: 'RESTRICT';
58
+ readonly c: 'CASCADE';
59
+ readonly n: 'SET NULL';
60
+ readonly d: 'SET DEFAULT';
61
+ };
55
62
  /**
56
63
  * CockroachDB answers the same catalogue queries and differs only in what it can express: v26.2.5
57
64
  * still rejects `NULLS FIRST/LAST` and operator classes as "unimplemented", and it sorts nulls first
@@ -69,6 +76,14 @@ export declare class CockroachSchemaIntrospector extends PostgresSchemaIntrospec
69
76
  */
70
77
  protected readonly constraintIndexTypes: readonly string[];
71
78
  }
79
+ type PostgresForeignKeyRow = {
80
+ constraint_name: string;
81
+ columns: string[];
82
+ referenced_table: string;
83
+ referenced_columns: string[];
84
+ delete_rule: keyof typeof FOREIGN_KEY_ACTION_CODES;
85
+ update_rule: keyof typeof FOREIGN_KEY_ACTION_CODES;
86
+ };
72
87
  /** One entry of one index; what the index itself is repeats across its rows. */
73
88
  type PostgresIndexRow = {
74
89
  index_name: string;
@@ -34,8 +34,8 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
34
34
  ) AS exists
35
35
  `;
36
36
  }
37
- parseTableExistsResult(results) {
38
- return results[0]?.['exists'] ?? false;
37
+ parseTableExistsResult([row]) {
38
+ return row['exists'] === true;
39
39
  }
40
40
  getColumnsQuery(_tableName) {
41
41
  return /*sql*/ `
@@ -50,28 +50,25 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
50
50
  c.numeric_scale,
51
51
  c.is_identity,
52
52
  c.identity_generation,
53
- COALESCE(
54
- (SELECT TRUE FROM information_schema.table_constraints tc
55
- JOIN information_schema.key_column_usage kcu
56
- ON tc.constraint_name = kcu.constraint_name
57
- WHERE tc.table_name = c.table_name
58
- AND tc.constraint_type = 'PRIMARY KEY'
59
- AND kcu.column_name = c.column_name
60
- LIMIT 1),
61
- FALSE
53
+ EXISTS (
54
+ SELECT 1 FROM information_schema.table_constraints tc
55
+ JOIN information_schema.key_column_usage kcu USING (constraint_schema, constraint_name)
56
+ WHERE tc.table_schema = c.table_schema
57
+ AND tc.table_name = c.table_name
58
+ AND tc.constraint_type = 'PRIMARY KEY'
59
+ AND kcu.column_name = c.column_name
62
60
  ) AS is_primary_key,
63
- COALESCE(
64
- (SELECT TRUE FROM information_schema.table_constraints tc
65
- JOIN information_schema.key_column_usage kcu
66
- ON tc.constraint_name = kcu.constraint_name
67
- WHERE tc.table_name = c.table_name
68
- AND tc.constraint_type = 'UNIQUE'
69
- AND kcu.column_name = c.column_name
70
- LIMIT 1),
71
- FALSE
61
+ EXISTS (
62
+ SELECT 1 FROM information_schema.table_constraints tc
63
+ JOIN information_schema.key_column_usage kcu USING (constraint_schema, constraint_name)
64
+ WHERE tc.table_schema = c.table_schema
65
+ AND tc.table_name = c.table_name
66
+ AND tc.constraint_type = 'UNIQUE'
67
+ GROUP BY tc.constraint_name
68
+ HAVING COUNT(*) = 1 AND MIN(kcu.column_name) = c.column_name
72
69
  ) AS is_unique,
73
70
  pg_catalog.col_description(
74
- (SELECT oid FROM pg_catalog.pg_class WHERE relname = c.table_name),
71
+ (quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass,
75
72
  c.ordinal_position
76
73
  ) AS column_comment
77
74
  FROM information_schema.columns c
@@ -113,7 +110,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
113
110
  LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum AND k.attnum > 0
114
111
  LEFT JOIN pg_opclass op ON op.oid = ix.indclass[k.n - 1]
115
112
  WHERE t.relname = $1
116
- AND n.nspname = 'public'
113
+ AND n.nspname = ${this.schemaExpr}
117
114
  AND NOT ix.indisprimary
118
115
  AND NOT EXISTS (
119
116
  SELECT 1 FROM pg_constraint con
@@ -132,30 +129,29 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
132
129
  * compare. This one decides which indexes are reported at all.
133
130
  */
134
131
  constraintIndexTypes = ['p', 'u', 'x'];
132
+ /** From `pg_constraint`, whose key arrays keep each column paired with the one it references. */
135
133
  getForeignKeysQuery(_tableName) {
134
+ const columnsOf = (keys, table) => /*sql*/ `ARRAY_TO_JSON(ARRAY(
135
+ SELECT a.attname FROM UNNEST(${keys}) WITH ORDINALITY AS k(attnum, n)
136
+ JOIN pg_attribute a ON a.attrelid = ${table} AND a.attnum = k.attnum
137
+ ORDER BY k.n
138
+ ))`;
136
139
  return /*sql*/ `
137
140
  SELECT
138
- tc.constraint_name,
139
- ARRAY_TO_JSON(ARRAY_AGG(kcu.column_name ORDER BY kcu.ordinal_position)) AS columns,
140
- ccu.table_name AS referenced_table,
141
- ARRAY_TO_JSON(ARRAY_AGG(ccu.column_name ORDER BY kcu.ordinal_position)) AS referenced_columns,
142
- rc.delete_rule,
143
- rc.update_rule
144
- FROM information_schema.table_constraints tc
145
- JOIN information_schema.key_column_usage kcu
146
- ON tc.constraint_name = kcu.constraint_name
147
- AND tc.table_schema = kcu.table_schema
148
- JOIN information_schema.constraint_column_usage ccu
149
- ON ccu.constraint_name = tc.constraint_name
150
- AND ccu.table_schema = tc.table_schema
151
- JOIN information_schema.referential_constraints rc
152
- ON rc.constraint_name = tc.constraint_name
153
- AND rc.constraint_schema = tc.table_schema
154
- WHERE tc.constraint_type = 'FOREIGN KEY'
155
- AND tc.table_name = $1
156
- AND tc.table_schema = ${this.schemaExpr}
157
- GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
158
- ORDER BY tc.constraint_name
141
+ con.conname AS constraint_name,
142
+ ${columnsOf('con.conkey', 'con.conrelid')} AS columns,
143
+ ref.relname AS referenced_table,
144
+ ${columnsOf('con.confkey', 'con.confrelid')} AS referenced_columns,
145
+ con.confdeltype AS delete_rule,
146
+ con.confupdtype AS update_rule
147
+ FROM pg_constraint con
148
+ JOIN pg_class t ON t.oid = con.conrelid
149
+ JOIN pg_namespace n ON n.oid = t.relnamespace
150
+ JOIN pg_class ref ON ref.oid = con.confrelid
151
+ WHERE con.contype = 'f'
152
+ AND t.relname = $1
153
+ AND n.nspname = ${this.schemaExpr}
154
+ ORDER BY con.conname
159
155
  `;
160
156
  }
161
157
  getPrimaryKeyQuery(_tableName) {
@@ -205,8 +201,8 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
205
201
  name: row.constraint_name,
206
202
  columns: row.columns,
207
203
  references: { table: row.referenced_table, columns: row.referenced_columns },
208
- onDelete: this.normalizeReferentialAction(row.delete_rule),
209
- onUpdate: this.normalizeReferentialAction(row.update_rule),
204
+ onDelete: FOREIGN_KEY_ACTION_CODES[row.delete_rule],
205
+ onUpdate: FOREIGN_KEY_ACTION_CODES[row.update_rule],
210
206
  }));
211
207
  }
212
208
  normalizeType(dataType, udtName) {
@@ -219,29 +215,32 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
219
215
  }
220
216
  return dataType.toUpperCase();
221
217
  }
218
+ /**
219
+ * Postgres quotes a negative number (`'-3'::integer`); CockroachDB parenthesizes one (`(-3)`) and
220
+ * writes a quote-bearing string in escape syntax (`e'it\'s'`). Any other cast is dropped, and a
221
+ * function call (`now()`, `nextval(...)`) is returned as written.
222
+ */
222
223
  parseDefaultValue(defaultValue) {
223
224
  if (!defaultValue) {
224
225
  return undefined;
225
226
  }
226
- // Remove type casting (e.g., ::text, ::character varying, ::text[])
227
227
  const cleaned = defaultValue.replace(/::[a-z_]+(\s+[a-z_]+)?(\[\])?/gi, '').trim();
228
- if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
229
- return cleaned.slice(1, -1);
228
+ const number = NUMBER_DEFAULT.exec(cleaned) ?? QUOTED_NUMBER_DEFAULT.exec(defaultValue);
229
+ if (number) {
230
+ return Number(number[1]);
230
231
  }
231
- if (cleaned === 'true' || cleaned === 'false') {
232
- return cleaned === 'true';
232
+ const quoted = /^'(.*)'$/s.exec(cleaned);
233
+ if (quoted) {
234
+ return quoted[1].replaceAll("''", "'");
233
235
  }
234
- if (cleaned === 'NULL') {
235
- return null;
236
+ const escaped = /^e'(.*)'$/s.exec(cleaned);
237
+ if (escaped) {
238
+ return escaped[1].replace(/\\(.)/gs, '$1');
236
239
  }
237
- if (/^-?\d+$/.test(cleaned)) {
238
- return Number.parseInt(cleaned, 10);
239
- }
240
- if (/^-?\d+\.\d+$/.test(cleaned)) {
241
- return Number.parseFloat(cleaned);
240
+ if (cleaned === 'true' || cleaned === 'false') {
241
+ return cleaned === 'true';
242
242
  }
243
- // Return cleaned value for functions like CURRENT_TIMESTAMP, nextval(), etc.
244
- return cleaned;
243
+ return cleaned === 'NULL' ? null : cleaned;
245
244
  }
246
245
  isAutoIncrement(columnDefault, isIdentity) {
247
246
  // PostgreSQL identity columns (GENERATED ... AS IDENTITY)
@@ -252,6 +251,16 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
252
251
  return columnDefault?.includes('nextval(') ?? false;
253
252
  }
254
253
  }
254
+ /** `pg_constraint`'s one-letter spelling of each action. */
255
+ const FOREIGN_KEY_ACTION_CODES = {
256
+ a: 'NO ACTION',
257
+ r: 'RESTRICT',
258
+ c: 'CASCADE',
259
+ n: 'SET NULL',
260
+ d: 'SET DEFAULT',
261
+ };
262
+ const NUMBER_DEFAULT = /^\(?(-?\d+(?:\.\d+)?)\)?$/;
263
+ const QUOTED_NUMBER_DEFAULT = /^'(-?\d+(?:\.\d+)?)'::(?:smallint|integer|bigint|numeric|real|double precision)$/;
255
264
  /**
256
265
  * Postgres states every entry in full: a plain column still reports `order: 'asc'`, and only a
257
266
  * non-default operator class is named. The diff defaults the entity side to match, so an option
@@ -6,7 +6,7 @@ import { AbstractSqlSchemaIntrospector, type TableRowReader } from './abstractSq
6
6
  export declare class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
7
7
  protected getTableNamesQuery(): string;
8
8
  protected tableExistsQuery(): string;
9
- protected parseTableExistsResult(results: SqliteCountRow[]): boolean;
9
+ protected parseTableExistsResult([row]: SqliteCountRow[]): boolean;
10
10
  /**
11
11
  * `table_xinfo`, not `table_info`: the latter omits generated columns entirely, so a table carrying
12
12
  * one read back without it and every sync offered to add a column that was already there - which
@@ -21,9 +21,8 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
21
21
  AND name = ?
22
22
  `;
23
23
  }
24
- parseTableExistsResult(results) {
25
- // No row, or no count in it, reads as `NaN`, which is not above zero.
26
- return Number(results[0]?.count) > 0;
24
+ parseTableExistsResult([row]) {
25
+ return Number(row.count) > 0;
27
26
  }
28
27
  /**
29
28
  * `table_xinfo`, not `table_info`: the latter omits generated columns entirely, so a table carrying
@@ -59,15 +58,16 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
59
58
  return row.name;
60
59
  }
61
60
  async mapColumnsResult(read, tableName, results) {
62
- // Get unique columns from indexes
63
61
  const uniqueColumns = await this.getUniqueColumns(read, tableName);
62
+ // Only a sole `INTEGER PRIMARY KEY` is the rowid, which is what numbers itself.
63
+ const soleKey = results.filter((row) => row.pk > 0).length === 1;
64
64
  return results.map((row) => ({
65
65
  name: row.name,
66
66
  type: this.normalizeType(row.type),
67
67
  nullable: row.notnull === 0,
68
68
  defaultValue: this.parseDefaultValue(row.dflt_value),
69
69
  isPrimaryKey: row.pk > 0,
70
- isAutoIncrement: row.pk > 0 && row.type.toUpperCase() === 'INTEGER',
70
+ isAutoIncrement: soleKey && row.pk > 0 && row.type.toUpperCase() === 'INTEGER',
71
71
  isUnique: uniqueColumns.has(row.name),
72
72
  length: this.extractLength(row.type),
73
73
  precision: undefined,
@@ -130,8 +130,9 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
130
130
  async getUniqueColumns(read, tableName) {
131
131
  const indexes = await read(this.getIndexesQuery(tableName));
132
132
  const uniqueColumns = new Set();
133
+ // The key's own index is left out: a key column is unique already, which the entity side never states.
133
134
  for (const index of indexes) {
134
- if (index.unique) {
135
+ if (index.unique && index.origin !== 'pk') {
135
136
  const columns = await this.getIndexColumns(read, index.name);
136
137
  // Only single-column unique constraints, and only over a real column (not an expression)
137
138
  const [column] = columns;
@@ -165,14 +166,11 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
165
166
  return defaultValue;
166
167
  }
167
168
  if (/^'.*'$/.test(defaultValue)) {
168
- return defaultValue.slice(1, -1);
169
+ return defaultValue.slice(1, -1).replaceAll("''", "'");
169
170
  }
170
171
  if (/^-?\d+$/.test(defaultValue)) {
171
172
  return Number.parseInt(defaultValue, 10);
172
173
  }
173
- if (typeof defaultValue !== 'string') {
174
- return defaultValue;
175
- }
176
174
  if (/^-?\d+\.\d+$/.test(defaultValue)) {
177
175
  return Number.parseFloat(defaultValue);
178
176
  }
@@ -46,13 +46,7 @@ export declare class Migrator {
46
46
  to?: string;
47
47
  step?: number;
48
48
  }): Promise<MigrationResult[]>;
49
- /**
50
- * Narrow a run list by `to`/`step` and execute it, stopping at the first failure.
51
- *
52
- * Both directions do exactly this and differ only in the list they start from: `up` takes the
53
- * pending migrations, `down` the executed ones reversed. Keeping the selection in one place is what
54
- * makes `--to` and `--step` mean the same thing whichever way you are going.
55
- */
49
+ /** Runs the list narrowed by `to`/`step`, stopping at the first failure: `up` over the pending, `down` over the executed reversed. */
56
50
  private runInOrder;
57
51
  /**
58
52
  * Run a single migration, within a transaction where the dialect has one for it
@@ -73,29 +67,13 @@ export declare class Migrator {
73
67
  */
74
68
  getDiffs(): Promise<SchemaDiff[]>;
75
69
  /**
76
- * One AST spanning every schema the entities claim, each table stamped with the schema it was read
77
- * from. Read per schema rather than all at once, so a table comes back keyed exactly as the entity
78
- * that wants it spells the key: `undefined` on both sides for the connection's default, a name on
79
- * both sides otherwise. Ordinarily that is one schema and one pass, as before, and that pass keeps
80
- * {@link schemaIntrospector} so a caller that replaced it still wins.
81
- */
82
- private introspectClaimedSchemas;
83
- /**
84
- * Applies the entity schema to the database: every registered entity, or the one `entity` names.
85
- *
86
- * The whole surface is this and {@link planSync}, which answers the same question without running
87
- * it - `force` and a single entity included, so `--dry-run` means the same thing whatever else was
88
- * asked for.
70
+ * The tables `entities` name, read a schema at a time so each is keyed as its entity spells it. Those
71
+ * alone: nothing else is diffed, and another table can be dropped mid-scan by whatever else is running.
89
72
  */
73
+ private introspectEntities;
74
+ /** Applies the entity schema: every entity, or the one `entity` names. {@link planSync} answers the same without running it. */
90
75
  sync(options?: SyncOptions): Promise<void>;
91
- /**
92
- * Every table dropped and recreated.
93
- *
94
- * Both directions span the whole entity set rather than looping an entity at a time. A per-entity
95
- * AST cannot resolve a cross-entity foreign key, so the old create loop silently produced a schema
96
- * with no referential integrity; and the old drop loop went in reverse *declaration* order, which
97
- * says nothing about the relation graph and is rejected as soon as the constraints are really there.
98
- */
76
+ /** Every table dropped and recreated, the whole entity set at once, so foreign keys resolve and drop in graph order. */
99
77
  private forceStatements;
100
78
  /**
101
79
  * The DDL for one entity: {@link planSync} narrowed to the table it names. A new table costs one
@@ -114,14 +92,7 @@ export declare class Migrator {
114
92
  * statements rather than a summary of a second, differently-computed diff.
115
93
  */
116
94
  planSync(options?: SyncOptions): Promise<string[]>;
117
- /**
118
- * New tables are emitted together, never one at a time: a single-entity AST has no other table for a
119
- * relation to resolve against, so every cross-entity foreign key was dropped and generated schemas
120
- * carried none. Spanning the graph is also what lets a cyclic relation (any `createdBy`
121
- * back-reference) be created at all.
122
- *
123
- * Empty in, empty out, so a diff with no new tables does not build an AST for the whole graph.
124
- */
95
+ /** The new tables, created together so a foreign key between them, cyclic included, resolves. Empty in, empty out. */
125
96
  private createSchema;
126
97
  /**
127
98
  * The pending diffs a sync or a generated migration acts on: the tables to create and the tables to
@@ -184,22 +155,7 @@ export interface BuilderMigrationDefinition<Q extends Querier = SqlQuerier> {
184
155
  down(builder: IMigrationBuilder, querier: Q): Promise<void>;
185
156
  }
186
157
  /**
187
- * Define a migration using the type-safe builder API.
188
- *
189
- * @example
190
- * ```ts
191
- * export default defineBuilderMigration({
192
- * async up(m) {
193
- * await m.createTable('users', (t) => {
194
- * t.id();
195
- * t.string('email', { length: 255 }).unique();
196
- * t.timestamps();
197
- * });
198
- * },
199
- * async down(m) {
200
- * await m.dropTable('users');
201
- * }
202
- * });
203
- * ```
158
+ * Defines a migration with the builder:
159
+ * `defineBuilderMigration({ up: (m) => m.createTable('users', (t) => t.id()), down: (m) => m.dropTable('users') })`.
204
160
  */
205
161
  export declare function defineBuilderMigration<Q extends Querier = SqlQuerier>(migration: BuilderMigrationDefinition<Q>): MigrationDefinition<Q>;
@@ -2,7 +2,7 @@ import { mkdir, readdir, writeFile } from 'node:fs/promises';
2
2
  import { basename, extname, join } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { getEntities, getMeta } from '../entity/index.js';
5
- import { introspectSchema, SchemaAST } from '../schema/index.js';
5
+ import { SchemaAST } from '../schema/index.js';
6
6
  import { LoggerWrapper } from '../util/index.js';
7
7
  import { buildMigrationModule } from './codegen/migrationFile.js';
8
8
  import { introspectorFor } from './introspection/registry.js';
@@ -88,13 +88,7 @@ export class Migrator {
88
88
  const executedMigrations = migrations.filter((m) => executedSet.has(m.name)).reverse(); // Rollback in reverse order
89
89
  return this.runInOrder(executedMigrations, 'down', options);
90
90
  }
91
- /**
92
- * Narrow a run list by `to`/`step` and execute it, stopping at the first failure.
93
- *
94
- * Both directions do exactly this and differ only in the list they start from: `up` takes the
95
- * pending migrations, `down` the executed ones reversed. Keeping the selection in one place is what
96
- * makes `--to` and `--step` mean the same thing whichever way you are going.
97
- */
91
+ /** Runs the list narrowed by `to`/`step`, stopping at the first failure: `up` over the pending, `down` over the executed reversed. */
98
92
  async runInOrder(migrations, direction, options) {
99
93
  let selected = migrations;
100
94
  if (options.to) {
@@ -211,7 +205,7 @@ export class Migrator {
211
205
  */
212
206
  async getDiffs() {
213
207
  const generator = await this.getSchemaGenerator();
214
- const ast = await this.introspectClaimedSchemas();
208
+ const ast = await this.introspectEntities(this.entities);
215
209
  // Both sides built once: the database's above, the entities' here. Left to `diffSchema`, each
216
210
  // entity would rebuild the whole AST, which is quadratic in the number of entities. Absent on a
217
211
  // generator that compares no schema of its own - MongoDB, which reads only indexes.
@@ -223,29 +217,22 @@ export class Migrator {
223
217
  });
224
218
  }
225
219
  /**
226
- * One AST spanning every schema the entities claim, each table stamped with the schema it was read
227
- * from. Read per schema rather than all at once, so a table comes back keyed exactly as the entity
228
- * that wants it spells the key: `undefined` on both sides for the connection's default, a name on
229
- * both sides otherwise. Ordinarily that is one schema and one pass, as before, and that pass keeps
230
- * {@link schemaIntrospector} so a caller that replaced it still wins.
220
+ * The tables `entities` name, read a schema at a time so each is keyed as its entity spells it. Those
221
+ * alone: nothing else is diffed, and another table can be dropped mid-scan by whatever else is running.
231
222
  */
232
- async introspectClaimedSchemas() {
233
- const claimed = new Set(this.entities.map((entity) => this.pool.dialect.resolveSchema(getMeta(entity))));
223
+ async introspectEntities(entities) {
224
+ const { dialect } = this.pool;
225
+ const bySchema = Map.groupBy(new Set(entities), (entity) => dialect.resolveSchema(getMeta(entity)));
234
226
  const merged = new SchemaAST();
235
- for (const schema of claimed) {
236
- for (const table of (await introspectSchema(this.schemaIntrospectorFor(schema))).getTables()) {
227
+ for (const [schema, members] of bySchema) {
228
+ const tables = members.map((entity) => dialect.resolveTableAlias(getMeta(entity)));
229
+ for (const table of (await this.schemaIntrospectorFor(schema).introspect(tables)).getTables()) {
237
230
  merged.addTable(table);
238
231
  }
239
232
  }
240
233
  return merged;
241
234
  }
242
- /**
243
- * Applies the entity schema to the database: every registered entity, or the one `entity` names.
244
- *
245
- * The whole surface is this and {@link planSync}, which answers the same question without running
246
- * it - `force` and a single entity included, so `--dry-run` means the same thing whatever else was
247
- * asked for.
248
- */
235
+ /** Applies the entity schema: every entity, or the one `entity` names. {@link planSync} answers the same without running it. */
249
236
  async sync(options = {}) {
250
237
  const statements = await this.planSync(options);
251
238
  if (statements.length) {
@@ -255,14 +242,7 @@ export class Migrator {
255
242
  this.logger.logSchema('Schema is already in sync.');
256
243
  }
257
244
  }
258
- /**
259
- * Every table dropped and recreated.
260
- *
261
- * Both directions span the whole entity set rather than looping an entity at a time. A per-entity
262
- * AST cannot resolve a cross-entity foreign key, so the old create loop silently produced a schema
263
- * with no referential integrity; and the old drop loop went in reverse *declaration* order, which
264
- * says nothing about the relation graph and is rejected as soon as the constraints are really there.
265
- */
245
+ /** Every table dropped and recreated, the whole entity set at once, so foreign keys resolve and drop in graph order. */
266
246
  forceStatements(generator) {
267
247
  return [
268
248
  ...generator.generateDropSchema(this.entities, { ifExists: true, cascade: true }),
@@ -276,14 +256,17 @@ export class Migrator {
276
256
  */
277
257
  async planEntity(generator, entity, options) {
278
258
  const meta = getMeta(entity);
279
- const introspector = this.schemaIntrospectorFor(this.pool.dialect.resolveSchema(meta));
259
+ const { dialect } = this.pool;
260
+ const introspector = this.schemaIntrospectorFor(dialect.resolveSchema(meta));
280
261
  const tableName = generator.resolveTableName(meta);
281
- return (await introspector.tableExists(tableName))
282
- ? this.alterFromEntity(generator, entity, (await introspectSchema(introspector)).getTable(tableName), options)
283
- : // Spanning the whole set, so a foreign key resolves against the tables it points at, and
284
- // always including this entity: pinned to an explicit `entities` list, a sync of one outside
285
- // it emitted nothing at all. `only` is what keeps the statements to this table.
286
- generator.generateCreateSchema(this.entitiesWith(entity), { only: [tableName], ifNotExists: true });
262
+ if (!(await introspector.tableExists(dialect.resolveTableAlias(meta)))) {
263
+ // Spanning the whole set, so a foreign key resolves against the tables it points at, and always
264
+ // including this entity: `only` is what keeps the statements to this table.
265
+ return generator.generateCreateSchema(this.entitiesWith(entity), { only: [tableName], ifNotExists: true });
266
+ }
267
+ // With the tables it references, which its foreign keys resolve against.
268
+ const ast = await this.introspectEntities([entity, ...referencedEntities(meta)]);
269
+ return this.alterFromEntity(generator, entity, ast.getTable(tableName), options);
287
270
  }
288
271
  /** The same for one entity against the table it already has, and nothing where the two agree. */
289
272
  alterFromEntity(generator, entity, table, options) {
@@ -319,14 +302,7 @@ export class Migrator {
319
302
  ...altered.flatMap((diff) => generator.generateAlterTable(this.filterDiff(diff, options))),
320
303
  ];
321
304
  }
322
- /**
323
- * New tables are emitted together, never one at a time: a single-entity AST has no other table for a
324
- * relation to resolve against, so every cross-entity foreign key was dropped and generated schemas
325
- * carried none. Spanning the graph is also what lets a cyclic relation (any `createdBy`
326
- * back-reference) be created at all.
327
- *
328
- * Empty in, empty out, so a diff with no new tables does not build an AST for the whole graph.
329
- */
305
+ /** The new tables, created together so a foreign key between them, cyclic included, resolves. Empty in, empty out. */
330
306
  createSchema(generator, tableNames) {
331
307
  return tableNames.length ? generator.generateCreateSchema(this.entities, { only: tableNames }) : [];
332
308
  }
@@ -484,23 +460,8 @@ export function defineMigration(migration) {
484
460
  return migration;
485
461
  }
486
462
  /**
487
- * Define a migration using the type-safe builder API.
488
- *
489
- * @example
490
- * ```ts
491
- * export default defineBuilderMigration({
492
- * async up(m) {
493
- * await m.createTable('users', (t) => {
494
- * t.id();
495
- * t.string('email', { length: 255 }).unique();
496
- * t.timestamps();
497
- * });
498
- * },
499
- * async down(m) {
500
- * await m.dropTable('users');
501
- * }
502
- * });
503
- * ```
463
+ * Defines a migration with the builder:
464
+ * `defineBuilderMigration({ up: (m) => m.createTable('users', (t) => t.id()), down: (m) => m.dropTable('users') })`.
504
465
  */
505
466
  export function defineBuilderMigration(migration) {
506
467
  return {
@@ -509,3 +470,9 @@ export function defineBuilderMigration(migration) {
509
470
  down: async (querier) => migration.down(await migrationBuilderFor(querier), querier),
510
471
  };
511
472
  }
473
+ /** The entities `meta` points at, through a relation or a foreign key field. */
474
+ function referencedEntities(meta) {
475
+ const fields = Object.values(meta.fields).flatMap((field) => field?.references?.() ?? []);
476
+ const relations = Object.values(meta.relations).flatMap((relation) => relation?.entity?.() ?? []);
477
+ return [...fields, ...relations];
478
+ }