@mikro-orm/sql 7.0.13-dev.7 → 7.0.13-dev.9
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.
- package/dialects/postgresql/BasePostgreSqlPlatform.js +12 -3
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +2 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +17 -17
- package/package.json +2 -2
- package/schema/DatabaseSchema.js +8 -4
- package/schema/DatabaseTable.js +120 -28
- package/schema/SchemaHelper.js +1 -1
|
@@ -90,18 +90,27 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
90
90
|
}
|
|
91
91
|
normalizeColumnType(type, options) {
|
|
92
92
|
const simpleType = this.extractSimpleType(type);
|
|
93
|
-
if (['int', 'int4', 'integer'].includes(simpleType)) {
|
|
93
|
+
if (['int', 'int4', 'integer', 'serial'].includes(simpleType)) {
|
|
94
94
|
return this.getIntegerTypeDeclarationSQL({});
|
|
95
95
|
}
|
|
96
|
-
if (['bigint', 'int8'].includes(simpleType)) {
|
|
96
|
+
if (['bigint', 'int8', 'bigserial'].includes(simpleType)) {
|
|
97
97
|
return this.getBigIntTypeDeclarationSQL({});
|
|
98
98
|
}
|
|
99
|
-
if (['smallint', 'int2'].includes(simpleType)) {
|
|
99
|
+
if (['smallint', 'int2', 'smallserial'].includes(simpleType)) {
|
|
100
100
|
return this.getSmallIntTypeDeclarationSQL({});
|
|
101
101
|
}
|
|
102
102
|
if (['boolean', 'bool'].includes(simpleType)) {
|
|
103
103
|
return this.getBooleanTypeDeclarationSQL();
|
|
104
104
|
}
|
|
105
|
+
if (['double', 'double precision', 'float8'].includes(simpleType)) {
|
|
106
|
+
return this.getDoubleDeclarationSQL();
|
|
107
|
+
}
|
|
108
|
+
if (['real', 'float4'].includes(simpleType)) {
|
|
109
|
+
return this.getFloatDeclarationSQL();
|
|
110
|
+
}
|
|
111
|
+
if (['timestamptz', 'timestamp with time zone'].includes(simpleType)) {
|
|
112
|
+
return this.getDateTimeTypeDeclarationSQL(options);
|
|
113
|
+
}
|
|
105
114
|
if (['varchar', 'character varying'].includes(simpleType)) {
|
|
106
115
|
return this.getVarcharTypeDeclarationSQL(options);
|
|
107
116
|
}
|
|
@@ -15,6 +15,7 @@ export declare class SqlitePlatform extends AbstractSqlPlatform {
|
|
|
15
15
|
getDateTimeTypeDeclarationSQL(column: {
|
|
16
16
|
length: number;
|
|
17
17
|
}): string;
|
|
18
|
+
getDefaultVersionLength(): number;
|
|
18
19
|
getBeginTransactionSQL(options?: {
|
|
19
20
|
isolationLevel?: IsolationLevel;
|
|
20
21
|
readOnly?: boolean;
|
|
@@ -24,6 +24,10 @@ export class SqlitePlatform extends AbstractSqlPlatform {
|
|
|
24
24
|
getDateTimeTypeDeclarationSQL(column) {
|
|
25
25
|
return 'datetime';
|
|
26
26
|
}
|
|
27
|
+
// sqlite's datetime DDL drops precision and the current-ts expression hardcodes ms scaling
|
|
28
|
+
getDefaultVersionLength() {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
27
31
|
getBeginTransactionSQL(options) {
|
|
28
32
|
return ['begin'];
|
|
29
33
|
}
|
|
@@ -44,7 +44,8 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
|
|
|
44
44
|
* We need to add them back so they match what we generate in DDL.
|
|
45
45
|
*/
|
|
46
46
|
private wrapExpressionDefault;
|
|
47
|
-
|
|
47
|
+
/** Extract enum values from `IN (…)` CHECKs only — a `!= 'x'` check would otherwise be misread as a one-item enum. */
|
|
48
|
+
private extractEnumValuesFromChecks;
|
|
48
49
|
getPrimaryKeys(connection: AbstractSqlConnection, indexes: IndexDef[], tableName: string, schemaName?: string, ctx?: Transaction): Promise<string[]>;
|
|
49
50
|
private getIndexes;
|
|
50
51
|
private getChecks;
|
|
@@ -100,7 +100,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
100
100
|
const checks = await this.getChecks(connection, table.name, table.schema, ctx);
|
|
101
101
|
const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema, ctx);
|
|
102
102
|
const fks = await this.getForeignKeys(connection, table.name, table.schema, ctx);
|
|
103
|
-
const enums =
|
|
103
|
+
const enums = this.extractEnumValuesFromChecks(checks);
|
|
104
104
|
table.init(cols, indexes, checks, pks, fks, enums);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -321,23 +321,23 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
321
321
|
// everything else is an expression that had its outer parens stripped
|
|
322
322
|
return `(${value})`;
|
|
323
323
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// check constraints are defined as (note that last closing paren is missing):
|
|
331
|
-
// `type` text check (`type` in ('local', 'global')
|
|
332
|
-
const match = /[`["']([^`\]"']+)[`\]"'] text check \(.* \((.*)\)/i.exec(item);
|
|
333
|
-
/* v8 ignore next */
|
|
334
|
-
if (match) {
|
|
335
|
-
o[match[1]] = match[2]
|
|
336
|
-
.split(/,(?=\s*'(?:[^']|'')*'(?:\s*\)|$))/)
|
|
337
|
-
.map((item) => /^\(?'((?:[^']|'')*)'/.exec(item.trim())[1].replace(/''/g, "'"));
|
|
324
|
+
/** Extract enum values from `IN (…)` CHECKs only — a `!= 'x'` check would otherwise be misread as a one-item enum. */
|
|
325
|
+
extractEnumValuesFromChecks(checks) {
|
|
326
|
+
const result = {};
|
|
327
|
+
for (const check of checks) {
|
|
328
|
+
if (!check.columnName || typeof check.expression !== 'string') {
|
|
329
|
+
continue;
|
|
338
330
|
}
|
|
339
|
-
|
|
340
|
-
|
|
331
|
+
const inClause = /\bin\s*\(([^)]*)\)/i.exec(check.expression);
|
|
332
|
+
if (!inClause) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const items = [...inClause[1].matchAll(/'((?:[^']|'')*)'/g)].map(m => m[1].replace(/''/g, "'"));
|
|
336
|
+
if (items.length > 0) {
|
|
337
|
+
result[check.columnName] = items;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return result;
|
|
341
341
|
}
|
|
342
342
|
async getPrimaryKeys(connection, indexes, tableName, schemaName, ctx) {
|
|
343
343
|
const prefix = this.getSchemaPrefix(schemaName);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.0.13-dev.
|
|
3
|
+
"version": "7.0.13-dev.9",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@mikro-orm/core": "^7.0.12"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.0.13-dev.
|
|
56
|
+
"@mikro-orm/core": "7.0.13-dev.9"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -323,12 +323,16 @@ export class DatabaseSchema {
|
|
|
323
323
|
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner));
|
|
324
324
|
}
|
|
325
325
|
toJSON() {
|
|
326
|
+
// locale-independent comparison so the snapshot is stable across machines
|
|
327
|
+
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
328
|
+
const tableKey = (t) => `${t.schema ?? ''}.${t.name}`;
|
|
329
|
+
const byTable = (a, b) => byString(tableKey(a), tableKey(b));
|
|
326
330
|
return {
|
|
327
331
|
name: this.name,
|
|
328
|
-
namespaces: [...this.#namespaces],
|
|
329
|
-
tables: this.#tables,
|
|
330
|
-
views: this.#views,
|
|
331
|
-
nativeEnums: this.#nativeEnums,
|
|
332
|
+
namespaces: [...this.#namespaces].sort(),
|
|
333
|
+
tables: [...this.#tables].sort(byTable),
|
|
334
|
+
views: [...this.#views].sort(byTable),
|
|
335
|
+
nativeEnums: Object.fromEntries(Object.entries(this.#nativeEnums).sort(([a], [b]) => byString(a, b))),
|
|
332
336
|
};
|
|
333
337
|
}
|
|
334
338
|
prune(schema, wildcardSchemaTables) {
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -51,6 +51,7 @@ export class DatabaseTable {
|
|
|
51
51
|
this.#indexes = indexes;
|
|
52
52
|
this.#checks = checks;
|
|
53
53
|
this.#foreignKeys = fks;
|
|
54
|
+
const helper = this.#platform.getSchemaHelper();
|
|
54
55
|
this.#columns = cols.reduce((o, v) => {
|
|
55
56
|
const index = indexes.filter(i => i.columnNames[0] === v.name);
|
|
56
57
|
v.primary = v.primary || pks.includes(v.name);
|
|
@@ -59,6 +60,11 @@ export class DatabaseTable {
|
|
|
59
60
|
v.mappedType = this.#platform.getMappedType(type);
|
|
60
61
|
v.default = v.default?.toString().startsWith('nextval(') ? null : v.default;
|
|
61
62
|
v.enumItems ??= enums[v.name] || [];
|
|
63
|
+
// recover length from the declared type so introspection matches `addColumnFromProperty`;
|
|
64
|
+
// scoped to types with `getDefaultLength` to skip mysql's `tinyint(1)` boolean width
|
|
65
|
+
if (v.length == null && v.type && helper && typeof v.mappedType.getDefaultLength !== 'undefined') {
|
|
66
|
+
v.length = helper.inferLengthFromColumnType(v.type);
|
|
67
|
+
}
|
|
62
68
|
o[v.name] = v;
|
|
63
69
|
return o;
|
|
64
70
|
}, {});
|
|
@@ -68,7 +74,9 @@ export class DatabaseTable {
|
|
|
68
74
|
}
|
|
69
75
|
addColumnFromProperty(prop, meta, config) {
|
|
70
76
|
prop.fieldNames?.forEach((field, idx) => {
|
|
71
|
-
|
|
77
|
+
// numeric enums fall through to the underlying numeric type — no platform emits a CHECK we could parse back
|
|
78
|
+
const isStringEnum = !!prop.nativeEnumName || !!prop.items?.every(item => typeof item === 'string');
|
|
79
|
+
const type = prop.enum && isStringEnum ? 'enum' : prop.columnTypes[idx];
|
|
72
80
|
const mappedType = this.#platform.getMappedType(type);
|
|
73
81
|
if (mappedType instanceof DecimalType) {
|
|
74
82
|
const match = /\w+\((\d+), ?(\d+)\)/.exec(prop.columnTypes[idx]);
|
|
@@ -887,51 +895,135 @@ export class DatabaseTable {
|
|
|
887
895
|
}
|
|
888
896
|
toJSON() {
|
|
889
897
|
const columns = this.#columns;
|
|
890
|
-
|
|
898
|
+
// locale-independent comparison so the snapshot is stable across machines
|
|
899
|
+
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
900
|
+
const sortedColumnKeys = Utils.keys(columns).sort(byString);
|
|
901
|
+
// mirror `DatabaseTable.init()`: derive `primary`/`unique` from index membership
|
|
902
|
+
// so metadata (which keeps `primary: false` on composite PK columns) and introspection agree
|
|
903
|
+
const primaryColumns = new Set();
|
|
904
|
+
const uniqueColumns = new Set();
|
|
905
|
+
for (const idx of this.#indexes) {
|
|
906
|
+
if (idx.primary) {
|
|
907
|
+
idx.columnNames.forEach(c => primaryColumns.add(c));
|
|
908
|
+
}
|
|
909
|
+
if (idx.unique && !idx.primary && idx.columnNames.length > 0) {
|
|
910
|
+
uniqueColumns.add(idx.columnNames[0]);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
// integer/float widths live in the type name (`int2`/`int4`/`float8`) — drop redundant precision/scale
|
|
914
|
+
const isFixedPrecisionFamily = (mappedType) => mappedType instanceof t.integer ||
|
|
915
|
+
mappedType instanceof t.smallint ||
|
|
916
|
+
mappedType instanceof t.tinyint ||
|
|
917
|
+
mappedType instanceof t.mediumint ||
|
|
918
|
+
mappedType instanceof t.bigint ||
|
|
919
|
+
mappedType instanceof t.float ||
|
|
920
|
+
mappedType instanceof t.double;
|
|
921
|
+
const supportsUnsigned = this.#platform.supportsUnsigned();
|
|
922
|
+
const columnsMapped = sortedColumnKeys.reduce((o, col) => {
|
|
891
923
|
const c = columns[col];
|
|
924
|
+
// omit `autoincrement` from options so `serial` (metadata) and `int4` (introspection) collapse the same
|
|
925
|
+
const rawType = c.type?.toLowerCase();
|
|
926
|
+
const normOptions = { length: c.length, precision: c.precision, scale: c.scale };
|
|
927
|
+
const type = this.#platform.normalizeColumnType(c.type ?? '', normOptions)?.toLowerCase() || rawType;
|
|
928
|
+
const fixedPrecision = isFixedPrecisionFamily(c.mappedType);
|
|
892
929
|
const normalized = {
|
|
893
930
|
name: c.name,
|
|
894
|
-
type
|
|
895
|
-
unsigned: !!c.unsigned,
|
|
931
|
+
type,
|
|
932
|
+
unsigned: supportsUnsigned && !!c.unsigned,
|
|
896
933
|
autoincrement: !!c.autoincrement,
|
|
897
|
-
primary: !!c.primary,
|
|
934
|
+
primary: primaryColumns.has(c.name) || !!c.primary,
|
|
898
935
|
nullable: !!c.nullable,
|
|
899
|
-
unique: !!c.unique,
|
|
900
|
-
length: c.length
|
|
901
|
-
precision: c.precision ?? null,
|
|
902
|
-
scale: c.scale ?? null,
|
|
936
|
+
unique: uniqueColumns.has(c.name) || !!c.unique,
|
|
937
|
+
length: c.length || null,
|
|
938
|
+
precision: fixedPrecision ? null : (c.precision ?? null),
|
|
939
|
+
scale: fixedPrecision ? null : (c.scale ?? null),
|
|
903
940
|
default: c.default ?? null,
|
|
904
941
|
comment: c.comment ?? null,
|
|
905
942
|
enumItems: c.enumItems ?? [],
|
|
906
943
|
mappedType: Utils.keys(t).find(k => t[k] === c.mappedType.constructor),
|
|
907
944
|
};
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
normalized.ignoreSchemaChanges = c.ignoreSchemaChanges;
|
|
919
|
-
}
|
|
920
|
-
if (c.defaultConstraint) {
|
|
921
|
-
normalized.defaultConstraint = c.defaultConstraint;
|
|
945
|
+
for (const field of [
|
|
946
|
+
'generated',
|
|
947
|
+
'nativeEnumName',
|
|
948
|
+
'extra',
|
|
949
|
+
'ignoreSchemaChanges',
|
|
950
|
+
'defaultConstraint',
|
|
951
|
+
]) {
|
|
952
|
+
if (c[field]) {
|
|
953
|
+
normalized[field] = c[field];
|
|
954
|
+
}
|
|
922
955
|
}
|
|
923
956
|
o[col] = normalized;
|
|
924
957
|
return o;
|
|
925
958
|
}, {});
|
|
959
|
+
const normalizeIndex = (idx) => {
|
|
960
|
+
const out = {
|
|
961
|
+
columnNames: idx.columnNames,
|
|
962
|
+
composite: !!idx.composite,
|
|
963
|
+
// PK indexes are always backed by a constraint — force it so postgres introspection matches
|
|
964
|
+
constraint: !!idx.constraint || !!idx.primary,
|
|
965
|
+
keyName: idx.keyName,
|
|
966
|
+
primary: !!idx.primary,
|
|
967
|
+
unique: !!idx.unique,
|
|
968
|
+
};
|
|
969
|
+
const optional = [
|
|
970
|
+
'expression',
|
|
971
|
+
'type',
|
|
972
|
+
'deferMode',
|
|
973
|
+
'columns',
|
|
974
|
+
'include',
|
|
975
|
+
'fillFactor',
|
|
976
|
+
'invisible',
|
|
977
|
+
'disabled',
|
|
978
|
+
'clustered',
|
|
979
|
+
];
|
|
980
|
+
for (const field of optional) {
|
|
981
|
+
if (idx[field] != null && idx[field] !== false) {
|
|
982
|
+
out[field] = idx[field];
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return out;
|
|
986
|
+
};
|
|
987
|
+
const normalizeFk = (fk) => {
|
|
988
|
+
const isNoAction = (rule) => !rule || rule.toLowerCase() === 'no action';
|
|
989
|
+
// JSON.stringify drops undefined properties — let them through instead of guarding
|
|
990
|
+
return {
|
|
991
|
+
columnNames: fk.columnNames,
|
|
992
|
+
constraintName: fk.constraintName,
|
|
993
|
+
localTableName: fk.localTableName,
|
|
994
|
+
referencedColumnNames: fk.referencedColumnNames,
|
|
995
|
+
referencedTableName: fk.referencedTableName,
|
|
996
|
+
updateRule: isNoAction(fk.updateRule) ? undefined : fk.updateRule,
|
|
997
|
+
deleteRule: isNoAction(fk.deleteRule) ? undefined : fk.deleteRule,
|
|
998
|
+
deferMode: fk.deferMode,
|
|
999
|
+
};
|
|
1000
|
+
};
|
|
1001
|
+
const normalizeCheck = (check) => {
|
|
1002
|
+
const out = { name: check.name };
|
|
1003
|
+
if (typeof check.expression === 'string') {
|
|
1004
|
+
out.expression = check.expression;
|
|
1005
|
+
}
|
|
1006
|
+
for (const field of ['definition', 'columnName']) {
|
|
1007
|
+
if (check[field]) {
|
|
1008
|
+
out[field] = check[field];
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return out;
|
|
1012
|
+
};
|
|
1013
|
+
const sortedIndexes = [...this.#indexes].sort((a, b) => byString(a.keyName, b.keyName)).map(normalizeIndex);
|
|
1014
|
+
const sortedChecks = [...this.#checks].sort((a, b) => byString(a.name, b.name)).map(normalizeCheck);
|
|
1015
|
+
const sortedForeignKeys = Object.fromEntries(Object.entries(this.#foreignKeys)
|
|
1016
|
+
.sort(([a], [b]) => byString(a, b))
|
|
1017
|
+
.map(([k, v]) => [k, normalizeFk(v)]));
|
|
926
1018
|
return {
|
|
927
1019
|
name: this.name,
|
|
928
1020
|
schema: this.schema,
|
|
929
1021
|
columns: columnsMapped,
|
|
930
|
-
indexes:
|
|
931
|
-
checks:
|
|
932
|
-
foreignKeys:
|
|
933
|
-
|
|
934
|
-
comment: this.comment,
|
|
1022
|
+
indexes: sortedIndexes,
|
|
1023
|
+
checks: sortedChecks,
|
|
1024
|
+
foreignKeys: sortedForeignKeys,
|
|
1025
|
+
// emit `comment` even when unset so introspection (which always reads it) matches metadata
|
|
1026
|
+
comment: this.comment ?? null,
|
|
935
1027
|
};
|
|
936
1028
|
}
|
|
937
1029
|
}
|