prisma/prisma · error · ContractValidationError

CONTRACT.VALIDATION_FAILED

CONTRACT.VALIDATION_FAILED

Error message

Index: every index carries a full physical name; an expression index must be explicitly named (a default name cannot be derived from an expression).

What it means

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).

Source

Thrown at packages/2-sql/1-core/contract/src/ir/sql-index.ts:78

 * alias one (e.g.
 * `import { Index as SqlIndexNode } from '@prisma-next/sql-contract/types'`).
 */
export class Index extends SqlNode {
  readonly name: string;
  readonly unique: boolean;
  /** Derived from the wire naming arm — presence is the naming-mode discriminator in the flat JSON. */
  declare readonly prefix?: string;
  declare readonly columns?: readonly string[];
  declare readonly expression?: string;
  declare readonly where?: string;
  declare readonly type?: string;
  declare readonly options?: Record<string, unknown>;

  constructor(input: IndexInput) {
    super();
    const name = nameOf(input.naming);
    if (name.length === 0) {
      throw new ContractValidationError(
        'Index: every index carries a full physical name; an expression index must be explicitly named (a default name cannot be derived from an expression).',
        'storage',
      );
    }
    if ((input.columns === undefined) === (input.expression === undefined)) {
      throw new ContractValidationError(
        `Index "${name}": exactly one of columns or expression must be set.`,
        'storage',
      );
    }
    this.name = name;
    this.unique = input.unique;
    if (input.naming.kind === 'wire') this.prefix = input.naming.prefix;
    if (input.columns !== undefined) this.columns = input.columns;
    if (input.expression !== undefined) this.expression = input.expression;
    if (input.where !== undefined) this.where = input.where;
    if (input.type !== undefined) this.type = input.type;
    if (input.options !== undefined) this.options = input.options;

View on GitHub (pinned to 1243cdffbe)

Solutions

  1. Give the expression index an explicit name (or wire-mode prefix) via the index authoring API.
  2. If you did not intend an expression index, switch to a column index (`columns: [...]`) so a default name can be derived.
  3. If you build Index nodes directly in an extension, ensure the passed `naming` resolves to a non-empty name before constructing the node.
  4. Re-run `prisma-next contract emit` after fixing the schema so the now-named index is serialized.

Example fix

// before
index({ expression: 'lower(name)', unique: true })   // no name -> error

// after
index({ name: 'users_lower_name_idx', expression: 'lower(name)', unique: true })
Defensive patterns

Strategy: validation

Validate before calling

import { nameOf, type SqlObjectNaming } from '@prisma-next/sql-schema-ir/naming';

function hasExplicitIndexName(naming: SqlObjectNaming): boolean {
  return nameOf(naming).length > 0;
}

// Before constructing `new Index({...expression, naming})`:
// if (!hasExplicitIndexName(input.naming)) {
//   throw new Error('Expression indexes require an explicit name — pass naming with a prefix or exact name.');
// }

Try / catch

import { ContractValidationError } from '@prisma-next/contract/contract-validation-error';

try {
  const node = new Index(input);
} catch (error) {
  if (error instanceof ContractValidationError && /expression index must be explicitly named/.test(error.message)) {
    // An expression index has no derivable default name. Supply naming.prefix yourself.
    throw new Error('Pass an explicit naming.prefix when building an expression index.');
  }
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of prisma/prisma@1243cdffbe (2026-08-01). Data as JSON: /data/errors/cf7341f3f10c2f93.json. Report an issue: GitHub.