{"id":"cf7341f3f10c2f93","repo":"prisma/prisma","slug":"contract-validation-failed-cf7341","errorCode":"CONTRACT.VALIDATION_FAILED","errorMessage":"Index: every index carries a full physical name; an expression index must be explicitly named (a default name cannot be derived from an expression).","messagePattern":"Index: every index carries a full physical name; an expression index must be explicitly named \\(a default name cannot be derived from an expression\\)\\.","errorType":"validation","errorClass":"ContractValidationError","httpStatus":null,"severity":"error","filePath":"packages/2-sql/1-core/contract/src/ir/sql-index.ts","lineNumber":78,"sourceCode":" * alias one (e.g.\n * `import { Index as SqlIndexNode } from '@prisma-next/sql-contract/types'`).\n */\nexport class Index extends SqlNode {\n  readonly name: string;\n  readonly unique: boolean;\n  /** Derived from the wire naming arm — presence is the naming-mode discriminator in the flat JSON. */\n  declare readonly prefix?: string;\n  declare readonly columns?: readonly string[];\n  declare readonly expression?: string;\n  declare readonly where?: string;\n  declare readonly type?: string;\n  declare readonly options?: Record<string, unknown>;\n\n  constructor(input: IndexInput) {\n    super();\n    const name = nameOf(input.naming);\n    if (name.length === 0) {\n      throw new ContractValidationError(\n        'Index: every index carries a full physical name; an expression index must be explicitly named (a default name cannot be derived from an expression).',\n        'storage',\n      );\n    }\n    if ((input.columns === undefined) === (input.expression === undefined)) {\n      throw new ContractValidationError(\n        `Index \"${name}\": exactly one of columns or expression must be set.`,\n        'storage',\n      );\n    }\n    this.name = name;\n    this.unique = input.unique;\n    if (input.naming.kind === 'wire') this.prefix = input.naming.prefix;\n    if (input.columns !== undefined) this.columns = input.columns;\n    if (input.expression !== undefined) this.expression = input.expression;\n    if (input.where !== undefined) this.where = input.where;\n    if (input.type !== undefined) this.type = input.type;\n    if (input.options !== undefined) this.options = input.options;","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/prisma/prisma/blob/1243cdffbec0506c595ac8fd5865fe258c336fa0/packages/2-sql/1-core/contract/src/ir/sql-index.ts#L60-L96","documentation":"Thrown in the Index IR constructor (packages/2-sql/1-core/contract/src/ir/sql-index.ts:78) when `nameOf(input.naming)` returns an empty string. The contract requires every index to carry a full physical name; a column index can derive a default name from its columns, but an expression index's element list is opaque SQL that cannot be turned into a stable identifier, so it must be named explicitly. This fires during authoring/lowering when an index is built without a usable name (typically an unnamed expression index).","triggerScenarios":"Constructing an Index whose naming resolves to an empty name — most concretely building an expression index (`expression: 'lower(name)'`) without supplying a name/prefix through the naming arm. The constructor runs `nameOf(input.naming)` and, on empty result, throws before the columns/expression xor guard.","commonSituations":"Schema authoring calls for an expression/partial index but omits the name; a lowering step builds an expression index and forgets to stamp a naming object; a custom extension that creates Index nodes directly without going through the schema builder's naming defaults.","solutions":["Give the expression index an explicit name (or wire-mode prefix) via the index authoring API.","If you did not intend an expression index, switch to a column index (`columns: [...]`) so a default name can be derived.","If you build Index nodes directly in an extension, ensure the passed `naming` resolves to a non-empty name before constructing the node.","Re-run `prisma-next contract emit` after fixing the schema so the now-named index is serialized."],"exampleFix":"// before\nindex({ expression: 'lower(name)', unique: true })   // no name -> error\n\n// after\nindex({ name: 'users_lower_name_idx', expression: 'lower(name)', unique: true })","handlingStrategy":"validation","validationCode":"import { nameOf, type SqlObjectNaming } from '@prisma-next/sql-schema-ir/naming';\n\nfunction hasExplicitIndexName(naming: SqlObjectNaming): boolean {\n  return nameOf(naming).length > 0;\n}\n\n// Before constructing `new Index({...expression, naming})`:\n// if (!hasExplicitIndexName(input.naming)) {\n//   throw new Error('Expression indexes require an explicit name — pass naming with a prefix or exact name.');\n// }","typeGuard":null,"tryCatchPattern":"import { ContractValidationError } from '@prisma-next/contract/contract-validation-error';\n\ntry {\n  const node = new Index(input);\n} catch (error) {\n  if (error instanceof ContractValidationError && /expression index must be explicitly named/.test(error.message)) {\n    // An expression index has no derivable default name. Supply naming.prefix yourself.\n    throw new Error('Pass an explicit naming.prefix when building an expression index.');\n  }\n  throw error;\n}","preventionTips":["Expression indexes cannot derive a name from their opaque SQL — always pass naming with a non-empty prefix or exact name.","When lowering from authoring DSL, compute a deterministic wire name (e.g. `${table}_${purpose}_idx`) for expression indexes.","For column indexes, a name can be auto-derived; for expression indexes, treat the name as a required authoring input.","Add a unit test that every expression index in your schema produces a non-empty nameOf(naming)."],"tags":["contract","validation","sql","index","authoring"],"analyzedSha":"1243cdffbec0506c595ac8fd5865fe258c336fa0","analyzedAt":"2026-08-01T19:42:02.087Z","schemaVersion":2}