rake-db 2.36.14 → 2.36.16

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Adapter, Column as Column$1, ColumnSchemaConfig, ColumnTypeSchemaArg, ColumnsByType, ColumnsShape, DbDomainArg, DbResult, DbStructureDomainsMap, DefaultColumnTypes, DefaultPrivileges, DefaultSchemaConfig, EmptyObject, EnumColumn, Grant, MaybeArray, MaybePromise, NoPrimaryKeyOption, NonUniqDataItem, PickQueryQ, QueryData, QueryLogObject, QueryLogOptions, QueryLogger, QuerySchema, RawSqlBase, RecordOptionalString, RecordString, RlsPolicy, SearchWeight, TableData, TableDataFn, TableDataItem, TableDataMethods, raw } from "pqb/internal";
1
+ import { Adapter, Column as Column$1, ColumnSchemaConfig, ColumnsByType, ColumnsShape, DbDomainArg, DbResult, DbStructureDomainsMap, DefaultColumnTypes, DefaultPrivileges, DefaultSchemaConfig, EmptyObject, EnumColumn, Grant, MaybeArray, MaybePromise, NoPrimaryKeyOption, NonUniqDataItem, PickQueryQ, QueryData, QueryLogObject, QueryLogOptions, QueryLogger, QuerySchema, RawSqlBase, RecordOptionalString, RecordString, RlsPolicy, SearchWeight, TableData, TableDataFn, TableDataItem, TableDataMethods, raw } from "pqb/internal";
2
2
  import { Db } from "pqb";
