@mikro-orm/oracledb 7.1.0-dev.3 → 7.1.0-dev.31

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.
@@ -1,5 +1,5 @@
1
- import { MikroORM, type Options, type IDatabaseDriver, type EntityManager, type EntityManagerType, type EntityClass, type AnyEntity, type EntitySchema } from '@mikro-orm/core';
2
- import type { SqlEntityManager } from '@mikro-orm/sql';
1
+ import { type MikroORM, type Options, type IDatabaseDriver, type EntityManager, type EntityManagerType, type EntityClass, type AnyEntity, type EntitySchema } from '@mikro-orm/core';
2
+ import { SqlMikroORM, type SqlEntityManager } from '@mikro-orm/sql';
3
3
  import { OracleDriver } from './OracleDriver.js';
4
4
  /** Configuration options for the Oracle driver. */
5
5
  export type OracleOptions<EM extends SqlEntityManager<OracleDriver> = SqlEntityManager<OracleDriver>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> = Partial<Options<OracleDriver, EM, Entities>>;
@@ -8,7 +8,7 @@ export declare function defineOracleConfig<EM extends SqlEntityManager<OracleDri
8
8
  /**
9
9
  * @inheritDoc
10
10
  */
11
- export declare class OracleMikroORM<EM extends SqlEntityManager<OracleDriver> = SqlEntityManager<OracleDriver>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends MikroORM<OracleDriver, EM, Entities> {
11
+ export declare class OracleMikroORM<EM extends SqlEntityManager<OracleDriver> = SqlEntityManager<OracleDriver>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends SqlMikroORM<OracleDriver, EM, Entities> {
12
12
  /**
13
13
  * @inheritDoc
14
14
  */
package/OracleMikroORM.js CHANGED
@@ -1,4 +1,5 @@
1
- import { defineConfig, MikroORM, } from '@mikro-orm/core';
1
+ import { defineConfig, } from '@mikro-orm/core';
2
+ import { SqlMikroORM } from '@mikro-orm/sql';
2
3
  import { OracleDriver } from './OracleDriver.js';
3
4
  /** Creates a type-safe configuration object for the Oracle driver. */
4
5
  export function defineOracleConfig(options) {
@@ -7,7 +8,7 @@ export function defineOracleConfig(options) {
7
8
  /**
8
9
  * @inheritDoc
9
10
  */
10
- export class OracleMikroORM extends MikroORM {
11
+ export class OracleMikroORM extends SqlMikroORM {
11
12
  /**
12
13
  * @inheritDoc
13
14
  */
package/OraclePlatform.js CHANGED
@@ -203,7 +203,7 @@ export class OraclePlatform extends AbstractSqlPlatform {
203
203
  if (b.length === 0) {
204
204
  return raw(`json_equal(${root}, json(?))`, [value]);
205
205
  }
206
- return raw(`json_value(${root}, '$.${b.map(this.quoteJsonKey).join('.')}')`);
206
+ return raw(`json_value(${root}, ${this.quoteValue(`$.${b.map(this.quoteJsonKey).join('.')}`)})`);
207
207
  }
208
208
  processJsonCondition(o, value, path, alias) {
209
209
  if (Utils.isPlainObject(value) && !Object.keys(value).some(k => Utils.isOperator(k))) {
@@ -2,7 +2,13 @@ import { type AbstractSqlConnection, type CheckDef, type Column, type DatabaseSc
2
2
  /** Schema introspection helper for Oracle Database. */
3
3
  export declare class OracleSchemaHelper extends SchemaHelper {
4
4
  static readonly DEFAULT_VALUES: Record<string, string[]>;
5
+ private static readonly AUTO_NOT_NULL_RE;
6
+ private static readonly PARTIAL_INDEX_RE;
5
7
  getDatabaseExistsSQL(name: string): string;
8
+ getSetSchemaSQL(schema: string): string;
9
+ getResetSchemaSQL(defaultSchema: string): string;
10
+ supportsMigrationSchema(): boolean;
11
+ tableExists(connection: AbstractSqlConnection, tableName: string, schemaName: string | undefined, ctx?: Transaction): Promise<boolean>;
6
12
  getAllTables(connection: AbstractSqlConnection, schemas?: string[], ctx?: Transaction): Promise<Table[]>;
7
13
  getListTablesSQL(schemaName?: string): string;
8
14
  getListViewsSQL(): string;
@@ -19,6 +25,7 @@ export declare class OracleSchemaHelper extends SchemaHelper {
19
25
  private getEnumDefinitions;
20
26
  private getChecksSQL;
21
27
  getAllChecks(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>, ctx?: Transaction): Promise<Dictionary<CheckDef[]>>;
28
+ getDatabaseCollation(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string | undefined>;
22
29
  loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[], ctx?: Transaction): Promise<void>;
23
30
  getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
24
31
  getPostAlterTable(tableDiff: TableDifference, safe: boolean): string[];
@@ -35,6 +42,9 @@ export declare class OracleSchemaHelper extends SchemaHelper {
35
42
  createTableColumn(column: Column, table: DatabaseTable, changedProperties?: Set<string>): string | undefined;
36
43
  alterTableColumn(column: Column, table: DatabaseTable, changedProperties: Set<string>): string[];
37
44
  getCreateIndexSQL(tableName: string, index: IndexDef, partialExpression?: boolean): string;
45
+ protected getIndexColumns(index: IndexDef): string;
46
+ /** Oracle has no native WHERE clause for indexes; the predicate is folded into CASE-WHEN columns. */
47
+ protected getIndexWhereClause(_index: IndexDef): string;
38
48
  createIndex(index: IndexDef, table: DatabaseTable, createPrimary?: boolean): string;
39
49
  dropForeignKey(tableName: string, constraintName: string): string;
40
50
  dropViewIfExists(name: string, schema?: string): string;
@@ -7,9 +7,29 @@ export class OracleSchemaHelper extends SchemaHelper {
7
7
  systimestamp: ['current_timestamp'],
8
8
  sysdate: ['current_timestamp'],
9
9
  };
10
+ // `stripAutoNotNullFilter` unwraps balanced per-clause parens before calling `.exec`, so we
11
+ // only need to match the bare form here.
12
+ static AUTO_NOT_NULL_RE = /^"?([^"\s()]+)"?\s+is\s+not\s+null$/i;
13
+ // Greedy `(.+)` so nested CASE expressions inside the predicate don't trip the match on
14
+ // an inner `then <col> end`.
15
+ static PARTIAL_INDEX_RE = /^\s*\(?\s*case\s+when\s+(.+)\s+then\s+"?([^"\s)]+)"?\s+end\s*\)?\s*$/is;
10
16
  getDatabaseExistsSQL(name) {
11
17
  return `select 1 from all_users where username = ${this.platform.quoteValue(name)}`;
12
18
  }
19
+ getSetSchemaSQL(schema) {
20
+ return `alter session set current_schema = ${this.quote(schema)}`;
21
+ }
22
+ getResetSchemaSQL(defaultSchema) {
23
+ return `alter session set current_schema = ${this.quote(defaultSchema)}`;
24
+ }
25
+ supportsMigrationSchema() {
26
+ return true;
27
+ }
28
+ async tableExists(connection, tableName, schemaName, ctx) {
29
+ const schema = schemaName ?? this.platform.getDefaultSchemaName();
30
+ const rows = await connection.execute(`select 1 from all_tables where owner = ${this.platform.quoteValue(schema)} and table_name = ${this.platform.quoteValue(tableName)}`, [], 'all', ctx);
31
+ return rows.length > 0;
32
+ }
13
33
  async getAllTables(connection, schemas, ctx) {
14
34
  if (!schemas || schemas.length === 0) {
15
35
  return connection.execute(this.getListTablesSQL(), [], 'all', ctx);
@@ -121,7 +141,8 @@ export class OracleSchemaHelper extends SchemaHelper {
121
141
  atc.char_length as character_maximum_length,
122
142
  atc.data_length as data_length,
123
143
  atc.identity_column as is_identity,
124
- atc.column_id as ordinal_position
144
+ atc.column_id as ordinal_position,
145
+ atc.collation as collation_name
125
146
  from all_tab_cols atc
126
147
  left join all_col_comments acc on atc.owner = acc.owner and atc.table_name = acc.table_name and atc.column_name = acc.column_name
127
148
  where atc.hidden_column = 'NO'
@@ -177,6 +198,11 @@ export class OracleSchemaHelper extends SchemaHelper {
177
198
  precision: col.numeric_precision,
178
199
  scale: col.numeric_scale,
179
200
  comment: col.column_comment,
201
+ // `USING_NLS_COMP` is the Oracle sentinel for "inherit from the session NLS settings", so
202
+ // a column without an explicit `COLLATE` clause introspects as `USING_NLS_COMP` rather
203
+ // than NULL. Treat it as "no explicit collation" so the comparator doesn't flag every
204
+ // string column as changed when the database default is something else (e.g. `BINARY`).
205
+ collation: col.collation_name && col.collation_name !== 'USING_NLS_COMP' ? col.collation_name : undefined,
180
206
  generated,
181
207
  });
182
208
  }
@@ -210,16 +236,19 @@ export class OracleSchemaHelper extends SchemaHelper {
210
236
  if (isPrimary) {
211
237
  continue;
212
238
  }
239
+ const partialMatch = typeof index.expression === 'string' ? OracleSchemaHelper.PARTIAL_INDEX_RE.exec(index.expression) : null;
213
240
  const indexDef = {
214
- columnNames: [index.column_name],
241
+ columnNames: [partialMatch ? partialMatch[2] : index.column_name],
215
242
  keyName: index.index_name,
216
243
  unique: index.is_unique === 'YES',
217
- primary: false, // We skip PK indexes above, so this is always false
244
+ primary: false,
218
245
  constraint: isConstraintIndex || index.is_unique === 'YES',
219
246
  };
220
- // Handle function-based indexes (expression indexes)
221
- /* v8 ignore start: expression index branches */
222
- if (index.expression) {
247
+ /* v8 ignore start: function-based (non-partial) index branches */
248
+ if (partialMatch) {
249
+ indexDef.where = partialMatch[1].trim();
250
+ }
251
+ else if (index.expression) {
223
252
  indexDef.expression = index.expression;
224
253
  }
225
254
  else if (index.column_name?.match(/[(): ,"'`]/)) {
@@ -231,6 +260,17 @@ export class OracleSchemaHelper extends SchemaHelper {
231
260
  }
232
261
  for (const key of Object.keys(ret)) {
233
262
  ret[key] = await this.mapIndexes(ret[key]);
263
+ for (const idx of ret[key]) {
264
+ if (idx.where) {
265
+ const stripped = this.stripAutoNotNullFilter(idx.where, idx.columnNames, OracleSchemaHelper.AUTO_NOT_NULL_RE);
266
+ if (stripped === '') {
267
+ delete idx.where;
268
+ }
269
+ else {
270
+ idx.where = stripped;
271
+ }
272
+ }
273
+ }
234
274
  }
235
275
  return ret;
236
276
  }
@@ -338,6 +378,10 @@ export class OracleSchemaHelper extends SchemaHelper {
338
378
  }
339
379
  return ret;
340
380
  }
381
+ async getDatabaseCollation(connection, ctx) {
382
+ const [row] = await connection.execute(`select property_value as collation from database_properties where property_name = 'DEFAULT_COLLATION'`, [], 'all', ctx);
383
+ return row?.collation;
384
+ }
341
385
  async loadInformationSchema(schema, connection, tables, schemas, ctx) {
342
386
  if (tables.length === 0) {
343
387
  return;
@@ -347,9 +391,11 @@ export class OracleSchemaHelper extends SchemaHelper {
347
391
  const indexes = await this.getAllIndexes(connection, tablesBySchema, ctx);
348
392
  const checks = await this.getAllChecks(connection, tablesBySchema, ctx);
349
393
  const fks = await this.getAllForeignKeys(connection, tablesBySchema, ctx);
394
+ const dbCollation = await this.getDatabaseCollation(connection, ctx);
350
395
  for (const t of tables) {
351
396
  const key = this.getTableKey(t);
352
397
  const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
398
+ table.collation = dbCollation;
353
399
  const pks = await this.getPrimaryKeys(connection, indexes[key], table.name, table.schema);
354
400
  const enums = this.getEnumDefinitions(checks[key] ?? []);
355
401
  table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums);
@@ -450,6 +496,7 @@ export class OracleSchemaHelper extends SchemaHelper {
450
496
  /* v8 ignore next: generated column branch */
451
497
  const columnType = column.generated ? `as ${column.generated}` : column.type;
452
498
  const col = [this.quote(column.name), columnType];
499
+ Utils.runIfNotEmpty(() => col.push(this.getCollateSQL(column.collation)), column.collation);
453
500
  Utils.runIfNotEmpty(() => col.push('generated by default as identity'), column.autoincrement);
454
501
  /* v8 ignore next 3: default value branch */
455
502
  const useDefault = changedProperties
@@ -473,8 +520,15 @@ export class OracleSchemaHelper extends SchemaHelper {
473
520
  const parts = [];
474
521
  const quotedTableName = table.getQuotedName();
475
522
  // Oracle uses MODIFY for column changes, and always requires the column type
476
- if (changedProperties.has('type') || changedProperties.has('nullable') || changedProperties.has('default')) {
523
+ if (changedProperties.has('type') ||
524
+ changedProperties.has('nullable') ||
525
+ changedProperties.has('default') ||
526
+ changedProperties.has('collation')) {
477
527
  const colParts = [this.quote(column.name), column.type];
528
+ // Oracle's MODIFY restates the column type, so re-emit COLLATE whenever the column has
529
+ // one — even if the trigger was a nullable/default change — so we can't accidentally
530
+ // drop the column collation by omitting it from the new type spec.
531
+ Utils.runIfNotEmpty(() => colParts.push(this.getCollateSQL(column.collation)), column.collation);
478
532
  if (changedProperties.has('default')) {
479
533
  if (column.default != null && column.default !== 'null') {
480
534
  colParts.push(`default ${column.default}`);
@@ -503,6 +557,16 @@ export class OracleSchemaHelper extends SchemaHelper {
503
557
  }
504
558
  return super.getCreateIndexSQL(tableName, index);
505
559
  }
560
+ getIndexColumns(index) {
561
+ if (index.where) {
562
+ return this.emulatePartialIndexColumns(index);
563
+ }
564
+ return super.getIndexColumns(index);
565
+ }
566
+ /** Oracle has no native WHERE clause for indexes; the predicate is folded into CASE-WHEN columns. */
567
+ getIndexWhereClause(_index) {
568
+ return '';
569
+ }
506
570
  createIndex(index, table, createPrimary = false) {
507
571
  if (index.primary) {
508
572
  return '';
@@ -524,10 +588,21 @@ export class OracleSchemaHelper extends SchemaHelper {
524
588
  const quotedTableName = table.getQuotedName();
525
589
  if (index.unique) {
526
590
  const nullableCols = index.columnNames.filter(column => table.getColumn(column)?.nullable);
591
+ const autoNotNull = nullableCols.length
592
+ ? nullableCols.map(c => `${this.quote(c)} is not null`).join(' and ')
593
+ : '';
594
+ if (index.where) {
595
+ // Wrap the user predicate in parens before ANDing the auto-NOT-NULL guard — otherwise a
596
+ // disjunctive `a = 1 or b = 2` would rebind as `a = 1 or (b = 2 and <col> is not null)`.
597
+ const predicate = autoNotNull ? `(${index.where}) and ${autoNotNull}` : index.where;
598
+ return `create unique index ${this.quote(index.keyName)} on ${quotedTableName} (${index.columnNames
599
+ .map(c => `case when ${predicate} then ${this.quote(c)} end`)
600
+ .join(', ')})`;
601
+ }
527
602
  return `create unique index ${this.quote(index.keyName)} on ${quotedTableName} (${index.columnNames
528
603
  .map(c => {
529
604
  if (table.getColumn(c)?.nullable) {
530
- return `case when ${nullableCols.map(c => `${this.quote(c)} is not null`).join(' and ')} then ${this.quote(c)} end`;
605
+ return `case when ${autoNotNull} then ${this.quote(c)} end`;
531
606
  }
532
607
  return this.quote(c);
533
608
  })
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <a href="https://mikro-orm.io"><img src="https://raw.githubusercontent.com/mikro-orm/mikro-orm/master/docs/static/img/logo-readme.svg?sanitize=true" alt="MikroORM" /></a>
3
3
  </h1>
4
4
 
5
- TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite (including libSQL), MSSQL and Oracle databases.
5
+ TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL (including CockroachDB and PGlite), SQLite (including libSQL), MSSQL and Oracle databases.
6
6
 
7
7
  > Heavily inspired by [Doctrine](https://www.doctrine-project.org/) and [Hibernate](https://hibernate.org/).
8
8
 
@@ -19,6 +19,7 @@ Install a driver package for your database:
19
19
 
20
20
  ```sh
21
21
  npm install @mikro-orm/postgresql # PostgreSQL
22
+ npm install @mikro-orm/pglite # PGlite (embedded PostgreSQL in WASM)
22
23
  npm install @mikro-orm/mysql # MySQL
23
24
  npm install @mikro-orm/mariadb # MariaDB
24
25
  npm install @mikro-orm/sqlite # SQLite
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/oracledb",
3
- "version": "7.1.0-dev.3",
3
+ "version": "7.1.0-dev.31",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite, MSSQL and Oracle databases.",
5
5
  "keywords": [
6
6
  "data-mapper",
@@ -42,15 +42,15 @@
42
42
  "copy": "node ../../scripts/copy.mjs"
43
43
  },
44
44
  "dependencies": {
45
- "@mikro-orm/sql": "7.1.0-dev.3",
46
- "kysely": "0.28.16",
45
+ "@mikro-orm/sql": "7.1.0-dev.31",
46
+ "kysely": "0.29.0",
47
47
  "oracledb": "6.10.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@mikro-orm/core": "^7.0.11"
50
+ "@mikro-orm/core": "^7.0.15"
51
51
  },
52
52
  "peerDependencies": {
53
- "@mikro-orm/core": "7.1.0-dev.3"
53
+ "@mikro-orm/core": "7.1.0-dev.31"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">= 22.17.0"