@mikro-orm/sql 7.1.2-dev.9 → 7.1.3-dev.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.
- package/AbstractSqlDriver.js +4 -1
- package/dialects/postgresql/BasePostgreSqlPlatform.js +5 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +6 -0
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/package.json +3 -3
- package/schema/DatabaseSchema.js +3 -1
- package/schema/DatabaseTable.js +24 -12
- package/schema/SchemaComparator.js +8 -3
package/AbstractSqlDriver.js
CHANGED
|
@@ -510,8 +510,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
510
510
|
else if (prop.fieldNames.length > 1) {
|
|
511
511
|
// composite keys
|
|
512
512
|
const fk = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
513
|
+
// `mapFlatCompositePrimaryKey` collapses to a scalar when the target PK is a single prop
|
|
514
|
+
// (e.g. a relation referencing a composite unique key on a single-PK target).
|
|
513
515
|
const pk = Utils.mapFlatCompositePrimaryKey(fk, prop);
|
|
514
|
-
|
|
516
|
+
const valid = Array.isArray(pk) ? pk.every(val => val != null) : pk != null;
|
|
517
|
+
relationPojo[prop.name] = valid ? pk : null;
|
|
515
518
|
}
|
|
516
519
|
else if (prop.runtimeType === 'Date') {
|
|
517
520
|
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
@@ -126,6 +126,11 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
126
126
|
if (['interval'].includes(simpleType)) {
|
|
127
127
|
return this.getIntervalTypeDeclarationSQL(options);
|
|
128
128
|
}
|
|
129
|
+
// postgis reports the typmod without spaces (`geometry(point,4326)`) — collapse any
|
|
130
|
+
// user-supplied spacing so the metadata and introspection forms compare equal
|
|
131
|
+
if (['geometry', 'geography'].includes(simpleType)) {
|
|
132
|
+
return type.toLowerCase().replace(/\s+/g, '');
|
|
133
|
+
}
|
|
129
134
|
// TimeType.getColumnType drops the timezone qualifier, so detect tz aliases from the original column type.
|
|
130
135
|
const originalType = options.columnTypes?.[0]?.toLowerCase() ?? type;
|
|
131
136
|
if (/^timetz\b/.test(originalType) || /^time\s+with\s+time\s+zone\b/.test(originalType)) {
|
|
@@ -370,6 +370,7 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
370
370
|
is_nullable,
|
|
371
371
|
udt_name,
|
|
372
372
|
udt_schema,
|
|
373
|
+
pg_catalog.format_type(pga.atttypid, pga.atttypmod) format_type,
|
|
373
374
|
coalesce(datetime_precision, character_maximum_length) length,
|
|
374
375
|
atttypmod custom_length,
|
|
375
376
|
numeric_precision,
|
|
@@ -404,6 +405,11 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
404
405
|
if (type === 'bpchar') {
|
|
405
406
|
type = 'char';
|
|
406
407
|
}
|
|
408
|
+
// postgis stores the geometry/geography typmod (`geometry(point,4326)`) in atttypmod, which
|
|
409
|
+
// information_schema does not expose — recover the full type via format_type so it round-trips
|
|
410
|
+
if ((col.udt_name === 'geometry' || col.udt_name === 'geography') && col.format_type) {
|
|
411
|
+
type = col.format_type;
|
|
412
|
+
}
|
|
407
413
|
if (type === 'vector' && col.length == null && col.custom_length != null && col.custom_length !== -1) {
|
|
408
414
|
col.length = col.custom_length;
|
|
409
415
|
}
|
|
@@ -11,6 +11,7 @@ export declare class SqlitePlatform extends AbstractSqlPlatform {
|
|
|
11
11
|
usesDefaultKeyword(): boolean;
|
|
12
12
|
usesReturningStatement(): boolean;
|
|
13
13
|
usesEnumCheckConstraints(): boolean;
|
|
14
|
+
supportsComments(): boolean;
|
|
14
15
|
getCurrentTimestampSQL(length: number): string;
|
|
15
16
|
getDateTimeTypeDeclarationSQL(column: {
|
|
16
17
|
length: number;
|
|
@@ -18,6 +18,10 @@ export class SqlitePlatform extends AbstractSqlPlatform {
|
|
|
18
18
|
usesEnumCheckConstraints() {
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
|
+
// sqlite has no table/column comments, so they cannot round-trip through introspection
|
|
22
|
+
supportsComments() {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
21
25
|
getCurrentTimestampSQL(length) {
|
|
22
26
|
return `(strftime('%s', 'now') * 1000)`;
|
|
23
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.3-dev.0",
|
|
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",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"kysely": "0.29.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.1.
|
|
53
|
+
"@mikro-orm/core": "^7.1.2"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.1.
|
|
56
|
+
"@mikro-orm/core": "7.1.3-dev.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -233,7 +233,9 @@ export class DatabaseSchema {
|
|
|
233
233
|
? platform.formatQuery(check.expression.sql, check.expression.params)
|
|
234
234
|
: check.expression;
|
|
235
235
|
table.addCheck({
|
|
236
|
-
|
|
236
|
+
// the engine truncates identifiers past its limit (postgres 63 bytes) on storage, so
|
|
237
|
+
// mirror that here — otherwise the introspected (truncated) name never matches metadata
|
|
238
|
+
name: check.name.substring(0, platform.getMaxIdentifierLength()),
|
|
237
239
|
expression,
|
|
238
240
|
definition: `check (${expression})`,
|
|
239
241
|
columnName,
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -975,6 +975,7 @@ export class DatabaseTable {
|
|
|
975
975
|
mappedType instanceof t.float ||
|
|
976
976
|
mappedType instanceof t.double;
|
|
977
977
|
const supportsUnsigned = this.#platform.supportsUnsigned();
|
|
978
|
+
const supportsComments = this.#platform.supportsComments();
|
|
978
979
|
const columnsMapped = sortedColumnKeys.reduce((o, col) => {
|
|
979
980
|
const c = columns[col];
|
|
980
981
|
// omit `autoincrement` from options so `serial` (metadata) and `int4` (introspection) collapse the same
|
|
@@ -982,6 +983,15 @@ export class DatabaseTable {
|
|
|
982
983
|
const normOptions = { length: c.length, precision: c.precision, scale: c.scale };
|
|
983
984
|
const type = this.#platform.normalizeColumnType(c.type ?? '', normOptions)?.toLowerCase() || rawType;
|
|
984
985
|
const fixedPrecision = isFixedPrecisionFamily(c.mappedType);
|
|
986
|
+
// only emit `length` for types that actually use one — text/enum/json/etc. don't, but mysql
|
|
987
|
+
// information_schema still reports `character_maximum_length` for them (65535 for text, etc.)
|
|
988
|
+
const hasMeaningfulLength = typeof c.mappedType.getDefaultLength !== 'undefined';
|
|
989
|
+
// mysql stores decimal defaults padded to scale (`0` → `0.00`); collapse to canonical numeric form
|
|
990
|
+
// so the metadata-side (`0`) and introspection-side (`0.00`) snapshots agree
|
|
991
|
+
let defaultValue = c.default ?? null;
|
|
992
|
+
if (defaultValue != null && c.mappedType instanceof DecimalType && Number.isFinite(+defaultValue)) {
|
|
993
|
+
defaultValue = this.#platform.formatDecimal(defaultValue, c.scale).toString();
|
|
994
|
+
}
|
|
985
995
|
const normalized = {
|
|
986
996
|
name: c.name,
|
|
987
997
|
type,
|
|
@@ -990,26 +1000,26 @@ export class DatabaseTable {
|
|
|
990
1000
|
primary: primaryColumns.has(c.name) || !!c.primary,
|
|
991
1001
|
nullable: !!c.nullable,
|
|
992
1002
|
unique: uniqueColumns.has(c.name) || !!c.unique,
|
|
993
|
-
length: c.length || null,
|
|
1003
|
+
length: hasMeaningfulLength ? c.length || null : null,
|
|
994
1004
|
precision: fixedPrecision ? null : (c.precision ?? null),
|
|
995
1005
|
scale: fixedPrecision ? null : (c.scale ?? null),
|
|
996
|
-
default:
|
|
997
|
-
comment: c.comment
|
|
1006
|
+
default: defaultValue,
|
|
1007
|
+
comment: supportsComments ? c.comment || null : null,
|
|
998
1008
|
collation: c.collation ?? null,
|
|
999
1009
|
enumItems: c.enumItems ?? [],
|
|
1000
1010
|
mappedType: Utils.keys(t).find(k => t[k] === c.mappedType.constructor),
|
|
1001
1011
|
};
|
|
1002
|
-
for (const field of [
|
|
1003
|
-
'generated',
|
|
1004
|
-
'nativeEnumName',
|
|
1005
|
-
'extra',
|
|
1006
|
-
'ignoreSchemaChanges',
|
|
1007
|
-
'defaultConstraint',
|
|
1008
|
-
]) {
|
|
1012
|
+
for (const field of ['generated', 'nativeEnumName', 'ignoreSchemaChanges', 'defaultConstraint']) {
|
|
1009
1013
|
if (c[field]) {
|
|
1010
1014
|
normalized[field] = c[field];
|
|
1011
1015
|
}
|
|
1012
1016
|
}
|
|
1017
|
+
// `extra` casing varies between metadata (user-supplied, typically uppercase like
|
|
1018
|
+
// `ON UPDATE CURRENT_TIMESTAMP`) and mysql introspection (returns it lowercased) — the
|
|
1019
|
+
// comparator already lowercases both sides when diffing, mirror that in the snapshot
|
|
1020
|
+
if (c.extra) {
|
|
1021
|
+
normalized.extra = c.extra.toLowerCase();
|
|
1022
|
+
}
|
|
1013
1023
|
o[col] = normalized;
|
|
1014
1024
|
return o;
|
|
1015
1025
|
}, {});
|
|
@@ -1082,8 +1092,10 @@ export class DatabaseTable {
|
|
|
1082
1092
|
checks: sortedChecks,
|
|
1083
1093
|
triggers: sortedTriggers,
|
|
1084
1094
|
foreignKeys: sortedForeignKeys,
|
|
1085
|
-
// emit `comment` even when unset so introspection (which always reads it) matches metadata
|
|
1086
|
-
|
|
1095
|
+
// emit `comment` even when unset so introspection (which always reads it) matches metadata;
|
|
1096
|
+
// collapse mysql's `""` for unset comments to `null` so both sources agree. drop entirely on
|
|
1097
|
+
// platforms that can't read comments back (sqlite), where keeping it would flip the snapshot
|
|
1098
|
+
comment: supportsComments ? this.comment || null : null,
|
|
1087
1099
|
};
|
|
1088
1100
|
}
|
|
1089
1101
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArrayType, BooleanType, DateTimeType, inspect, JsonType, parseJsonSafe, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { ArrayType, BooleanType, DateTimeType, DecimalType, inspect, JsonType, parseJsonSafe, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
3
|
import { diffPartitioning } from './partitioning.js';
|
|
4
4
|
/**
|
|
@@ -339,7 +339,7 @@ export class SchemaComparator {
|
|
|
339
339
|
fromTable,
|
|
340
340
|
toTable,
|
|
341
341
|
};
|
|
342
|
-
if (this.diffComment(fromTable.comment, toTable.comment)) {
|
|
342
|
+
if (this.#platform.supportsComments() && this.diffComment(fromTable.comment, toTable.comment)) {
|
|
343
343
|
tableDifferences.changedComment = toTable.comment;
|
|
344
344
|
this.log(`table comment changed for ${tableDifferences.name}`, {
|
|
345
345
|
fromTableComment: fromTable.comment,
|
|
@@ -713,7 +713,7 @@ export class SchemaComparator {
|
|
|
713
713
|
log(`'default' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
714
714
|
changedProperties.add('default');
|
|
715
715
|
}
|
|
716
|
-
if (this.diffComment(fromColumn.comment, toColumn.comment)) {
|
|
716
|
+
if (this.#platform.supportsComments() && this.diffComment(fromColumn.comment, toColumn.comment)) {
|
|
717
717
|
log(`'comment' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
718
718
|
changedProperties.add('comment');
|
|
719
719
|
}
|
|
@@ -996,6 +996,11 @@ export class SchemaComparator {
|
|
|
996
996
|
const defaultValueTo = to.default.toLowerCase().replace('current_timestamp', 'now').replace(/\(\)$/, '');
|
|
997
997
|
return defaultValueFrom === defaultValueTo;
|
|
998
998
|
}
|
|
999
|
+
// mysql stores decimal defaults padded to scale (`0` → `0.00`); compare numerically so the
|
|
1000
|
+
// entity-side raw literal and the introspected padded form don't churn the no-op migration
|
|
1001
|
+
if (to.mappedType instanceof DecimalType && Number.isFinite(+from.default) && Number.isFinite(+to.default)) {
|
|
1002
|
+
return (this.#platform.formatDecimal(from.default, to.scale) === this.#platform.formatDecimal(to.default, to.scale));
|
|
1003
|
+
}
|
|
999
1004
|
if (from.default && to.default) {
|
|
1000
1005
|
return from.default.toString().toLowerCase() === to.default.toString().toLowerCase();
|
|
1001
1006
|
}
|