{"record":{"id":"cbc01096bea50264","repo":"sqlalchemy/alembic","slug":"string-or-text-construct-expected","errorCode":null,"errorMessage":"String or text() construct expected","messagePattern":"String or text\\(\\) construct expected","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"alembic/util/sqla_compat.py","lineNumber":398,"sourceCode":"        collection.remove(to_remove)\n\n\ndef _textual_index_column(\n    table: Table, text_: str | TextClause | ColumnElement[Any]\n) -> ColumnElement[Any] | Column[Any]:\n    \"\"\"a workaround for the Index construct's severe lack of flexibility\"\"\"\n    if isinstance(text_, str):\n        c = Column(text_, sqltypes.NULLTYPE)\n        table.append_column(c)\n        return c\n    elif isinstance(text_, TextClause):\n        return _textual_index_element(table, text_)\n    elif isinstance(text_, _textual_index_element):\n        return _textual_index_column(table, text_.text)\n    elif isinstance(text_, sql.ColumnElement):\n        return _copy_expression(text_, table)\n    else:\n        raise ValueError(\"String or text() construct expected\")\n\n\ndef _copy_expression(expression: _CE, target_table: Table) -> _CE:\n    def replace(col):\n        if (\n            isinstance(col, Column)\n            and col.table is not None\n            and col.table is not target_table\n        ):\n            if col.name in target_table.c:\n                return target_table.c[col.name]\n            else:\n                c = _copy(col)\n                target_table.append_column(c)\n                return c\n        else:\n            return None\n","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/util/sqla_compat.py#L380-L416","documentation":"Raised as ValueError by _textual_index_column when the text_ argument is not a str, TextClause, _textual_index_element, or ColumnElement (sqla_compat.py:383-398). This helper converts textual index column expressions into proper SQLAlchemy column objects for use in migrations. Passing an unsupported type means Alembic cannot interpret the index definition and refuses to proceed.","triggerScenarios":"Calling an Alembic operation that internally uses _textual_index_column with an invalid expression type — e.g., passing an integer, a raw SQL fragment object, or a custom expression that isn't a SQLAlchemy ColumnElement. This is used in index operations that accept textual column definitions.","commonSituations":"Using op.create_index with a non-standard expression type for a column argument. Passing a Python int/float where a column name string or text() construct is expected. Custom SQLAlchemy constructs that don't subclass ColumnElement. Version mismatches where an expression type changed between SQLAlchemy versions.","solutions":["Pass a string column name, sa.text('expression'), or a proper sa.Column expression to the index operation.","Wrap raw SQL fragments in sa.text(): use sa.text('lower(name)') instead of a bare object.","If using a custom expression, ensure it subclasses sqlalchemy.sql.ColumnElement.","Check the SQLAlchemy version compatibility — some expression types changed across major versions."],"exampleFix":"# before\nop.create_index('idx_name', 'users', [123])  # int not valid\n\n# after\nop.create_index('idx_name', 'users', ['name'])  # string column name\n# or\nop.create_index('idx_name', 'users', [sa.text('lower(name)')])  # text construct","handlingStrategy":"type-guard","validationCode":"import sqlalchemy as sa\nfrom sqlalchemy.sql.elements import ColumnElement, TextClause\n\ndef is_valid_index_expression(value):\n    return isinstance(value, (str, TextClause, ColumnElement))","typeGuard":"import sqlalchemy as sa\nfrom sqlalchemy.sql.elements import ColumnElement\nfrom sqlalchemy.sql.elements import TextClause\n\ndef is_valid_textual_index_arg(value) -> bool:\n    \"\"\"Type guard: True if value is acceptable for _textual_index_column.\"\"\"\n    return isinstance(value, (str, TextClause, ColumnElement))","tryCatchPattern":null,"preventionTips":["Always pass string column names or sa.text() constructs to index operations.","Avoid passing raw integers or non-SQLAlchemy objects as index column arguments.","When in doubt, wrap expressions in sa.text() to ensure they're valid TextClause objects."],"tags":["alembic","sqlalchemy","index","type-validation","valueerror"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}