8

我希望能够在我的 SQLAlchemy 映射对象之一的多个文本字段中进行全文搜索。我还希望我的映射对象支持外键和事务。

我打算使用 MySQL 来运行全文搜索。但是,我知道 MySQL 只能在不支持事务和外键的 MyISAM 表上运行全文搜索。

为了实现我的目标,我计划创建两个表。我的代码将如下所示:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    description = Column(Text)

users_myisam = Table('users_myisam', Base.metadata,
                     Column('id', Integer),
                     Column('name', String(50)),
                     Column('description', Text),
                     mysql_engine='MyISAM')

conn = Base.metadata.bind.connect()
conn.execute("CREATE FULLTEXT INDEX idx_users_ftxt \
              on users_myisam (name, description)")

然后,搜索我将运行这个:

q = 'monkey'
ft_search = users_myisam.select("MATCH (name,description) AGAINST ('%s')" % q)
result = ft_search.execute()
for row in result: print row

这似乎有效,但我有几个问题:

  1. 我创建两个表来解决我的问题的方法是否合理?有没有标准/更好/更清洁的方法来做到这一点?

  2. 是否有一种 SQLAlchemy 方法来创建全文索引,或者我最好像上面那样直接执行“CREATE FULLTEXT INDEX ...”?

  3. 看起来我在搜索/匹配查询时遇到了 SQL 注入问题。如何选择“SQLAlchemy 方式”来解决这个问题?

  4. 有没有一种干净的方法可以将 users_myisam 选择/匹配直接加入我的用户表并返回实际的用户实例,因为这是我真正想要的?

  5. 为了使我的 users_myisam 表与我的映射对象用户表保持同步,我在我的 User 类上使用 MapperExtension 并设置 before_insert、before_update 和 before_delete 方法来适当地更新 users_myisam 表是否有意义,或者是有更好的方法来做到这一点吗?

谢谢,迈克尔

4

1 回答 1

14

我创建两个表来解决我的问题的方法是否合理?有没有标准/更好/更清洁的方法来做到这一点?

我以前从未见过这种用例尝试过,因为重视事务和约束的开发人员往往首先使用 Postgresql。我知道这在您的特定情况下可能是不可能的。

是否有一种 SQLAlchemy 方法来创建全文索引,或者我最好像上面那样直接执行“CREATE FULLTEXT INDEX ...”?

conn.execute() 很好,但如果你想要更集成的东西,你可以使用 DDL() 构造,通读http://docs.sqlalchemy.org/en/rel_0_8/core/schema.html?highlight=ddl#定制-ddl了解详情

看起来我在搜索/匹配查询时遇到了 SQL 注入问题。如何选择“SQLAlchemy 方式”来解决这个问题?

注意:此配方适用于MATCH同时针对多列 - 如果您只有一列,请更简单地使用match()运算符。

基本上你可以使用 text() 构造:

from sqlalchemy import text, bindparam

users_myisam.select(
  text("MATCH (name,description) AGAINST (:value)", 
       bindparams=[bindparam('value', q)])
)

更全面地,您可以定义一个自定义构造:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import ClauseElement
from sqlalchemy import literal

class Match(ClauseElement):
    def __init__(self, columns, value):
        self.columns = columns
        self.value = literal(value)

@compiles(Match)
def _match(element, compiler, **kw):
    return "MATCH (%s) AGAINST (%s)" % (
               ", ".join(compiler.process(c, **kw) for c in element.columns),
               compiler.process(element.value)
             )

my_table.select(Match([my_table.c.a, my_table.c.b], "some value"))

文档:

http://docs.sqlalchemy.org/en/rel_0_8/core/compiler.html

有没有一种干净的方法可以将 users_myisam 选择/匹配直接加入我的用户表并返回实际的用户实例,因为这是我真正想要的?

您可能应该创建一个 UserMyISAM 类,像 User 一样映射它,然后使用 relationship() 将两个类链接在一起,然后像这样的简单操作是可能的:

query(User).join(User.search_table).\
           filter(Match([UserSearch.x, UserSearch.y], "some value"))

为了使我的 users_myisam 表与我的映射对象用户表保持同步,我在我的 User 类上使用 MapperExtension 并设置 before_insert、before_update 和 before_delete 方法来适当地更新 users_myisam 表是否有意义,或者是有更好的方法来做到这一点吗?

MapperExtensions 已被弃用,因此您至少会使用事件 API,并且在大多数情况下,我们希望尝试在刷新过程之外应用对象突变。在这种情况下,我将使用 User 的构造函数,或者使用init 事件,以及一个基本的@validates装饰器,它将接收 User 上目标属性的值并将这些值复制到User.search_table.

总的来说,如果您一直在从其他来源(如 Oreilly 书)学习 SQLAlchemy,那么它确实已经过时多年了,我将专注于当前的在线文档。

于 2013-02-24T18:03:51.023 回答