3
3
  declare namespace DbStructure {
4
4
  interface TableNameAndSchemaName {
@@ -87,6 +87,8 @@ declare namespace DbStructure {
87
87
  name: string;
88
88
  using: string;
89
89
  unique: boolean;
90
+ /** Deferrability mode when the unique index is backed by a constraint. */
91
+ deferrable?: false | 'immediate' | 'deferred';
90
92
  columns: (({
91
93
  column: string;
92
94
  } | {
@@ -576,7 +578,7 @@ declare namespace RakeDbAst {
576
578
  export {};
577
579
  }
578
580
  interface TableMethods {
579
- enum(name: string): EnumColumn<ColumnTypeSchemaArg, undefined, [string, ...string[]]>;
581
+ enum(name: string): EnumColumn<DefaultSchemaConfig, undefined, [string, ...string[]]>;
580
582
  }
581
583
  type Add = typeof add;
582
584
  declare function add(item: Column$1, options?: {
@@ -721,7 +723,7 @@ type TableOptions = {
721
723
  language?: string;
722
724
  };
723
725
  type MigrationColumnTypes<CT> = Omit<CT, 'enum'> & {
724
- enum: (name: string) => EnumColumn<ColumnSchemaConfig, unknown, readonly string[]>;
726
+ enum: (name: string) => EnumColumn<DefaultSchemaConfig, unknown, readonly string[]>;
725
727
  };
726
728
  type ColumnsShapeCallback<CT, Shape extends ColumnsShape = ColumnsShape> = (t: MigrationColumnTypes<CT> & {
727
729
  raw: typeof raw;
package/dist/index.js CHANGED
@@ -266,17 +266,20 @@ const getExcludeName = (table, columns) => getIndexOrExcludeName(table, columns,
266
266
  const indexesToQuery = (up, { schema, name: tableName }, indexes, snakeCase, language) => {
267
267
  return indexes.map((index) => {
268
268
  const { options } = index;
269
+ const isDeferrableUnique = isActiveUniqueDeferrable(options.deferrable);
270
+ if (isDeferrableUnique && !options.unique) throw new Error("Only unique indexes can be deferrable");
269
271
  const { columns, include, name } = getIndexOrExcludeMainOptions(tableName, index, getIndexName, snakeCase);
270
- if (!up) return { text: `DROP INDEX "${name}"${options.dropMode ? ` ${options.dropMode}` : ""}` };
272
+ if (!up) return { text: `${isDeferrableUnique ? `ALTER TABLE ${quoteTable(schema, tableName)} DROP CONSTRAINT "${name}"` : `DROP INDEX "${name}"`}${options.dropMode ? ` ${options.dropMode}` : ""}` };
273
+ if (isDeferrableUnique) validateDeferrableUnique(index);
271
274
  const values = [];
272
- const sql = ["CREATE"];
273
- if (options.unique) sql.push("UNIQUE");
274
- sql.push(`INDEX "${name}" ON ${quoteTable(schema, tableName)}`);
275
+ const sql = isDeferrableUnique ? [`ALTER TABLE ${quoteTable(schema, tableName)} ADD CONSTRAINT "${name}"`, `UNIQUE${options.nullsNotDistinct ? " NULLS NOT DISTINCT" : ""}`] : ["CREATE"];
276
+ if (!isDeferrableUnique && options.unique) sql.push("UNIQUE");
277
+ if (!isDeferrableUnique) sql.push(`INDEX "${name}" ON ${quoteTable(schema, tableName)}`);
275
278
  const u = options.using || options.tsVector && "GIN";
276
- if (u) sql.push(`USING ${u}`);
279
+ if (!isDeferrableUnique && u) sql.push(`USING ${u}`);
277
280
  const lang = options.tsVector && options.languageColumn ? `"${options.languageColumn}"` : options.language ? `'${options.language}'` : `'${language || "english"}'`;
278
- let hasWeight = options.tsVector && columns.some((column) => !!column.weight);
279
- const columnsSql = columns.map((column) => {
281
+ let hasWeight = !isDeferrableUnique && options.tsVector && columns.some((column) => !!column.weight);
282
+ const columnsSql = isDeferrableUnique ? columns.map(uniqueConstraintColumnToSql) : columns.map((column) => {
280
283
  let sql = [
281
284
  "expression" in column ? `(${column.expression})` : `"${column.column}"`,
282
285
  column.collate && `COLLATE ${quoteNameFromString(schema, column.collate)}`,
@@ -298,16 +301,45 @@ const indexesToQuery = (up, { schema, name: tableName }, indexes, snakeCase, lan
298
301
  else columnList = columnsSql.join(", ");
299
302
  sql.push(`(${columnList})`);
300
303
  if (include && include.length) sql.push(`INCLUDE (${include.map((column) => `"${column}"`).join(", ")})`);
301
- if (options.nullsNotDistinct) sql.push(`NULLS NOT DISTINCT`);
304
+ if (!isDeferrableUnique && options.nullsNotDistinct) sql.push(`NULLS NOT DISTINCT`);
302
305
  if (options.with) sql.push(`WITH (${options.with})`);
303
- if (options.tablespace) sql.push(`TABLESPACE ${options.tablespace}`);
304
- if (options.where) sql.push(`WHERE ${(0, pqb_internal.isRawSQL)(options.where) ? options.where.toSQL({ values }) : options.where}`);
306
+ if (options.tablespace) sql.push(isDeferrableUnique ? `USING INDEX TABLESPACE ${options.tablespace}` : `TABLESPACE ${options.tablespace}`);
307
+ if (!isDeferrableUnique && options.where) sql.push(`WHERE ${(0, pqb_internal.isRawSQL)(options.where) ? options.where.toSQL({ values }) : options.where}`);
308
+ if (isDeferrableUnique) sql.push(`DEFERRABLE INITIALLY ${options.deferrable === "deferred" ? "DEFERRED" : "IMMEDIATE"}`);
305
309
  return {
306
310
  text: sql.join(" "),
307
311
  values
308
312
  };
309
313
  });
310
314
  };
315
+ const isActiveUniqueDeferrable = (deferrable) => deferrable === "immediate" || deferrable === "deferred";
316
+ const uniqueConstraintColumnToSql = (column) => {
317
+ if ("expression" in column) throw new Error("Deferrable unique constraints do not support expression indexes");
318
+ return `"${column.column}"`;
319
+ };
320
+ const validateDeferrableUnique = (index) => {
321
+ const { options } = index;
322
+ if (options.where) throw new Error("Deferrable unique constraints do not support partial indexes");
323
+ const unsupportedOption = getUnsupportedDeferrableUniqueIndexOption(options);
324
+ if (unsupportedOption) throw new Error(`Deferrable unique constraints do not support index option: ${unsupportedOption}`);
325
+ for (const column of index.columns) {
326
+ if ("expression" in column) throw new Error("Deferrable unique constraints do not support expression indexes");
327
+ const unsupportedColumnOption = getUnsupportedDeferrableUniqueColumnOption(column);
328
+ if (unsupportedColumnOption) throw new Error(`Deferrable unique constraints do not support index column option: ${unsupportedColumnOption}`);
329
+ }
330
+ };
331
+ const getUnsupportedDeferrableUniqueIndexOption = (options) => {
332
+ if (options.using) return "using";
333
+ if (options.tsVector) return "tsVector";
334
+ if (options.language) return "language";
335
+ if (options.languageColumn) return "languageColumn";
336
+ };
337
+ const getUnsupportedDeferrableUniqueColumnOption = (column) => {
338
+ if (column.collate) return "collate";
339
+ if (column.opclass) return "opclass";
340
+ if (column.order) return "order";
341
+ if (column.weight) return "weight";
342
+ };
311
343
  const excludesToQuery = (up, { schema, name: tableName }, excludes, snakeCase) => {
312
344
  return excludes.map((exclude) => {
313
345
  const { options } = exclude;
@@ -4769,6 +4801,10 @@ const indexesSql = `SELECT
4769
4801
  ic.relname "name",
4770
4802
  am.amname AS "using",
4771
4803
  i.indisunique "unique",
4804
+ CASE
4805
+ WHEN c.condeferrable AND c.condeferred THEN 'deferred'
4806
+ WHEN c.condeferrable THEN 'immediate'
4807
+ END AS "deferrable",
4772
4808
  (
4773
4809
  SELECT json_agg(
4774
4810
  (
@@ -4865,6 +4901,9 @@ JOIN pg_class t ON t.oid = i.indrelid
4865
4901
  JOIN pg_namespace n ON n.oid = t.relnamespace
4866
4902
  JOIN pg_class ic ON ic.oid = i.indexrelid
4867
4903
  JOIN pg_am am ON am.oid = ic.relam
4904
+ LEFT JOIN pg_constraint c
4905
+ ON c.conindid = ic.oid
4906
+ AND c.contype = 'u'
4868
4907
  LEFT JOIN pg_catalog.pg_class tc ON (ic.reltoastrelid = tc.oid)
4869
4908
  WHERE ${filterSchema("n.nspname")}
4870
4909
  AND NOT i.indisprimary
@@ -5218,6 +5257,7 @@ async function introspectDbSchema(db, params) {
5218
5257
  const excludes = [];
5219
5258
  for (const index of raw.indexes) {
5220
5259
  nullsToUndefined(index);
5260
+ if (!index.deferrable) index.deferrable = void 0;
5221
5261
  for (const column of index.columns) {
5222
5262
  if (!("expression" in column)) continue;
5223
5263
  const s = column.expression;
@@ -5767,6 +5807,7 @@ const makeIndexOrExcludeOptions = (tableName, index, key) => {
5767
5807
  name: index.name !== (key === "indexes" ? getIndexName : getExcludeName)(tableName, index.columns) ? index.name : void 0,
5768
5808
  using: index.using === "btree" ? void 0 : index.using,
5769
5809
  unique: index.unique || void 0,
5810
+ deferrable: index.deferrable || void 0,
5770
5811
  include: index.include,
5771
5812
  nullsNotDistinct: index.nullsNotDistinct || void 0,
5772
5813
  with: index.with,