@mikro-orm/sql 7.1.0-dev.20 → 7.1.0-dev.21
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.d.ts +1 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +18 -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 +122 -29
- package/schema/SchemaHelper.js +1 -1
|
@@ -47,6 +47,7 @@ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
47
47
|
precision?: number;
|
|
48
48
|
scale?: number;
|
|
49
49
|
autoincrement?: boolean;
|
|
50
|
+
columnTypes?: string[];
|
|
50
51
|
}): string;
|
|
51
52
|
getMappedType(type: string): Type<unknown>;
|
|
52
53
|
getRegExpOperator(val?: unknown, flags?: string): string;
|
|
@@ -93,18 +93,27 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
93
93
|
}
|
|
94
94
|
normalizeColumnType(type, options) {
|
|
95
95
|
const simpleType = this.extractSimpleType(type);
|
|
96
|
-
if (['int', 'int4', 'integer'].includes(simpleType)) {
|
|
96
|
+
if (['int', 'int4', 'integer', 'serial'].includes(simpleType)) {
|
|
97
97
|
return this.getIntegerTypeDeclarationSQL({});
|
|
98
98
|
}
|
|
99
|
-
if (['bigint', 'int8'].includes(simpleType)) {
|
|
99
|
+
if (['bigint', 'int8', 'bigserial'].includes(simpleType)) {
|
|
100
100
|
return this.getBigIntTypeDeclarationSQL({});
|
|
101
101
|
}
|
|
102
|
-
if (['smallint', 'int2'].includes(simpleType)) {
|
|
102
|
+
if (['smallint', 'int2', 'smallserial'].includes(simpleType)) {
|
|
103
103
|
return this.getSmallIntTypeDeclarationSQL({});
|
|
104
104
|
}
|
|
105
105
|
if (['boolean', 'bool'].includes(simpleType)) {
|
|
106
106
|
return this.getBooleanTypeDeclarationSQL();
|
|
107
107
|
}
|
|
108
|
+
if (['double', 'double precision', 'float8'].includes(simpleType)) {
|
|
109
|
+
return this.getDoubleDeclarationSQL();
|
|
110
|
+
}
|
|
111
|
+
if (['real', 'float4'].includes(simpleType)) {
|
|
112
|
+
return this.getFloatDeclarationSQL();
|
|
113
|
+
}
|
|
114
|
+
if (['timestamptz', 'timestamp with time zone'].includes(simpleType)) {
|
|
115
|
+
return this.getDateTimeTypeDeclarationSQL(options);
|
|
116
|
+
}
|
|
108
117
|
if (['varchar', 'character varying'].includes(simpleType)) {
|
|
109
118
|
return this.getVarcharTypeDeclarationSQL(options);
|
|
110
119
|
}
|
|
@@ -117,6 +126,12 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
117
126
|
if (['interval'].includes(simpleType)) {
|
|
118
127
|
return this.getIntervalTypeDeclarationSQL(options);
|
|
119
128
|
}
|
|
129
|
+
// TimeType.getColumnType drops the timezone qualifier, so detect tz aliases from the original column type.
|
|
130
|
+
const originalType = options.columnTypes?.[0]?.toLowerCase() ?? type;
|
|
131
|
+
if (/^timetz\b/.test(originalType) || /^time\s+with\s+time\s+zone\b/.test(originalType)) {
|
|
132
|
+
const length = options.length ?? this.getDefaultDateTimeLength();
|
|
133
|
+
return `timetz(${length})`;
|
|
134
|
+
}
|
|
120
135
|
return super.normalizeColumnType(type, options);
|
|
121
136
|
}
|
|
122
137
|
getMappedType(type) {
|
|
@@ -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
|
}
|
|
@@ -45,7 +45,8 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
|
|
|
45
45
|
* We need to add them back so they match what we generate in DDL.
|
|
46
46
|
*/
|
|
47
47
|
private wrapExpressionDefault;
|
|
48
|
-
|
|
48
|
+
/** Extract enum values from `IN (…)` CHECKs only — a `!= 'x'` check would otherwise be misread as a one-item enum. */
|
|
49
|
+
private extractEnumValuesFromChecks;
|
|
49
50
|
getPrimaryKeys(connection: AbstractSqlConnection, indexes: IndexDef[], tableName: string, schemaName?: string, ctx?: Transaction): Promise<string[]>;
|
|
50
51
|
private getIndexes;
|
|
51
52
|
private getChecks;
|
|
@@ -101,7 +101,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
101
101
|
const checks = await this.getChecks(connection, table.name, table.schema, ctx);
|
|
102
102
|
const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema, ctx);
|
|
103
103
|
const fks = await this.getForeignKeys(connection, table.name, table.schema, ctx);
|
|
104
|
-
const enums =
|
|
104
|
+
const enums = this.extractEnumValuesFromChecks(checks);
|
|
105
105
|
const triggers = await this.getTableTriggers(connection, table.name);
|
|
106
106
|
table.init(cols, indexes, checks, pks, fks, enums);
|
|
107
107
|
table.setTriggers(triggers);
|
|
@@ -327,23 +327,23 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
327
327
|
// everything else is an expression that had its outer parens stripped
|
|
328
328
|
return `(${value})`;
|
|
329
329
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// check constraints are defined as (note that last closing paren is missing):
|
|
337
|
-
// `type` text check (`type` in ('local', 'global')
|
|
338
|
-
const match = /[`["']([^`\]"']+)[`\]"'] text check \(.* \((.*)\)/i.exec(item);
|
|
339
|
-
/* v8 ignore next */
|
|
340
|
-
if (match) {
|
|
341
|
-
o[match[1]] = match[2]
|
|
342
|
-
.split(/,(?=\s*'(?:[^']|'')*'(?:\s*\)|$))/)
|
|
343
|
-
.map((item) => /^\(?'((?:[^']|'')*)'/.exec(item.trim())[1].replace(/''/g, "'"));
|
|
330
|
+
/** Extract enum values from `IN (…)` CHECKs only — a `!= 'x'` check would otherwise be misread as a one-item enum. */
|
|
331
|
+
extractEnumValuesFromChecks(checks) {
|
|
332
|
+
const result = {};
|
|
333
|
+
for (const check of checks) {
|
|
334
|
+
if (!check.columnName || typeof check.expression !== 'string') {
|
|
335
|
+
continue;
|
|
344
336
|
}
|
|
345
|
-
|
|
346
|
-
|
|
337
|
+
const inClause = /\bin\s*\(([^)]*)\)/i.exec(check.expression);
|
|
338
|
+
if (!inClause) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const items = [...inClause[1].matchAll(/'((?:[^']|'')*)'/g)].map(m => m[1].replace(/''/g, "'"));
|
|
342
|
+
if (items.length > 0) {
|
|
343
|
+
result[check.columnName] = items;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return result;
|
|
347
347
|
}
|
|
348
348
|
async getPrimaryKeys(connection, indexes, tableName, schemaName, ctx) {
|
|
349
349
|
const prefix = this.getSchemaPrefix(schemaName);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.0-dev.
|
|
3
|
+
"version": "7.1.0-dev.21",
|
|
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.1.0-dev.
|
|
56
|
+
"@mikro-orm/core": "7.1.0-dev.21"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -341,12 +341,16 @@ export class DatabaseSchema {
|
|
|
341
341
|
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner));
|
|
342
342
|
}
|
|
343
343
|
toJSON() {
|
|
344
|
+
// locale-independent comparison so the snapshot is stable across machines
|
|
345
|
+
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
346
|
+
const tableKey = (t) => `${t.schema ?? ''}.${t.name}`;
|
|
347
|
+
const byTable = (a, b) => byString(tableKey(a), tableKey(b));
|
|
344
348
|
return {
|
|
345
349
|
name: this.name,
|
|
346
|
-
namespaces: [...this.#namespaces],
|
|
347
|
-
tables: this.#tables,
|
|
348
|
-
views: this.#views,
|
|
349
|
-
nativeEnums: this.#nativeEnums,
|
|
350
|
+
namespaces: [...this.#namespaces].sort(),
|
|
351
|
+
tables: [...this.#tables].sort(byTable),
|
|
352
|
+
views: [...this.#views].sort(byTable),
|
|
353
|
+
nativeEnums: Object.fromEntries(Object.entries(this.#nativeEnums).sort(([a], [b]) => byString(a, b))),
|
|
350
354
|
};
|
|
351
355
|
}
|
|
352
356
|
prune(schema, wildcardSchemaTables) {
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -68,6 +68,7 @@ export class DatabaseTable {
|
|
|
68
68
|
this.#indexes = indexes;
|
|
69
69
|
this.#checks = checks;
|
|
70
70
|
this.#foreignKeys = fks;
|
|
71
|
+
const helper = this.#platform.getSchemaHelper();
|
|
71
72
|
this.#columns = cols.reduce((o, v) => {
|
|
72
73
|
const index = indexes.filter(i => i.columnNames[0] === v.name);
|
|
73
74
|
v.primary = v.primary || pks.includes(v.name);
|
|
@@ -76,6 +77,11 @@ export class DatabaseTable {
|
|
|
76
77
|
v.mappedType = this.#platform.getMappedType(type);
|
|
77
78
|
v.default = v.default?.toString().startsWith('nextval(') ? null : v.default;
|
|
78
79
|
v.enumItems ??= enums[v.name] || [];
|
|
80
|
+
// recover length from the declared type so introspection matches `addColumnFromProperty`;
|
|
81
|
+
// scoped to types with `getDefaultLength` to skip mysql's `tinyint(1)` boolean width
|
|
82
|
+
if (v.length == null && v.type && helper && typeof v.mappedType.getDefaultLength !== 'undefined') {
|
|
83
|
+
v.length = helper.inferLengthFromColumnType(v.type);
|
|
84
|
+
}
|
|
79
85
|
o[v.name] = v;
|
|
80
86
|
return o;
|
|
81
87
|
}, {});
|
|
@@ -85,7 +91,9 @@ export class DatabaseTable {
|
|
|
85
91
|
}
|
|
86
92
|
addColumnFromProperty(prop, meta, config) {
|
|
87
93
|
prop.fieldNames?.forEach((field, idx) => {
|
|
88
|
-
|
|
94
|
+
// numeric enums fall through to the underlying numeric type — no platform emits a CHECK we could parse back
|
|
95
|
+
const isStringEnum = !!prop.nativeEnumName || !!prop.items?.every(item => typeof item === 'string');
|
|
96
|
+
const type = prop.enum && isStringEnum ? 'enum' : prop.columnTypes[idx];
|
|
89
97
|
const mappedType = this.#platform.getMappedType(type);
|
|
90
98
|
if (mappedType instanceof DecimalType) {
|
|
91
99
|
const match = /\w+\((\d+), ?(\d+)\)/.exec(prop.columnTypes[idx]);
|
|
@@ -933,52 +941,137 @@ export class DatabaseTable {
|
|
|
933
941
|
}
|
|
934
942
|
toJSON() {
|
|
935
943
|
const columns = this.#columns;
|
|
936
|
-
|
|
944
|
+
// locale-independent comparison so the snapshot is stable across machines
|
|
945
|
+
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
946
|
+
const sortedColumnKeys = Utils.keys(columns).sort(byString);
|
|
947
|
+
// mirror `DatabaseTable.init()`: derive `primary`/`unique` from index membership
|
|
948
|
+
// so metadata (which keeps `primary: false` on composite PK columns) and introspection agree
|
|
949
|
+
const primaryColumns = new Set();
|
|
950
|
+
const uniqueColumns = new Set();
|
|
951
|
+
for (const idx of this.#indexes) {
|
|
952
|
+
if (idx.primary) {
|
|
953
|
+
idx.columnNames.forEach(c => primaryColumns.add(c));
|
|
954
|
+
}
|
|
955
|
+
if (idx.unique && !idx.primary && idx.columnNames.length > 0) {
|
|
956
|
+
uniqueColumns.add(idx.columnNames[0]);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
// integer/float widths live in the type name (`int2`/`int4`/`float8`) — drop redundant precision/scale
|
|
960
|
+
const isFixedPrecisionFamily = (mappedType) => mappedType instanceof t.integer ||
|
|
961
|
+
mappedType instanceof t.smallint ||
|
|
962
|
+
mappedType instanceof t.tinyint ||
|
|
963
|
+
mappedType instanceof t.mediumint ||
|
|
964
|
+
mappedType instanceof t.bigint ||
|
|
965
|
+
mappedType instanceof t.float ||
|
|
966
|
+
mappedType instanceof t.double;
|
|
967
|
+
const supportsUnsigned = this.#platform.supportsUnsigned();
|
|
968
|
+
const columnsMapped = sortedColumnKeys.reduce((o, col) => {
|
|
937
969
|
const c = columns[col];
|
|
970
|
+
// omit `autoincrement` from options so `serial` (metadata) and `int4` (introspection) collapse the same
|
|
971
|
+
const rawType = c.type?.toLowerCase();
|
|
972
|
+
const normOptions = { length: c.length, precision: c.precision, scale: c.scale };
|
|
973
|
+
const type = this.#platform.normalizeColumnType(c.type ?? '', normOptions)?.toLowerCase() || rawType;
|
|
974
|
+
const fixedPrecision = isFixedPrecisionFamily(c.mappedType);
|
|
938
975
|
const normalized = {
|
|
939
976
|
name: c.name,
|
|
940
|
-
type
|
|
941
|
-
unsigned: !!c.unsigned,
|
|
977
|
+
type,
|
|
978
|
+
unsigned: supportsUnsigned && !!c.unsigned,
|
|
942
979
|
autoincrement: !!c.autoincrement,
|
|
943
|
-
primary: !!c.primary,
|
|
980
|
+
primary: primaryColumns.has(c.name) || !!c.primary,
|
|
944
981
|
nullable: !!c.nullable,
|
|
945
|
-
unique: !!c.unique,
|
|
946
|
-
length: c.length
|
|
947
|
-
precision: c.precision ?? null,
|
|
948
|
-
scale: c.scale ?? null,
|
|
982
|
+
unique: uniqueColumns.has(c.name) || !!c.unique,
|
|
983
|
+
length: c.length || null,
|
|
984
|
+
precision: fixedPrecision ? null : (c.precision ?? null),
|
|
985
|
+
scale: fixedPrecision ? null : (c.scale ?? null),
|
|
949
986
|
default: c.default ?? null,
|
|
950
987
|
comment: c.comment ?? null,
|
|
951
988
|
enumItems: c.enumItems ?? [],
|
|
952
989
|
mappedType: Utils.keys(t).find(k => t[k] === c.mappedType.constructor),
|
|
953
990
|
};
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
normalized.ignoreSchemaChanges = c.ignoreSchemaChanges;
|
|
965
|
-
}
|
|
966
|
-
if (c.defaultConstraint) {
|
|
967
|
-
normalized.defaultConstraint = c.defaultConstraint;
|
|
991
|
+
for (const field of [
|
|
992
|
+
'generated',
|
|
993
|
+
'nativeEnumName',
|
|
994
|
+
'extra',
|
|
995
|
+
'ignoreSchemaChanges',
|
|
996
|
+
'defaultConstraint',
|
|
997
|
+
]) {
|
|
998
|
+
if (c[field]) {
|
|
999
|
+
normalized[field] = c[field];
|
|
1000
|
+
}
|
|
968
1001
|
}
|
|
969
1002
|
o[col] = normalized;
|
|
970
1003
|
return o;
|
|
971
1004
|
}, {});
|
|
1005
|
+
const normalizeIndex = (idx) => {
|
|
1006
|
+
const out = {
|
|
1007
|
+
columnNames: idx.columnNames,
|
|
1008
|
+
composite: !!idx.composite,
|
|
1009
|
+
// PK indexes are always backed by a constraint — force it so postgres introspection matches
|
|
1010
|
+
constraint: !!idx.constraint || !!idx.primary,
|
|
1011
|
+
keyName: idx.keyName,
|
|
1012
|
+
primary: !!idx.primary,
|
|
1013
|
+
unique: !!idx.unique,
|
|
1014
|
+
};
|
|
1015
|
+
const optional = [
|
|
1016
|
+
'expression',
|
|
1017
|
+
'type',
|
|
1018
|
+
'deferMode',
|
|
1019
|
+
'columns',
|
|
1020
|
+
'include',
|
|
1021
|
+
'fillFactor',
|
|
1022
|
+
'invisible',
|
|
1023
|
+
'disabled',
|
|
1024
|
+
'clustered',
|
|
1025
|
+
];
|
|
1026
|
+
for (const field of optional) {
|
|
1027
|
+
if (idx[field] != null && idx[field] !== false) {
|
|
1028
|
+
out[field] = idx[field];
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return out;
|
|
1032
|
+
};
|
|
1033
|
+
const normalizeFk = (fk) => {
|
|
1034
|
+
const isNoAction = (rule) => !rule || rule.toLowerCase() === 'no action';
|
|
1035
|
+
// JSON.stringify drops undefined properties — let them through instead of guarding
|
|
1036
|
+
return {
|
|
1037
|
+
columnNames: fk.columnNames,
|
|
1038
|
+
constraintName: fk.constraintName,
|
|
1039
|
+
localTableName: fk.localTableName,
|
|
1040
|
+
referencedColumnNames: fk.referencedColumnNames,
|
|
1041
|
+
referencedTableName: fk.referencedTableName,
|
|
1042
|
+
updateRule: isNoAction(fk.updateRule) ? undefined : fk.updateRule,
|
|
1043
|
+
deleteRule: isNoAction(fk.deleteRule) ? undefined : fk.deleteRule,
|
|
1044
|
+
deferMode: fk.deferMode,
|
|
1045
|
+
};
|
|
1046
|
+
};
|
|
1047
|
+
const normalizeCheck = (check) => {
|
|
1048
|
+
const out = { name: check.name };
|
|
1049
|
+
if (typeof check.expression === 'string') {
|
|
1050
|
+
out.expression = check.expression;
|
|
1051
|
+
}
|
|
1052
|
+
for (const field of ['definition', 'columnName']) {
|
|
1053
|
+
if (check[field]) {
|
|
1054
|
+
out[field] = check[field];
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return out;
|
|
1058
|
+
};
|
|
1059
|
+
const sortedIndexes = [...this.#indexes].sort((a, b) => byString(a.keyName, b.keyName)).map(normalizeIndex);
|
|
1060
|
+
const sortedChecks = [...this.#checks].sort((a, b) => byString(a.name, b.name)).map(normalizeCheck);
|
|
1061
|
+
const sortedTriggers = [...this.#triggers].sort((a, b) => byString(a.name, b.name));
|
|
1062
|
+
const sortedForeignKeys = Object.fromEntries(Object.entries(this.#foreignKeys)
|
|
1063
|
+
.sort(([a], [b]) => byString(a, b))
|
|
1064
|
+
.map(([k, v]) => [k, normalizeFk(v)]));
|
|
972
1065
|
return {
|
|
973
1066
|
name: this.name,
|
|
974
1067
|
schema: this.schema,
|
|
975
1068
|
columns: columnsMapped,
|
|
976
|
-
indexes:
|
|
977
|
-
checks:
|
|
978
|
-
triggers:
|
|
979
|
-
foreignKeys:
|
|
980
|
-
|
|
981
|
-
comment: this.comment,
|
|
1069
|
+
indexes: sortedIndexes,
|
|
1070
|
+
checks: sortedChecks,
|
|
1071
|
+
triggers: sortedTriggers,
|
|
1072
|
+
foreignKeys: sortedForeignKeys,
|
|
1073
|
+
// emit `comment` even when unset so introspection (which always reads it) matches metadata
|
|
1074
|
+
comment: this.comment ?? null,
|
|
982
1075
|
};
|
|
983
1076
|
}
|
|
984
1077
|
}
|