@mikro-orm/sql 7.1.2-dev.1 → 7.1.2-dev.10
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.
|
@@ -479,8 +479,11 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
479
479
|
}
|
|
480
480
|
seen.add(dedupeKey);
|
|
481
481
|
ret[key] ??= [];
|
|
482
|
+
// CHECK: unwrap the `CHECK ((<predicate>))` shell and drop pg-added `::type` casts so the
|
|
483
|
+
// inner predicate matches the user's metadata. EXCLUDE bodies are kept verbatim — the user's
|
|
484
|
+
// `@Check` expression is the full body (see SchemaHelper.createCheck).
|
|
482
485
|
const m = /^check \(\((.*)\)\)$/is.exec(check.expression);
|
|
483
|
-
const def = m
|
|
486
|
+
const def = m ? m[1].replace(/\((.*?)\)::\w+/g, '$1') : check.expression;
|
|
484
487
|
ret[key].push({
|
|
485
488
|
name: check.name,
|
|
486
489
|
columnName: check.column_name,
|
|
@@ -1073,16 +1076,31 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
1073
1076
|
join pg_am as am on am.oid = i.relam
|
|
1074
1077
|
left join pg_constraint as c on c.conname = i.relname
|
|
1075
1078
|
where indrelid in (${tables.map(t => `${this.platform.quoteValue(`${this.quote(t.schema_name)}.${this.quote(t.table_name)}`)}::regclass`).join(', ')})
|
|
1079
|
+
and (c.contype is null or c.contype <> 'x')
|
|
1076
1080
|
order by relname`;
|
|
1077
1081
|
}
|
|
1078
1082
|
getChecksSQL(tablesBySchemas) {
|
|
1083
|
+
const checkFilter = [...tablesBySchemas.entries()]
|
|
1084
|
+
.map(([schema, tables]) => `ccu.table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(',')}) and ccu.table_schema = ${this.platform.quoteValue(schema)}`)
|
|
1085
|
+
.join(' or ');
|
|
1086
|
+
// EXCLUDE constraints don't appear in information_schema.constraint_column_usage, so the second
|
|
1087
|
+
// branch resolves the table from pg_class/pg_namespace directly.
|
|
1088
|
+
const excludeFilter = [...tablesBySchemas.entries()]
|
|
1089
|
+
.map(([schema, tables]) => `cls.relname in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(',')}) and nsp.nspname = ${this.platform.quoteValue(schema)}`)
|
|
1090
|
+
.join(' or ');
|
|
1079
1091
|
return `select ccu.table_name as table_name, ccu.table_schema as schema_name, pgc.conname as name, conrelid::regclass as table_from, ccu.column_name as column_name, pg_get_constraintdef(pgc.oid) as expression
|
|
1080
1092
|
from pg_constraint pgc
|
|
1081
1093
|
join pg_namespace nsp on nsp.oid = pgc.connamespace
|
|
1082
1094
|
join pg_class cls on pgc.conrelid = cls.oid
|
|
1083
1095
|
join information_schema.constraint_column_usage ccu on pgc.conname = ccu.constraint_name and nsp.nspname = ccu.constraint_schema and cls.relname = ccu.table_name
|
|
1084
|
-
where contype = 'c' and (${
|
|
1085
|
-
|
|
1096
|
+
where pgc.contype = 'c' and (${checkFilter})
|
|
1097
|
+
union all
|
|
1098
|
+
select cls.relname as table_name, nsp.nspname as schema_name, pgc.conname as name, conrelid::regclass as table_from, null as column_name, pg_get_constraintdef(pgc.oid) as expression
|
|
1099
|
+
from pg_constraint pgc
|
|
1100
|
+
join pg_namespace nsp on nsp.oid = pgc.connamespace
|
|
1101
|
+
join pg_class cls on pgc.conrelid = cls.oid
|
|
1102
|
+
where pgc.contype = 'x' and (${excludeFilter})
|
|
1103
|
+
order by name`;
|
|
1086
1104
|
}
|
|
1087
1105
|
inferLengthFromColumnType(type) {
|
|
1088
1106
|
const match = /^(\w+(?:\s+\w+)*)\s*(?:\(\s*(\d+)\s*\)|$)/.exec(type);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.2-dev.
|
|
3
|
+
"version": "7.1.2-dev.10",
|
|
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.1.1"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.1.2-dev.
|
|
56
|
+
"@mikro-orm/core": "7.1.2-dev.10"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -826,8 +826,9 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
826
826
|
getResultAndCount(): Promise<[Loaded<Entity, Hint, Fields>[], number]>;
|
|
827
827
|
/**
|
|
828
828
|
* Returns native query builder instance with sub-query aliased with given alias.
|
|
829
|
+
* The alias literal is preserved in the type so `addSelect()` exposes it on `execute()` results.
|
|
829
830
|
*/
|
|
830
|
-
as(alias:
|
|
831
|
+
as<Alias extends string>(alias: Alias): NativeQueryBuilder & RawQueryFragment<Alias>;
|
|
831
832
|
/**
|
|
832
833
|
* Returns native query builder instance with sub-query aliased with given alias.
|
|
833
834
|
* You can provide the target entity name as the first parameter and use the second parameter to point to an existing property to infer its field name.
|
|
@@ -1050,17 +1051,28 @@ type PopulatedDTO<T, K extends keyof T> = NonNullable<T[K]> extends Collection<i
|
|
|
1050
1051
|
type SubFields<F extends string, Rel extends string> = F extends `${Rel}.${infer Sub}` ? Sub : never;
|
|
1051
1052
|
type RootFields<F extends string, H extends string> = F extends `${string}.${string}` ? F extends `${H}.${string}` ? never : F : F;
|
|
1052
1053
|
type JoinDTO<T, K extends keyof T, F extends string> = NonNullable<T[K]> extends Collection<infer U> ? SubFields<F, K & string> extends never ? EntityDTOProp<T, Collection<U>> : DirectDTO<U, (SubFields<F, K & string> | PrimaryProperty<U>) & keyof U>[] : SubFields<F, K & string> extends never ? EntityDTOProp<T, T[K]> : DirectDTO<NonNullable<T[K]>, (SubFields<F, K & string> | PrimaryProperty<NonNullable<T[K]>>) & keyof NonNullable<T[K]>> | Extract<T[K], null | undefined>;
|
|
1053
|
-
|
|
1054
|
+
/**
|
|
1055
|
+
* Raw aliases (`sql`...`.as('x')`, `'col as x'`, or aliased sub-queries) are not entity keys,
|
|
1056
|
+
* so they never appear in the selected-fields DTO. They are tracked in the `RawAliases` generic
|
|
1057
|
+
* (preserved across joins), so expose them on the result as `unknown` — their value type is not
|
|
1058
|
+
* known at the type level. Only intersected when raw aliases are present, to keep the plain DTO
|
|
1059
|
+
* untouched when there are none.
|
|
1060
|
+
*/
|
|
1061
|
+
type WithRawAliases<DTO, RawAliases extends string> = [RawAliases] extends [never] ? DTO : DTO & {
|
|
1062
|
+
[K in RawAliases]: unknown;
|
|
1063
|
+
};
|
|
1064
|
+
type ExecuteDTOInner<T, H extends string, F extends string> = [H] extends [never] ? [F] extends ['*'] ? EntityDTOFlat<T> : DirectDTO<T, F & keyof T> : [F] extends ['*'] ? true extends (H extends `${string}.${string}` ? true : false) ? SerializeDTO<T, H> : Omit<EntityDTOFlat<T>, H & keyof EntityDTOFlat<T>> & {
|
|
1054
1065
|
[K in H & keyof T as K & keyof EntityDTOFlat<T>]: PopulatedDTO<T, K> | Extract<T[K], null | undefined>;
|
|
1055
1066
|
} : true extends (H extends `${string}.${string}` ? true : false) ? EntityDTOFlat<Loaded<T, H, F>> : DirectDTO<T, (RootFields<F, H> | PrimaryProperty<T>) & keyof T> & {
|
|
1056
1067
|
[K in H & keyof T]: JoinDTO<T, K, F>;
|
|
1057
1068
|
};
|
|
1069
|
+
type ExecuteDTO<T, H extends string, F extends string, RawAliases extends string = never> = WithRawAliases<ExecuteDTOInner<T, H, F>, RawAliases>;
|
|
1058
1070
|
/** Shorthand for `QueryBuilder` with all generic parameters set to `any`. */
|
|
1059
1071
|
export type AnyQueryBuilder<T extends object = AnyEntity> = QueryBuilder<T, any, any, any, any, any, any>;
|
|
1060
1072
|
export interface SelectQueryBuilder<Entity extends object = AnyEntity, RootAlias extends string = never, Hint extends string = never, Context extends object = never, RawAliases extends string = never, Fields extends string = '*', CTEs extends Record<string, object> = {}> extends QueryBuilder<Entity, RootAlias, Hint, Context, RawAliases, Fields, CTEs> {
|
|
1061
|
-
execute<Result = ExecuteDTO<Entity, Hint, Fields>[]>(method?: 'all' | 'get' | 'run', mapResults?: boolean): Promise<Result>;
|
|
1062
|
-
execute<Result = ExecuteDTO<Entity, Hint, Fields>[]>(method: 'all', mapResults?: boolean): Promise<Result>;
|
|
1063
|
-
execute<Result = ExecuteDTO<Entity, Hint, Fields>>(method: 'get', mapResults?: boolean): Promise<Result>;
|
|
1073
|
+
execute<Result = ExecuteDTO<Entity, Hint, Fields, RawAliases>[]>(method?: 'all' | 'get' | 'run', mapResults?: boolean): Promise<Result>;
|
|
1074
|
+
execute<Result = ExecuteDTO<Entity, Hint, Fields, RawAliases>[]>(method: 'all', mapResults?: boolean): Promise<Result>;
|
|
1075
|
+
execute<Result = ExecuteDTO<Entity, Hint, Fields, RawAliases>>(method: 'get', mapResults?: boolean): Promise<Result>;
|
|
1064
1076
|
execute<Result = QueryResult<Entity>>(method: 'run', mapResults?: boolean): Promise<Result>;
|
|
1065
1077
|
}
|
|
1066
1078
|
export interface CountQueryBuilder<Entity extends object> extends QueryBuilder<Entity, any, any> {
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -982,6 +982,15 @@ export class DatabaseTable {
|
|
|
982
982
|
const normOptions = { length: c.length, precision: c.precision, scale: c.scale };
|
|
983
983
|
const type = this.#platform.normalizeColumnType(c.type ?? '', normOptions)?.toLowerCase() || rawType;
|
|
984
984
|
const fixedPrecision = isFixedPrecisionFamily(c.mappedType);
|
|
985
|
+
// only emit `length` for types that actually use one — text/enum/json/etc. don't, but mysql
|
|
986
|
+
// information_schema still reports `character_maximum_length` for them (65535 for text, etc.)
|
|
987
|
+
const hasMeaningfulLength = typeof c.mappedType.getDefaultLength !== 'undefined';
|
|
988
|
+
// mysql stores decimal defaults padded to scale (`0` → `0.00`); collapse to canonical numeric form
|
|
989
|
+
// so the metadata-side (`0`) and introspection-side (`0.00`) snapshots agree
|
|
990
|
+
let defaultValue = c.default ?? null;
|
|
991
|
+
if (defaultValue != null && c.mappedType instanceof DecimalType && Number.isFinite(+defaultValue)) {
|
|
992
|
+
defaultValue = this.#platform.formatDecimal(defaultValue, c.scale).toString();
|
|
993
|
+
}
|
|
985
994
|
const normalized = {
|
|
986
995
|
name: c.name,
|
|
987
996
|
type,
|
|
@@ -990,26 +999,26 @@ export class DatabaseTable {
|
|
|
990
999
|
primary: primaryColumns.has(c.name) || !!c.primary,
|
|
991
1000
|
nullable: !!c.nullable,
|
|
992
1001
|
unique: uniqueColumns.has(c.name) || !!c.unique,
|
|
993
|
-
length: c.length || null,
|
|
1002
|
+
length: hasMeaningfulLength ? c.length || null : null,
|
|
994
1003
|
precision: fixedPrecision ? null : (c.precision ?? null),
|
|
995
1004
|
scale: fixedPrecision ? null : (c.scale ?? null),
|
|
996
|
-
default:
|
|
997
|
-
comment: c.comment
|
|
1005
|
+
default: defaultValue,
|
|
1006
|
+
comment: c.comment || null,
|
|
998
1007
|
collation: c.collation ?? null,
|
|
999
1008
|
enumItems: c.enumItems ?? [],
|
|
1000
1009
|
mappedType: Utils.keys(t).find(k => t[k] === c.mappedType.constructor),
|
|
1001
1010
|
};
|
|
1002
|
-
for (const field of [
|
|
1003
|
-
'generated',
|
|
1004
|
-
'nativeEnumName',
|
|
1005
|
-
'extra',
|
|
1006
|
-
'ignoreSchemaChanges',
|
|
1007
|
-
'defaultConstraint',
|
|
1008
|
-
]) {
|
|
1011
|
+
for (const field of ['generated', 'nativeEnumName', 'ignoreSchemaChanges', 'defaultConstraint']) {
|
|
1009
1012
|
if (c[field]) {
|
|
1010
1013
|
normalized[field] = c[field];
|
|
1011
1014
|
}
|
|
1012
1015
|
}
|
|
1016
|
+
// `extra` casing varies between metadata (user-supplied, typically uppercase like
|
|
1017
|
+
// `ON UPDATE CURRENT_TIMESTAMP`) and mysql introspection (returns it lowercased) — the
|
|
1018
|
+
// comparator already lowercases both sides when diffing, mirror that in the snapshot
|
|
1019
|
+
if (c.extra) {
|
|
1020
|
+
normalized.extra = c.extra.toLowerCase();
|
|
1021
|
+
}
|
|
1013
1022
|
o[col] = normalized;
|
|
1014
1023
|
return o;
|
|
1015
1024
|
}, {});
|
|
@@ -1082,8 +1091,9 @@ export class DatabaseTable {
|
|
|
1082
1091
|
checks: sortedChecks,
|
|
1083
1092
|
triggers: sortedTriggers,
|
|
1084
1093
|
foreignKeys: sortedForeignKeys,
|
|
1085
|
-
// emit `comment` even when unset so introspection (which always reads it) matches metadata
|
|
1086
|
-
|
|
1094
|
+
// emit `comment` even when unset so introspection (which always reads it) matches metadata;
|
|
1095
|
+
// collapse mysql's `""` for unset comments to `null` so both sources agree
|
|
1096
|
+
comment: this.comment || null,
|
|
1087
1097
|
};
|
|
1088
1098
|
}
|
|
1089
1099
|
}
|
|
@@ -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
|
/**
|
|
@@ -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
|
}
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -138,6 +138,8 @@ export declare abstract class SchemaHelper {
|
|
|
138
138
|
getReferencedTableName(referencedTableName: string, schema?: string): string;
|
|
139
139
|
createIndex(index: IndexDef, table: DatabaseTable, createPrimary?: boolean): string;
|
|
140
140
|
createCheck(table: DatabaseTable, check: CheckDef): string;
|
|
141
|
+
/** True for `@Check` expressions that are a full table-constraint body (e.g. PostgreSQL `exclude using gist (...)`) and must be emitted verbatim instead of wrapped in `check (...)`. */
|
|
142
|
+
private isRawConstraintBody;
|
|
141
143
|
/**
|
|
142
144
|
* Generates SQL to create a database trigger on a table.
|
|
143
145
|
* Override in driver-specific helpers for custom DDL (e.g., PostgreSQL function wrapping).
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -813,7 +813,13 @@ export class SchemaHelper {
|
|
|
813
813
|
return this.getCreateIndexSQL(table.getShortestName(), index);
|
|
814
814
|
}
|
|
815
815
|
createCheck(table, check) {
|
|
816
|
-
|
|
816
|
+
const expression = check.expression;
|
|
817
|
+
const body = this.isRawConstraintBody(expression) ? expression : `check (${expression})`;
|
|
818
|
+
return `alter table ${table.getQuotedName()} add constraint ${this.quote(check.name)} ${body}`;
|
|
819
|
+
}
|
|
820
|
+
/** True for `@Check` expressions that are a full table-constraint body (e.g. PostgreSQL `exclude using gist (...)`) and must be emitted verbatim instead of wrapped in `check (...)`. */
|
|
821
|
+
isRawConstraintBody(expression) {
|
|
822
|
+
return /^\s*exclude\b/i.test(expression);
|
|
817
823
|
}
|
|
818
824
|
/**
|
|
819
825
|
* Generates SQL to create a database trigger on a table.
|