From 50da29652d5d215cfbeb0bc7f43a0ae93a6f6662 Mon Sep 17 00:00:00 2001 From: Anton Kovalevich Date: Thu, 25 Mar 2021 15:08:48 +0300 Subject: [PATCH] Implement an `sqlalchemy.sql.expression.match` expression --- lib/sqlalchemy/dialects/mysql/base.py | 9 +++- lib/sqlalchemy/sql/elements.py | 67 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index e6052f69f7..7910415532 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -1588,7 +1588,14 @@ class MySQLCompiler(compiler.SQLCompiler): ) def visit_match_op_binary(self, binary, operator, **kw): - return "MATCH (%s) AGAINST (%s IN BOOLEAN MODE)" % ( + boolean_mode = kw.pop('boolean_mode', True) + + if boolean_mode: + template = "MATCH (%s) AGAINST (%s IN BOOLEAN MODE)" + else: + template = "MATCH (%s) AGAINST (%s)" + + return template % ( self.process(binary.left, **kw), self.process(binary.right, **kw), ) diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 20c3e89911..1f77d01580 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -177,6 +177,64 @@ def not_(clause): return operators.inv(coercions.expect(roles.ExpressionElementRole, clause)) +def match(*clauselist, against, **kwargs): + """Produce a ``MATCH (X, Y) AGAINST ('TEXT')`` clause. + + E.g.:: + + from sqlalchemy import match + + match_columns_where = match( + users_table.c.firstname, + users_table.c.lastname, + against="John Connor", + ) + + match_columns_order = match( + users_table.c.firstname, + users_table.c.lastname, + against="John Connor", + boolean_mode=False, + ) + + stmt = select(users_table)\ + .where(match_columns_where)\ + .order_by(match_columns_order) + + Would produce SQL resembling:: + + SELECT id, firstname, lastname FROM user + WHERE MATCH(firstname, lastname) + AGAINST (:param_1 IN BOOLEAN MODE) + ORDER BY MATCH(firstname, lastname) AGAINST (:param_2) + + The :func:`.match` function is a standalone version of the + :meth:`_expression.ColumnElement.match` method available on all + SQL expressions, as when :meth:`_expression.ColumnElement.match` is + used, but allows to pass multiple columns + + All positional arguments passed to :func:`.match`, should + be :class:`_expression.ColumnElement` subclass. + + :param clauselist: a column iterator, typically a + :class:`_expression.ColumnElement` instances or alternatively a Python + scalar expression to be coerced into a column expression, + serving as the ``MATCH`` side of expression. + + :param against: str. + + .. versionadded:: 1.4.4 + + .. seealso:: + + :meth:`_expression.ColumnElement.match` + + """ + + clause_batch = ClauseElementBatch(*clauselist, group=False) + return clause_batch.match(against, **kwargs) + + @inspection._self_inspects class ClauseElement( roles.SQLRole, @@ -2226,6 +2284,15 @@ class ClauseList( return self +class ClauseElementBatch(ClauseList, ColumnElement): + """Describe a batch of clauses, separated by an operator, but processing + as single column. + + By default, is comma-separated, such as a column listing. + + """ + + class BooleanClauseList(ClauseList, ColumnElement): __visit_name__ = "clauselist" inherit_cache = True -- 2.47.3