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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions