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
- 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.
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
- 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).
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
- CONTRACT.VALIDATION_FAILED
- CONTRACT.VALIDATION_FAILED
- CONTRACT.VALIDATION_FAILED
- CONTRACT.VALIDATION_FAILED
- CONTRACT.VALIDATION_FAILED
AI-assisted analysis of prisma/prisma@1243cdffbe (2026-08-01).
Data as JSON: /data/errors/cf7341f3f10c2f93.json.
Report an issue: GitHub.