Skip to content

T-SQL CreateIndex.where() is omitted for filtered indexes #624

Description

@cofin

Summary

CreateIndex.where() is available on the SQLSpec DDL builder, but the predicate is not emitted when rendering a SQL Server / T-SQL index. This means filtered unique indexes still need raw SQL even though the builder already models both if_not_exists() and where().

Environment

  • SQLSpec: 0.54.2
  • Python: 3.14.4
  • Dialect: tsql

Reproduction

from sqlspec import sql

stmt = (
    sql.create_index("ix_queue_tasks_task_key")
    .if_not_exists()
    .unique()
    .on_table("queue_tasks")
    .columns("task_key")
    .where("task_key IS NOT NULL")
)

print(stmt.build(dialect="tsql").sql)

Actual output

IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = object_id('queue_tasks') AND name = 'ix_queue_tasks_task_key') EXEC('CREATE UNIQUE INDEX [ix_queue_tasks_task_key] ON [queue_tasks]([task_key])')

The T-SQL IF NOT EXISTS ... EXEC(...) guard is rendered, but the filtered-index predicate is missing. Rendering without if_not_exists() also omits the predicate:

stmt = (
    sql.create_index("ix_queue_tasks_task_key")
    .unique()
    .on_table("queue_tasks")
    .columns("task_key")
    .where("task_key IS NOT NULL")
)
print(stmt.build(dialect="tsql").sql)

Actual:

CREATE UNIQUE INDEX [ix_queue_tasks_task_key] ON [queue_tasks]([task_key])

Expected output

For T-SQL, the generated index DDL should include the predicate, for example:

IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = object_id('queue_tasks') AND name = 'ix_queue_tasks_task_key') EXEC('CREATE UNIQUE INDEX [ix_queue_tasks_task_key] ON [queue_tasks]([task_key]) WHERE [task_key] IS NOT NULL')

For the non-guarded case:

CREATE UNIQUE INDEX [ix_queue_tasks_task_key] ON [queue_tasks]([task_key]) WHERE [task_key] IS NOT NULL

Use case

litestar-queues needs a nullable unique task key on SQL Server. SQL Server requires a filtered unique index (WHERE task_key IS NOT NULL) to allow multiple rows with NULL task keys while enforcing uniqueness for keyed tasks. Because the T-SQL builder drops .where(...), that backend still has to carry hand-written raw T-SQL for this DDL.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions