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.mjs CHANGED
@@ -243,17 +243,20 @@ const getExcludeName = (table, columns) => getIndexOrExcludeName(table, columns,
243
243
  const indexesToQuery = (up, { schema, name: tableName }, indexes, snakeCase, language) => {
244
244
  return indexes.map((index) => {
245
245
  const { options } = index;
246
+ const isDeferrableUnique = isActiveUniqueDeferrable(options.deferrable);
247
+ if (isDeferrableUnique && !options.unique) throw new Error("Only unique indexes can be deferrable");
246
248
  const { columns, include, name } = getIndexOrExcludeMainOptions(tableName, index, getIndexName, snakeCase);
247
- if (!up) return { text: `DROP INDEX "${name}"${options.dropMode ? ` ${options.dropMode}` : ""}` };
249
+ if (!up) return { text: `${isDeferrableUnique ? `ALTER TABLE ${quoteTable(schema, tableName)} DROP CONSTRAINT "${name}"` : `DROP INDEX "${name}"`}${options.dropMode ? ` ${options.dropMode}` : ""}` };
250
+ if (isDeferrableUnique) validateDeferrableUnique(index);
248
251
  const values = [];
249
- const sql = ["CREATE"];
250
- if (options.unique) sql.push("UNIQUE");
251
- sql.push(`INDEX "${name}" ON ${quoteTable(schema, tableName)}`);
252
+ const sql = isDeferrableUnique ? [`ALTER TABLE ${quoteTable(schema, tableName)} ADD CONSTRAINT "${name}"`, `UNIQUE${options.nullsNotDistinct ? " NULLS NOT DISTINCT" : ""}`] : ["CREATE"];
253
+ if (!isDeferrableUnique && options.unique) sql.push("UNIQUE");
254
+ if (!isDeferrableUnique) sql.push(`INDEX "${name}" ON ${quoteTable(schema, tableName)}`);
252
255
  const u = options.using || options.tsVector && "GIN";
253
- if (u) sql.push(`USING ${u}`);
256
+ if (!isDeferrableUnique && u) sql.push(`USING ${u}`);
254
257
  const lang = options.tsVector && options.languageColumn ? `"${options.languageColumn}"` : options.language ? `'${options.language}'` : `'${language || "english"}'`;
255
- let hasWeight = options.tsVector && columns.some((column) => !!column.weight);
256
- const columnsSql = columns.map((column) => {
258
+ let hasWeight = !isDeferrableUnique && options.tsVector && columns.some((column) => !!column.weight);
259
+ const columnsSql = isDeferrableUnique ? columns.map(uniqueConstraintColumnToSql) : columns.map((column) => {
257
260
  let sql = [
258
261
  "expression" in column ? `(${column.expression})` : `"${column.column}"`,
259
262
  column.collate && `COLLATE ${quoteNameFromString(schema, column.collate)}`,
@@ -275,16 +278,45 @@ const indexesToQuery = (up, { schema, name: tableName }, indexes, snakeCase, lan
275
278
  else columnList = columnsSql.join(", ");
276
279
  sql.push(`(${columnList})`);
277
280
  if (include && include.length) sql.push(`INCLUDE (${include.map((column) => `"${column}"`).join(", ")})`);
278
- if (options.nullsNotDistinct) sql.push(`NULLS NOT DISTINCT`);
281
+ if (!isDeferrableUnique && options.nullsNotDistinct) sql.push(`NULLS NOT DISTINCT`);
279
282
  if (options.with) sql.push(`WITH (${options.with})`);
280
- if (options.tablespace) sql.push(`TABLESPACE ${options.tablespace}`);
281
- if (options.where) sql.push(`WHERE ${isRawSQL(options.where) ? options.where.toSQL({ values }) : options.where}`);
283
+ if (options.tablespace) sql.push(isDeferrableUnique ? `USING INDEX TABLESPACE ${options.tablespace}` : `TABLESPACE ${options.tablespace}`);
284
+ if (!isDeferrableUnique && options.where) sql.push(`WHERE ${isRawSQL(options.where) ? options.where.toSQL({ values }) : options.where}`);
285
+ if (isDeferrableUnique) sql.push(`DEFERRABLE INITIALLY ${options.deferrable === "deferred" ? "DEFERRED" : "IMMEDIATE"}`);
282
286
  return {
283
287
  text: sql.join(" "),
284
288
  values
285
289
  };
286
290
  });
287
291
  };
292
+ const isActiveUniqueDeferrable = (deferrable) => deferrable === "immediate" || deferrable === "deferred";
293
+ const uniqueConstraintColumnToSql = (column) => {
294
+ if ("expression" in column) throw new Error("Deferrable unique constraints do not support expression indexes");
295
+ return `"${column.column}"`;
296
+ };
297
+ const validateDeferrableUnique = (index) => {
298
+ const { options } = index;
299
+ if (options.where) throw new Error("Deferrable unique constraints do not support partial indexes");
300
+ const unsupportedOption = getUnsupportedDeferrableUniqueIndexOption(options);
301
+ if (unsupportedOption) throw new Error(`Deferrable unique constraints do not support index option: ${unsupportedOption}`);
302
+ for (const column of index.columns) {
303
+ if ("expression" in column) throw new Error("Deferrable unique constraints do not support expression indexes");
304
+ const unsupportedColumnOption = getUnsupportedDeferrableUniqueColumnOption(column);
305
+ if (unsupportedColumnOption) throw new Error(`Deferrable unique constraints do not support index column option: ${unsupportedColumnOption}`);
306
+ }
307
+ };
308
+ const getUnsupportedDeferrableUniqueIndexOption = (options) => {
309
+ if (options.using) return "using";
310
+ if (options.tsVector) return "tsVector";
311
+ if (options.language) return "language";
312
+ if (options.languageColumn) return "languageColumn";
313
+ };
314
+ const getUnsupportedDeferrableUniqueColumnOption = (column) => {
315
+ if (column.collate) return "collate";
316
+ if (column.opclass) return "opclass";
317
+ if (column.order) return "order";
318
+ if (column.weight) return "weight";
319
+ };
288
320
  const excludesToQuery = (up, { schema, name: tableName }, excludes, snakeCase) => {
289
321
  return excludes.map((exclude) => {
290
322
  const { options } = exclude;
@@ -4746,6 +4778,10 @@ const indexesSql = `SELECT
4746
4778
  ic.relname "name",
4747
4779
  am.amname AS "using",
4748
4780
  i.indisunique "unique",
4781
+ CASE
4782
+ WHEN c.condeferrable AND c.condeferred THEN 'deferred'
4783
+ WHEN c.condeferrable THEN 'immediate'
4784
+ END AS "deferrable",
4749
4785
  (
4750
4786
  SELECT json_agg(
4751
4787
  (
@@ -4842,6 +4878,9 @@ JOIN pg_class t ON t.oid = i.indrelid
4842
4878
  JOIN pg_namespace n ON n.oid = t.relnamespace
4843
4879
  JOIN pg_class ic ON ic.oid = i.indexrelid
4844
4880
  JOIN pg_am am ON am.oid = ic.relam
4881
+ LEFT JOIN pg_constraint c
4882
+ ON c.conindid = ic.oid
4883
+ AND c.contype = 'u'
4845
4884
  LEFT JOIN pg_catalog.pg_class tc ON (ic.reltoastrelid = tc.oid)
4846
4885
  WHERE ${filterSchema("n.nspname")}
4847
4886
  AND NOT i.indisprimary
@@ -5195,6 +5234,7 @@ async function introspectDbSchema(db, params) {
5195
5234
  const excludes = [];
5196
5235
  for (const index of raw.indexes) {
5197
5236
  nullsToUndefined(index);
5237
+ if (!index.deferrable) index.deferrable = void 0;
5198
5238
  for (const column of index.columns) {
5199
5239
  if (!("expression" in column)) continue;
5200
5240
  const s = column.expression;
@@ -5744,6 +5784,7 @@ const makeIndexOrExcludeOptions = (tableName, index, key) => {
5744
5784
  name: index.name !== (key === "indexes" ? getIndexName : getExcludeName)(tableName, index.columns) ? index.name : void 0,
5745
5785
  using: index.using === "btree" ? void 0 : index.using,
5746
5786
  unique: index.unique || void 0,
5787
+ deferrable: index.deferrable || void 0,
5747
5788
  include: index.include,
5748
5789
  nullsNotDistinct: index.nullsNotDistinct || void 0,
5749
5790
  with: index.with,