{"id":"c56cb400f083eb0d","repo":"knex/knex","slug":"postgres-cannot-create-constraint-with-predicate","errorCode":null,"errorMessage":"postgres cannot create constraint with predicate","messagePattern":"postgres cannot create constraint with predicate","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/dialects/postgres/schema/pg-tablecompiler.js","lineNumber":204,"sourceCode":"      );\n    }\n  }\n\n  unique(columns, indexName) {\n    let deferrable;\n    let useConstraint = true;\n    let predicate;\n    if (isObject(indexName)) {\n      ({ indexName, deferrable, useConstraint, predicate } = indexName);\n      if (useConstraint === undefined) {\n        useConstraint = !!deferrable || !predicate;\n      }\n    }\n    if (!useConstraint && deferrable && deferrable !== 'not deferrable') {\n      throw new Error('postgres cannot create deferrable index');\n    }\n    if (useConstraint && predicate) {\n      throw new Error('postgres cannot create constraint with predicate');\n    }\n    deferrable = deferrable ? ` deferrable initially ${deferrable}` : '';\n    indexName = indexName\n      ? this.formatter.wrap(indexName)\n      : this._indexCommand('unique', this.tableNameRaw, columns);\n\n    if (useConstraint) {\n      this.pushQuery(\n        `alter table ${this.tableName()} add constraint ${indexName}` +\n          ' unique (' +\n          this.formatter.columnize(columns) +\n          ')' +\n          deferrable\n      );\n    } else {\n      const predicateQuery = predicate\n        ? ' ' + this.client.queryCompiler(predicate).where()\n        : '';","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/knex/knex/blob/e25d54bcb707714a17f5a5744eba5c4246bb4d1d/lib/dialects/postgres/schema/pg-tablecompiler.js#L186-L222","documentation":"postgres unique() (pg-tablecompiler.js:203) refuses to attach a WHERE predicate to a UNIQUE CONSTRAINT. Postgres supports partial unique indexes (WHERE clause) but not partial constraints; the two features are mutually exclusive. When useConstraint is true and predicate is set, knex throws rather than emit invalid DDL.","triggerScenarios":"Calling table.unique(['col'], { useConstraint: true, predicate: knex.raw('active = true') }) or any path where useConstraint resolves true (explicit, or implied by deferrable/no-predicate logic) alongside a non-empty predicate.","commonSituations":"Trying to make a soft-delete-friendly unique constraint by adding a WHERE; copy-pasting options between constraint and index forms; assuming constraints accept the same predicate option as indexes.","solutions":["Use an index instead of a constraint for partial uniqueness: table.unique(['col'], { useConstraint: false, predicate: knex.raw('active = true') }).","Drop the predicate if you must use a constraint.","Model the soft-delete uniqueness via an expression index or a generated column."],"exampleFix":"// before\ntable.unique('email', { useConstraint: true, predicate: knex.raw('deleted_at IS NULL') });\n\n// after (partial unique must be an index)\ntable.unique('email', { useConstraint: false, predicate: knex.raw('deleted_at IS NULL') });","handlingStrategy":"validation","validationCode":"function validateUniqueOptions({ useConstraint = true, predicate } = {}) {\n  if (useConstraint && predicate) {\n    throw new Error('Partial unique must be an index, not a constraint');\n  }\n  return { useConstraint, predicate };\n}\ntable.unique('email', validateUniqueOptions(opts));","typeGuard":"function isPartialUniqueConstraint(opts) {\n  return opts && (opts.useConstraint === undefined ? true : opts.useConstraint) && !!opts.predicate;\n}","tryCatchPattern":null,"preventionTips":["Predicate => useConstraint:false; deferrable => useConstraint:true. Never mix.","Document the rule in your migration helpers.","Lint migrations for predicate-with-constraint combinations."],"tags":["postgres","schema","unique","partial-index","constraint","migration"],"analyzedSha":"e25d54bcb707714a17f5a5744eba5c4246bb4d1d","analyzedAt":"2026-08-03T18:35:32.148Z","schemaVersion":2}