SQLAlchemy中的编译扩展:索引中使用的函数

时间:2014-03-03 18:56:14

标签: python sql sqlalchemy

我想在SQLAlchemy中创建一个不区分大小写的唯一约束,它可以与Postgres和SQLite一起使用。

在Postgres中实现了:

  • SQL:CREATE UNIQUE INDEX my_index ON my_table (lower(my_field));
  • SQLAlchemy:Index('my_index', func.lower(my_field), unique=True)

在SQLite中使用以下方法实现:

  • SQL:CREATE UNIQUE INDEX my_index ON my_table (my_field COLLATE NOCASE);
  • SQLAlchemy:Index('my_index', collate(my_field, 'nocase'), unique=True)

所以我试图将FunctionElement子类化,以便创建我自己的类似函数的构造,根据DBMS,它可以编译为my_field COLLATE NOCASElower(my_field)。我试图跟随the guide to compilation extension,但我收到一个错误(请参阅下面的回溯),我可以通过SQLAlchemy源代码来查看,但我无法理解。我尝试了对其他内容进行子类化,例如ColumnElementColumnClauseClauseElement,但我遇到了类似的错误。

以下是重现错误的代码。我使用的是SQLAlchemy的0.8.2版本。

from sqlalchemy import create_engine, Column, String, Integer
from sqlalchemy.types import TypeDecorator, VARCHAR
from sqlalchemy import func, Index, collate
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import (
    # Not sure which one to subclass
    FunctionElement, ColumnElement, ColumnClause, ClauseElement
)


engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base()


class CaseInsensitive(FunctionElement):
    name = 'CaseInsensitive'
    type = VARCHAR()


@compiles(CaseInsensitive, 'sqlite')
def case_insensitive_sqlite(element, compiler, **kw):
    arg1, = list(element.clauses)
    return collate(compiler.process(arg1), 'nocase')


@compiles(CaseInsensitive, 'postgresql')
def case_insensitive_postgresql(element, compiler, **kw):
    arg1, = list(element.clauses)
    return func.lower(compiler.process(arg1))


class MyTable(Base):

    __tablename__ = 'my_table'

    id = Column(Integer, primary_key=True)

    my_field = Column(String)

    __table_args__ = (
        Index('idx', CaseInsensitive(my_field), unique=True),
    )

Traceback (most recent call last):
  File "tmp.py", line 33, in <module>
    class MyTable(Base):
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py", line 50, in __init__
    _as_declarative(cls, classname, cls.__dict__)
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 222, in _as_declarative
    **table_kw)
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/schema.py", line 332, in __new__
    table._init(name, metadata, *args, **kw)
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/schema.py", line 400, in _init
    self._init_items(*args)
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/schema.py", line 65, in _init_items
    item._set_parent_with_dispatch(self)
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/events.py", line 236, in _set_parent_with_dispatch
    self._set_parent(parent)
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/schema.py", line 2421, in _set_parent
    ColumnCollectionMixin._set_parent(self, table)
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/schema.py", line 2035, in _set_parent
    self.columns.add(col)
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/sql/expression.py", line 2477, in add
    self[column.key] = column
  File "/Users/vladimir/git/wb/lib/python2.7/site-packages/sqlalchemy/sql/expression.py", line 2296, in __getattr__
    key)
AttributeError: Neither 'CaseInsensitive' object nor 'Comparator' object has an attribute 'key'

1 个答案:

答案 0 :(得分:4)

@compiles方法必须返回一个字符串。此外,在这里如何遍历功能元素有一些小问题所以我们需要一个小的解决方法:

class CaseInsensitive(FunctionElement):
    __visit_name__ = 'notacolumn'
    name = 'CaseInsensitive'
    type = VARCHAR()


@compiles(CaseInsensitive, 'sqlite')
def case_insensitive_sqlite(element, compiler, **kw):
    arg1, = list(element.clauses)
    return compiler.process(collate(arg1, 'nocase'), **kw)


@compiles(CaseInsensitive, 'postgresql')
def case_insensitive_postgresql(element, compiler, **kw):
    arg1, = list(element.clauses)
    return compiler.process(func.lower(arg1), **kw)