@mikro-orm/sql 7.1.2-dev.0 → 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?.[1].replace(/\((.*?)\)::\w+/g, '$1');
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 (${[...tablesBySchemas.entries()].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)}`).join(' or ')})
1085
- order by pgc.conname`;
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.0",
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.0"
56
+ "@mikro-orm/core": "7.1.2-dev.10"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -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: string): NativeQueryBuilder;
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
- type ExecuteDTO<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
+ /**
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> {
@@ -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: c.default ?? null,
997
- comment: c.comment ?? null,
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
- comment: this.comment ?? null,
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
  }
@@ -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).
@@ -813,7 +813,13 @@ export class SchemaHelper {
813
813
  return this.getCreateIndexSQL(table.getShortestName(), index);
814
814
  }
815
815
  createCheck(table, check) {
816
- return `alter table ${table.getQuotedName()} add constraint ${this.quote(check.name)} check (${check.expression})`;
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.