Skip to content

Operator classes in index produces wrong SQL when using column labels #1603

Description

@jleguina

Describe the bug
Operator classes using labels don't generate the correct SQL code.

Simple case:

Index(
    "ix_chunks_vector",
    Chunk.vector.label("vector"), 
    postgresql_using="hnsw",
    postgresql_with={"m": 16, "ef_construction": 64},
    postgresql_ops={"vector": "halfvec_cosine_ops"},
)

Translates to Alembic as:

    op.create_index(
        "ix_chunks_vector",
        "chunks",
        [sa.text("vector")],
        unique=False,
        postgresql_using="hnsw",
        postgresql_with={"m": 16, "ef_construction": 64},
        postgresql_ops={"vector": "halfvec_cosine_ops"},
    )

Which becomes:

CREATE INDEX ix_chunks_vector 
ON chunks
USING hnsw (vector) 
WITH (m = 16, ef_construction = 64)

The operator class is not inserted correctly into the query: hnsw (vector) should be hnsw (vector halfvec_cosine_ops)

Expected behavior
The correct query generated should've been:

CREATE INDEX ix_chunks_vector 
ON chunks
USING hnsw (vector halfvec_cosine_ops) 
WITH (m = 16, ef_construction = 64)

To Reproduce

from __future__ import annotations
import uuid


from pgvector.sqlalchemy import Vector
from sqlalchemy import UUID, Index
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

from src.settings import settings


class Base(DeclarativeBase):
    __abstract__ = True
    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        primary_key=True,
        default=uuid.uuid4,
        unique=True,
        index=True,
    )


class Chunk(Base):
    __tablename__ = "chunks"

    vector: Mapped[list[float]] = mapped_column(
        Vector(settings.EMBEDDING_DIMS), nullable=False
    )


Index(
    "ix_chunks_vector",
    Chunk.vector.label("vector"),
    postgresql_using="hnsw",
    postgresql_with={"m": 16, "ef_construction": 64},
    postgresql_ops={"vector": "halfvec_cosine_ops"},
)

Migration:

"""test

Revision ID: f4718fa0302f
Revises: None
Create Date: 2025-02-07 12:50:53.195748+00:00

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import pgvector.sqlalchemy
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision: str = "f4718fa0302f"
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table(
        "chunks",
        sa.Column(
            "vector", pgvector.sqlalchemy.vector.VECTOR(dim=3072), nullable=False
        ),
        sa.Column("id", sa.UUID(), nullable=False),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index(op.f("ix_chunks_id"), "chunks", ["id"], unique=True)
    op.create_index(
        "ix_chunks_vector",
        "chunks",
        [sa.text("vector")],
        unique=False,
        postgresql_using="hnsw",
        postgresql_with={"m": 16, "ef_construction": 64},
        postgresql_ops={"vector": "halfvec_cosine_ops"},
    )
    # ### end Alembic commands ###


def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index(
        "ix_chunks_vector",
        table_name="chunks",
        postgresql_using="hnsw",
        postgresql_with={"m": 16, "ef_construction": 64},
        postgresql_ops={"vector": "halfvec_cosine_ops"},
    )
    op.drop_index(op.f("ix_chunks_id"), table_name="chunks")
    op.drop_table("chunks")
    # ### end Alembic commands ###

Error

INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade k631fb1nmb08 -> f4718fa0302f, test
Traceback (most recent call last):
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context
    self.dialect.do_execute(
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute
    cursor.execute(statement, parameters)
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/psycopg/cursor.py", line 97, in execute
    raise ex.with_traceback(None)
psycopg.errors.UndefinedObject: data type vector has no default operator class for access method "hnsw"
HINT:  You must specify an operator class for the index or define a default operator class for the data type.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/javier/Projects/modelml-app/backend/.venv/bin/alembic", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/config.py", line 636, in main
    CommandLine(prog=prog).main(argv=argv)
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/config.py", line 626, in main
    self.run_cmd(cfg, options)
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/config.py", line 603, in run_cmd
    fn(
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/command.py", line 406, in upgrade
    script.run_env()
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/script/base.py", line 586, in run_env
    util.load_python_file(self.dir, "env.py")
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/util/pyfiles.py", line 95, in load_python_file
    module = load_module_py(module_id, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/util/pyfiles.py", line 113, in load_module_py
    spec.loader.exec_module(module)  # type: ignore
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/Users/javier/Projects/modelml-app/backend/alembic/env.py", line 82, in <module>
    run_migrations_online()
  File "/Users/javier/Projects/modelml-app/backend/alembic/env.py", line 76, in run_migrations_online
    context.run_migrations()
  File "<string>", line 8, in run_migrations
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/runtime/environment.py", line 946, in run_migrations
    self.get_context().run_migrations(**kw)
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/runtime/migration.py", line 623, in run_migrations
    step.migration_fn(**kw)
  File "/Users/javier/Projects/modelml-app/backend/alembic/versions/2025_02_07_1250_test_f4718fa0302f.py", line 34, in upgrade
    op.create_index(
  File "<string>", line 8, in create_index
  File "<string>", line 3, in create_index
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/operations/ops.py", line 999, in create_index
    return operations.invoke(op)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/operations/base.py", line 442, in invoke
    return fn(self, operation)
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/operations/toimpl.py", line 114, in create_index
    operations.impl.create_index(idx, **kw)
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py", line 96, in create_index
    self._exec(CreateIndex(index, **kw))
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/alembic/ddl/impl.py", line 247, in _exec
    return conn.execute(construct, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
    return meth(
           ^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/sql/ddl.py", line 180, in _execute_on_connection
    return connection._execute_ddl(
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1527, in _execute_ddl
    ret = self._execute_context(
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1843, in _execute_context
    return self._exec_single_context(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1983, in _exec_single_context
    self._handle_dbapi_exception(
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2352, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context
    self.dialect.do_execute(
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute
    cursor.execute(statement, parameters)
  File "/Users/javier/Projects/modelml-app/backend/.venv/lib/python3.12/site-packages/psycopg/cursor.py", line 97, in execute
    raise ex.with_traceback(None)
sqlalchemy.exc.ProgrammingError: (psycopg.errors.UndefinedObject) data type vector has no default operator class for access method "hnsw"
HINT:  You must specify an operator class for the index or define a default operator class for the data type.
[SQL: CREATE INDEX ix_chunks_vector ON chunks USING hnsw (vector) WITH (m = 16, ef_construction = 64)]
(Background on this error at: https://sqlalche.me/e/20/f405)

Versions.

  • OS: MacOS 15.3
  • Python: 3.12.2
  • Alembic: 1.14.1
  • SQLAlchemy: 2.0.36
  • Database: PostgreSQL 16.4 (Debian 16.4-1.pgdg120+2)
  • DBAPI: psycopg 3.2.3

Additional context
Using pgvector extension to define Vector operations, but does not make a difference.

Have a nice day!

Metadata

Metadata

Assignees

No one assigned

    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