@mikro-orm/sql 7.1.9-dev.1 → 7.1.9-dev.11

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.
@@ -1100,7 +1100,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1100
1100
  sql += conds.join(' or ');
1101
1101
  }
1102
1102
  if (this.platform.usesReturningStatement() && returning.size > 0) {
1103
- const returningFields = Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
1103
+ const returningFields = Utils.flatten([...returning].map(prop => (meta.properties[prop] ?? meta.root.properties[prop]).fieldNames));
1104
1104
  /* v8 ignore next */
1105
1105
  sql +=
1106
1106
  returningFields.length > 0
@@ -168,7 +168,7 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
168
168
  const fields = this.options.groupBy.map(field => this.quote(field));
169
169
  this.parts.push(`group by ${fields.join(', ')}`);
170
170
  }
171
- if (this.options.having) {
171
+ if (this.options.having?.sql.trim()) {
172
172
  this.parts.push(`having ${this.options.having.sql}`);
173
173
  this.params.push(...this.options.having.params);
174
174
  }
@@ -234,7 +234,7 @@ export class OracleNativeQueryBuilder extends NativeQueryBuilder {
234
234
  const fields = this.options.groupBy.map(field => this.quote(field));
235
235
  this.parts.push(`group by ${fields.join(', ')}`);
236
236
  }
237
- if (this.options.having) {
237
+ if (this.options.having?.sql.trim()) {
238
238
  this.parts.push(`having ${this.options.having.sql}`);
239
239
  this.params.push(...this.options.having.params);
240
240
  }
@@ -7,4 +7,7 @@ export declare class BaseSqliteConnection extends AbstractSqlConnection {
7
7
  skipOnConnect?: boolean;
8
8
  }): Promise<void>;
9
9
  protected attachDatabases(): Promise<void>;
10
+ /** Per-connection state, lost whenever the underlying connection is recreated, so it has to be replayed. */
11
+ protected getConnectionSetupSql(): Promise<string[]>;
12
+ private getAttachDatabasesSql;
10
13
  }
@@ -1,5 +1,6 @@
1
1
  import { CompiledQuery } from 'kysely';
2
2
  import { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
3
+ const FOREIGN_KEYS_PRAGMA = 'pragma foreign_keys = on';
3
4
  export class BaseSqliteConnection extends AbstractSqlConnection {
4
5
  createKyselyDialect(options) {
5
6
  throw new Error('No SQLite dialect configured. Pass a Kysely dialect via the `driverOptions` config option, ' +
@@ -7,19 +8,28 @@ export class BaseSqliteConnection extends AbstractSqlConnection {
7
8
  }
8
9
  async connect(options) {
9
10
  await super.connect(options);
10
- await this.getClient().executeQuery(CompiledQuery.raw('pragma foreign_keys = on'));
11
+ await this.getClient().executeQuery(CompiledQuery.raw(FOREIGN_KEYS_PRAGMA));
11
12
  await this.attachDatabases();
12
13
  }
13
14
  async attachDatabases() {
15
+ for (const sql of await this.getAttachDatabasesSql()) {
16
+ await this.execute(sql);
17
+ }
18
+ }
19
+ /** Per-connection state, lost whenever the underlying connection is recreated, so it has to be replayed. */
20
+ async getConnectionSetupSql() {
21
+ return [FOREIGN_KEYS_PRAGMA, ...(await this.getAttachDatabasesSql())];
22
+ }
23
+ async getAttachDatabasesSql() {
14
24
  const attachDatabases = this.config.get('attachDatabases');
15
25
  if (!attachDatabases?.length) {
16
- return;
26
+ return [];
17
27
  }
18
28
  const { fs } = await import('@mikro-orm/core/fs-utils');
19
29
  const baseDir = this.config.get('baseDir');
20
- for (const db of attachDatabases) {
30
+ return attachDatabases.map(db => {
21
31
  const path = fs.absolutePath(db.path, baseDir);
22
- await this.execute(`attach database '${path}' as ${this.platform.quoteIdentifier(db.name)}`);
23
- }
32
+ return `attach database '${path}' as ${this.platform.quoteIdentifier(db.name)}`;
33
+ });
24
34
  }
25
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.1.9-dev.1",
3
+ "version": "7.1.9-dev.11",
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.8"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.1.9-dev.1"
56
+ "@mikro-orm/core": "7.1.9-dev.11"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -308,7 +308,7 @@ export class NativeQueryBuilder {
308
308
  const fields = this.options.groupBy.map(field => this.quote(field));
309
309
  this.parts.push(`group by ${fields.join(', ')}`);
310
310
  }
311
- if (this.options.having) {
311
+ if (this.options.having?.sql.trim()) {
312
312
  this.parts.push(`having ${this.options.having.sql}`);
313
313
  this.params.push(...this.options.having.params);
314
314
  }
@@ -414,7 +414,8 @@ export class QueryBuilderHelper {
414
414
  }
415
415
  if (k === '$not') {
416
416
  const res = this._appendQueryCondition(type, cond[k]);
417
- parts.push(`not (${res.sql})`);
417
+ // negating a vacuously true condition (e.g. an empty `$and`) matches nothing
418
+ parts.push(res.sql ? `not (${res.sql})` : '1 = 0');
418
419
  res.params.forEach(p => params.push(p));
419
420
  continue;
420
421
  }
@@ -785,6 +786,10 @@ export class QueryBuilderHelper {
785
786
  appendGroupCondition(type, operator, subCondition) {
786
787
  const parts = [];
787
788
  const params = [];
789
+ // an empty disjunction is false, same as `$in: []`, while an empty conjunction is vacuously true
790
+ if (operator === '$or' && subCondition.length === 0) {
791
+ return { sql: '1 = 0', params };
792
+ }
788
793
  // single sub-condition can be ignored to reduce nesting of parens
789
794
  if (subCondition.length === 1 || operator === '$and') {
790
795
  for (const sub of subCondition) {
@@ -897,9 +897,13 @@ export class SchemaComparator {
897
897
  // lookbehind: only strip a real charset introducer, never an underscore inside a literal like 'a_b'
898
898
  ?.replace(/(?<![\w'])_\w+'(.*?)'/g, '$1')
899
899
  .replace(/!=/g, '<>')
900
- .replace(/in\s*\((.*?)\)/gi, '= any (array[$1])')
900
+ // `\b` keeps this from firing inside identifiers like `min(...)`
901
+ .replace(/\bin\s*\((.*?)\)/gi, '= any (array[$1])')
901
902
  // MySQL normalizes count(*) to count(0)
902
903
  .replace(/\bcount\s*\(\s*0\s*\)/gi, 'count(*)')
904
+ // multi word type names in casts, the generic `::\w+` below only covers single word ones
905
+ // the precision is kept, so `timestamptz(3)` and `timestamp(3) with time zone` leave the same residue
906
+ .replace(/::\s*(?:character\s+varying|bit\s+varying|double\s+precision|(?:timestamp|time)\b(\s*\(\d+\))?(?:\s+with(?:out)?\s+time\s+zone)?)/gi, '$1')
903
907
  // Remove quotes first so we can process identifiers
904
908
  .replace(/['"`]/g, '')
905
909
  // MySQL adds table/alias prefixes to columns (e.g., a.name or table_name.column vs just column)
@@ -907,6 +911,10 @@ export class SchemaComparator {
907
911
  .replace(/\b\w+\.(\w+)/g, '$1')
908
912
  // Normalize JOIN syntax: inner join -> join (equivalent in SQL)
909
913
  .replace(/\binner\s+join\b/gi, 'join')
914
+ // PostgreSQL names an unaliased bare function call after the function itself,
915
+ // so `max(created_at)` comes back as `max(created_at) AS max`
916
+ // the lookahead skips table function column alias lists like `unnest(a) AS unnest(c)`, which are meaningful
917
+ .replace(/\b(\w+)\s*\(((?:[^()]|\([^()]*\))*)\)\s+as\s+\1\b(?!\s*\()/gi, '$1($2)')
910
918
  // Remove redundant column aliases like `title AS title` -> `title`
911
919
  .replace(/\b(\w+)\s+as\s+\1\b/gi, '$1')
912
920
  // Remove AS keyword (optional in SQL, MySQL may add/remove it)