From: AlonM Date: Sun, 15 Nov 2020 13:13:25 +0000 (-0500) Subject: Add opsclass to exclusion constraint X-Git-Tag: rel_1_4_0b2~149^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=018aa9870110ef97316e9984c7ecd7f3357b501a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Add opsclass to exclusion constraint Added new parameter :paramref:`_postgresql.ExcludeConstraint.ops` to the :class:`_postgresql.ExcludeConstraint` object, to support operator class specification with this constraint. Pull request courtesy Alon Menczer. Fixes: #5604 Closes: #5700 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5700 Pull-request-sha: f8613e100792fc0bb3cf300ec6aebc78ecdf0361 Change-Id: Iaf664131ec84f8c2fb05edf799198b8d3bb83597 --- diff --git a/doc/build/changelog/unreleased_13/5604.rst b/doc/build/changelog/unreleased_13/5604.rst new file mode 100644 index 0000000000..dd2b52bce0 --- /dev/null +++ b/doc/build/changelog/unreleased_13/5604.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: usecase, postgresql + :tickets: 5604 + :versions: 1.4.0b2 + + Added new parameter :paramref:`_postgresql.ExcludeConstraint.ops` to the + :class:`_postgresql.ExcludeConstraint` object, to support operator class + specification with this constraint. Pull request courtesy Alon Menczer. diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index d738393e78..66fe2526ec 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -781,6 +781,8 @@ using the ``postgresql_where`` keyword argument:: Index('my_index', my_table.c.id, postgresql_where=my_table.c.value > 10) +.. _postgresql_operator_classes: + Operator Classes ^^^^^^^^^^^^^^^^ @@ -797,11 +799,10 @@ The :class:`.Index` construct allows these to be specified via the 'id': 'int4_ops' }) -Note that the keys in the ``postgresql_ops`` dictionary are the "key" name of -the :class:`_schema.Column`, i.e. the name used to access it from the ``.c`` -collection of :class:`_schema.Table`, -which can be configured to be different than -the actual name of the column as expressed in the database. +Note that the keys in the ``postgresql_ops`` dictionaries are the +"key" name of the :class:`_schema.Column`, i.e. the name used to access it from +the ``.c`` collection of :class:`_schema.Table`, which can be configured to be +different than the actual name of the column as expressed in the database. If ``postgresql_ops`` is to be used against a complex SQL expression such as a function call, then to apply to the column it must be given a label @@ -815,6 +816,14 @@ that is identified in the dictionary by name, e.g.:: 'id': 'int4_ops' }) +Operator classes are also supported by the +:class:`_postgresql.ExcludeConstraint` construct using the +:paramref:`_postgresql.ExcludeConstraint.ops` parameter. See that parameter for +details. + +.. versionadded:: 1.3.20 added support for operator classes with + :class:`_postgresql.ExcludeConstraint`. + Index Types ^^^^^^^^^^^ @@ -2417,9 +2426,13 @@ class PGDDLCompiler(compiler.DDLCompiler): elements = [] for expr, name, op in constraint._render_exprs: kw["include_table"] = False - elements.append( - "%s WITH %s" % (self.sql_compiler.process(expr, **kw), op) + exclude_element = self.sql_compiler.process(expr, **kw) + ( + (" " + constraint.ops[expr.key]) + if hasattr(expr, "key") and expr.key in constraint.ops + else "" ) + + elements.append("%s WITH %s" % (exclude_element, op)) text += "EXCLUDE USING %s (%s)" % ( self.preparer.validate_sql_phrase( constraint.using, IDX_USING diff --git a/lib/sqlalchemy/dialects/postgresql/ext.py b/lib/sqlalchemy/dialects/postgresql/ext.py index 78d9a96b67..4c8c3fc229 100644 --- a/lib/sqlalchemy/dialects/postgresql/ext.py +++ b/lib/sqlalchemy/dialects/postgresql/ext.py @@ -114,7 +114,8 @@ class ExcludeConstraint(ColumnCollectionConstraint): const = ExcludeConstraint( (Column('period'), '&&'), (Column('group'), '='), - where=(Column('group') != 'some group') + where=(Column('group') != 'some group'), + ops={'group': 'my_operator_class'} ) The constraint is normally embedded into the :class:`_schema.Table` @@ -133,7 +134,8 @@ class ExcludeConstraint(ColumnCollectionConstraint): (some_table.c.period, '&&'), (some_table.c.group, '='), where=some_table.c.group != 'some group', - name='some_table_excl_const' + name='some_table_excl_const', + ops={'group': 'my_operator_class'} ) ) @@ -171,6 +173,19 @@ class ExcludeConstraint(ColumnCollectionConstraint): If set, emit WHERE when issuing DDL for this constraint. + :param ops: + Optional dictionary. Used to define operator classes for the + elements; works the same way as that of the + :ref:`postgresql_ops ` + parameter specified to the :class:`_schema.Index` construct. + + .. versionadded:: 1.3.21 + + .. seealso:: + + :ref:`postgresql_operator_classes` - general description of how + PostgreSQL operator classes are specified. + """ columns = [] render_exprs = [] @@ -209,6 +224,8 @@ class ExcludeConstraint(ColumnCollectionConstraint): if where is not None: self.where = coercions.expect(roles.StatementOptionRole, where) + self.ops = kw.get("ops", {}) + def _set_parent(self, table, **kw): super(ExcludeConstraint, self)._set_parent(table) diff --git a/test/dialect/postgresql/test_compiler.py b/test/dialect/postgresql/test_compiler.py index dad7ccd3cb..a031c3df93 100644 --- a/test/dialect/postgresql/test_compiler.py +++ b/test/dialect/postgresql/test_compiler.py @@ -820,13 +820,14 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): where="room > 100", deferrable=True, initially="immediate", + ops={"room": "my_opclass"}, ) tbl.append_constraint(cons) self.assert_compile( schema.AddConstraint(cons), "ALTER TABLE testtbl ADD CONSTRAINT my_name " "EXCLUDE USING gist " - "(room WITH =, during WITH " + "(room my_opclass WITH =, during WITH " "&&) WHERE " "(room > 100) DEFERRABLE INITIALLY immediate", dialect=postgresql.dialect(), @@ -935,6 +936,24 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): dialect=postgresql.dialect(), ) + def test_exclude_constraint_ops_many(self): + m = MetaData() + tbl = Table( + "testtbl", m, Column("room", String), Column("during", TSRANGE) + ) + cons = ExcludeConstraint( + ("room", "="), + ("during", "&&"), + ops={"room": "first_opsclass", "during": "second_opclass"}, + ) + tbl.append_constraint(cons) + self.assert_compile( + schema.AddConstraint(cons), + "ALTER TABLE testtbl ADD EXCLUDE USING gist " + "(room first_opsclass WITH =, during second_opclass WITH &&)", + dialect=postgresql.dialect(), + ) + def test_substring(self): self.assert_compile( func.substring("abc", 1, 2),