From a54848274a8f3b071a7cd9b7412cabfcf6317fdb Mon Sep 17 00:00:00 2001 From: h30054849 Date: Sat, 7 Sep 2024 16:50:24 +0800 Subject: [PATCH 01/11] =?UTF-8?q?openGaussSqlalchemy=E9=80=82=E9=85=8D2.0S?= =?UTF-8?q?qlalchemy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opengauss_sqlalchemy/base.py | 11 ++++++++--- opengauss_sqlalchemy/psycopg2.py | 13 ++++--------- setup.cfg | 8 ++++---- setup.py | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/opengauss_sqlalchemy/base.py b/opengauss_sqlalchemy/base.py index ae6c9d8..5ceebd5 100644 --- a/opengauss_sqlalchemy/base.py +++ b/opengauss_sqlalchemy/base.py @@ -7,7 +7,7 @@ # This module is part of SQLAlchemy and is released under # the MIT License: https://www.opensource.org/licenses/mit-license.php -from sqlalchemy.dialects.postgresql.base import IDX_USING, PGDDLCompiler, PGIdentifierPreparer +from sqlalchemy.dialects.postgresql.base import IDX_USING, PGCompiler, PGDDLCompiler, PGIdentifierPreparer from sqlalchemy.dialects.postgresql.base import RESERVED_WORDS as _RESERVED_WORDS from sqlalchemy.sql import coercions, expression, roles from sqlalchemy import types @@ -51,10 +51,15 @@ RESERVED_WORDS = _RESERVED_WORDS.union( ) +class OpenGaussCompiler(PGCompiler): + def get_cte_preamble(self, recursive): + return "WITH RECURSIVE" + + class OpenGaussDDLCompiler(PGDDLCompiler): """DDLCompiler for opengauss""" - def visit_create_index(self, create): + def visit_create_index(self, create, **kw): preparer = self.preparer index = create.element self._verify_index_table(index) @@ -143,7 +148,7 @@ class OpenGaussDDLCompiler(PGDDLCompiler): return "".join(text_contents) - def visit_drop_index(self, drop): + def visit_drop_index(self, drop, **kw): index = drop.element text_contents = ["\nDROP INDEX "] diff --git a/opengauss_sqlalchemy/psycopg2.py b/opengauss_sqlalchemy/psycopg2.py index f4a7be9..a519d26 100644 --- a/opengauss_sqlalchemy/psycopg2.py +++ b/opengauss_sqlalchemy/psycopg2.py @@ -8,10 +8,10 @@ # the MIT License: https://www.opensource.org/licenses/mit-license.php from sqlalchemy import schema from sqlalchemy import util -from sqlalchemy.dialects.postgresql.psycopg2 import PGCompiler_psycopg2, PGDialect_psycopg2 +from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2 from sqlalchemy.ext.compiler import compiles -from opengauss_sqlalchemy.base import OpenGaussDDLCompiler, OpenGaussIdentifierPreparer +from opengauss_sqlalchemy.base import OpenGaussDDLCompiler, OpenGaussIdentifierPreparer, OpenGaussCompiler # If alembic is installed, register an alias in its dialect mapping. try: @@ -74,11 +74,6 @@ else: migrate_dialects["opengauss"] = OGDialect -class OpenGaussCompiler_psycopg2(PGCompiler_psycopg2): - def get_cte_preamble(self, recursive): - return "WITH RECURSIVE" - - class OpenGaussDialect_psycopg2(PGDialect_psycopg2): name = "opengauss" driver = "psycopg2" @@ -86,7 +81,7 @@ class OpenGaussDialect_psycopg2(PGDialect_psycopg2): cte_follows_insert = True supports_statement_cache = True - statement_compiler = OpenGaussCompiler_psycopg2 + statement_compiler = OpenGaussCompiler ddl_compiler = OpenGaussDDLCompiler preparer = OpenGaussIdentifierPreparer @@ -123,7 +118,7 @@ class OpenGaussDialect_psycopg2(PGDialect_psycopg2): @util.memoized_property def _isolation_lookup(self): - extensions = self._psycopg2_extensions() + extensions = self._psycopg2_extensions return { "AUTOCOMMIT": extensions.ISOLATION_LEVEL_AUTOCOMMIT, diff --git a/setup.cfg b/setup.cfg index aded715..d6ea2c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,7 @@ requirement_cls=opengauss_sqlalchemy.requirements:Requirements profile_file=test/profiles.txt [db] -default = opengauss://scott:Tiger123@127.0.0.1:37200/test -opengauss = opengauss+psycopg2://scott:Tiger123@127.0.0.1:37200/test -opengauss_psycopg2 = opengauss+psycopg2://scott:Tiger123@127.0.0.1:37200/test -opengauss_dc_psycopg2 = opengauss+dc_psycopg2://scott:Tiger123@127.0.0.1:37500/test +default = opengauss://hct:Huawei12#$@localhost:36783/t2 +opengauss = opengauss+psycopg2://hct:Huawei12#$@localhost:36783/t2 +opengauss_psycopg2 = opengauss+psycopg2://hct:Huawei12#$@localhost:36783/t2 +opengauss_dc_psycopg2 = opengauss+dc_psycopg2://hct:Huawei12#$@localhost:36783/t2 diff --git a/setup.py b/setup.py index 2d3e835..8087019 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ setup( ], packages=["opengauss_sqlalchemy"], include_package_data=True, - install_requires=["SQLAlchemy<2.0", "psycopg2>=2.8"], + install_requires=["SQLAlchemy<=2.0.23", "psycopg2>=2.8"], entry_points={ "sqlalchemy.dialects": [ "opengauss = opengauss_sqlalchemy.psycopg2:OpenGaussDialect_psycopg2", -- Gitee From 0c99efa1d83017a59e1824d805d7b595f2ac5b70 Mon Sep 17 00:00:00 2001 From: h30054849 Date: Wed, 11 Sep 2024 11:49:25 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8Dsqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opengauss_sqlalchemy/requirements.py | 6 + test/test_compiler.py | 2 +- test/test_suite.py | 220 +++++++++++++++++++++------ 3 files changed, 180 insertions(+), 48 deletions(-) diff --git a/opengauss_sqlalchemy/requirements.py b/opengauss_sqlalchemy/requirements.py index 3085b38..7fcbdcd 100644 --- a/opengauss_sqlalchemy/requirements.py +++ b/opengauss_sqlalchemy/requirements.py @@ -1053,3 +1053,9 @@ class Requirements(SuiteRequirements): Use `limit` with `order_by` if you need strict isotonicity. """ return exclusions.only_on(["opengauss+psycopg2"]) + + @property + def reflect_indexes_with_expressions(self): + """target database supports reflection of indexes with + SQL expressions.""" + return exclusions.open() diff --git a/test/test_compiler.py b/test/test_compiler.py index ce1ccc7..e85d426 100644 --- a/test/test_compiler.py +++ b/test/test_compiler.py @@ -377,7 +377,7 @@ class DDLCompilerTest(fixtures.TestBase, AssertsCompiledSQL): idx1 = Index("test_idx1", 5 / (tbl.c.x + tbl.c.y)) self.assert_compile( schema.CreateIndex(idx1), - "CREATE INDEX test_idx1 ON testtbl ((5 / (x + y)))", + "CREATE INDEX test_idx1 ON testtbl ((5 / CAST((x + y) AS NUMERIC)))", ) def test_create_index_literals(self): diff --git a/test/test_suite.py b/test/test_suite.py index c3c295b..f665b84 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -89,32 +89,30 @@ class ComponentReflectionTest(_ComponentReflectionTest): schema_prefix = "" if testing.requires.self_referential_foreign_keys.enabled: - users = Table( - "users", - metadata, - Column("user_id", sa.INT, primary_key=True), - Column("test1", sa.CHAR(5), nullable=False), - Column("test2", sa.Float(5), nullable=False), - Column( - "parent_user_id", - sa.Integer, - sa.ForeignKey( - "%susers.user_id" % schema_prefix, name="user_id_fk" - ), + parent_id_args = ( + ForeignKey( + "%susers.user_id" % schema_prefix, name="user_id_fk" ), - schema=schema, - test_needs_fk=True, ) else: - users = Table( - "users", - metadata, - Column("user_id", sa.INT, primary_key=True), - Column("test1", sa.CHAR(5), nullable=False), - Column("test2", sa.Float(5), nullable=False), - schema=schema, - test_needs_fk=True, - ) + parent_id_args = () + + users = Table( + "users", + metadata, + Column("user_id", sa.INT, primary_key=True), + Column("test1", sa.CHAR(5), nullable=False), + Column("test2", sa.Float(), nullable=False), + Column("parent_user_id", sa.Integer, *parent_id_args), + sa.CheckConstraint( + "test2 > 0", + name="zz_test2_gt_zero", + comment="users check constraint", + ), + sa.CheckConstraint("test2 <= 1000"), + schema=schema, + test_needs_fk=True, + ) if testing.requires.foreign_keys.enabled: # distributed opengauss does NOT support foreign keys @@ -125,9 +123,28 @@ class ComponentReflectionTest(_ComponentReflectionTest): Column( "address_id", sa.Integer, - sa.ForeignKey("%semail_addresses.address_id" % schema_prefix), + ForeignKey( + "%semail_addresses.address_id" % schema_prefix, + name="zz_email_add_id_fg", + comment="di fk comment", + ), + ), + Column( + "id_user", + sa.Integer, + ForeignKey("%susers.user_id" % schema_prefix), ), Column("data", sa.String(30)), + sa.CheckConstraint( + "address_id > 0 AND address_id < 1000", + name="address_id_gt_zero", + ), + sa.UniqueConstraint( + "address_id", + "dingaling_id", + name="zz_dingalings_multiple", + comment="di unique comment", + ), schema=schema, test_needs_fk=True, ) @@ -135,11 +152,11 @@ class ComponentReflectionTest(_ComponentReflectionTest): "email_addresses", metadata, Column("address_id", sa.Integer), - Column( - "remote_user_id", sa.Integer, sa.ForeignKey(users.c.user_id) + Column("remote_user_id", sa.Integer, ForeignKey(users.c.user_id)), + Column("email_address", sa.String(20), index=True), + sa.PrimaryKeyConstraint( + "address_id", name="email_ad_pk", comment="ea pk comment" ), - Column("email_address", sa.String(20)), - sa.PrimaryKeyConstraint("address_id", name="email_ad_pk"), schema=schema, test_needs_fk=True, ) @@ -152,7 +169,21 @@ class ComponentReflectionTest(_ComponentReflectionTest): "address_id", sa.Integer, ), + Column( + "id_user", + sa.Integer, + ), Column("data", sa.String(30)), + sa.CheckConstraint( + "address_id > 0 AND address_id < 1000", + name="address_id_gt_zero", + ), + sa.UniqueConstraint( + "address_id", + "dingaling_id", + name="zz_dingalings_multiple", + comment="di unique comment", + ), schema=schema, test_needs_fk=True, ) @@ -160,11 +191,11 @@ class ComponentReflectionTest(_ComponentReflectionTest): "email_addresses", metadata, Column("address_id", sa.Integer), - Column( - "remote_user_id", sa.Integer + Column("remote_user_id", sa.Integer), + Column("email_address", sa.String(20), index=True), + sa.PrimaryKeyConstraint( + "address_id", name="email_ad_pk", comment="ea pk comment" ), - Column("email_address", sa.String(20)), - sa.PrimaryKeyConstraint("address_id", name="email_ad_pk"), schema=schema, test_needs_fk=True, ) @@ -178,9 +209,17 @@ class ComponentReflectionTest(_ComponentReflectionTest): sa.String(20), comment=r"""Comment types type speedily ' " \ '' Fun!""", ), + Column("d3", sa.String(42), comment="Comment\nwith\rescapes"), schema=schema, comment=r"""the test % ' " \ table comment""", ) + Table( + "no_constraints", + metadata, + Column("data", sa.String(20)), + schema=schema, + comment="no\nconstraints\rhas\fescaped\vcomment", + ) if testing.requires.cross_schema_fk_reflection.enabled: if schema is None: @@ -224,7 +263,10 @@ class ComponentReflectionTest(_ComponentReflectionTest): ) if testing.requires.index_reflection.enabled: - cls.define_index(metadata, users) + Index("users_t_idx", users.c.test1, users.c.test2, unique=True) + Index( + "users_all_idx", users.c.user_id, users.c.test2, users.c.test1 + ) if not schema: # test_needs_fk is at the moment to force MySQL InnoDB @@ -243,7 +285,10 @@ class ComponentReflectionTest(_ComponentReflectionTest): test_needs_fk=True, ) - if testing.requires.indexes_with_ascdesc.enabled: + if ( + testing.requires.indexes_with_ascdesc.enabled + and testing.requires.reflect_indexes_with_ascdesc.enabled + ): Index("noncol_idx_nopk", noncol_idx_test_nopk.c.q.desc()) Index("noncol_idx_pk", noncol_idx_test_pk.c.q.desc()) @@ -255,9 +300,7 @@ class ComponentReflectionTest(_ComponentReflectionTest): @classmethod def define_temp_tables(cls, metadata): kw = temp_table_keyword_args(config, config.db) - table_name = get_temp_table_name( - config, config.db, "user_tmp_%s" % config.ident - ) + table_name = cls.temp_table_name() user_tmp = Table( table_name, metadata, @@ -271,7 +314,7 @@ class ComponentReflectionTest(_ComponentReflectionTest): # unique constraints created against temp tables in different # databases. # https://www.arbinada.com/en/node/1645 - sa.UniqueConstraint("name", name="user_tmp_uq_%s" % config.ident), + sa.UniqueConstraint("name", name=f"user_tmp_uq_{config.ident}"), sa.Index("user_tmp_ix", "foo"), **kw ) @@ -369,20 +412,21 @@ class ComponentReflectionTest(_ComponentReflectionTest): # "unique constraints" are actually unique indexes (with possible # exception of a unique that is a dupe of another one in the case # of Oracle). make sure # they aren't duplicated. - idx_names = set([idx.name for idx in reflected.indexes]) - uq_names = set( - [ - uq.name - for uq in reflected.constraints - if isinstance(uq, sa.UniqueConstraint) - ] - ).difference(["unique_c_a_b"]) + idx_names = {idx.name for idx in reflected.indexes} + uq_names = { + uq.name + for uq in reflected.constraints + if isinstance(uq, sa.UniqueConstraint) + }.difference(["unique_c_a_b"]) assert not idx_names.intersection(uq_names) if names_that_duplicate_index: eq_(names_that_duplicate_index, idx_names) eq_(uq_names, set()) + no_cst = self.tables.no_constraints.name + eq_(insp.get_unique_constraints(no_cst, schema=schema), []) + class CompositeKeyReflectionTest(_CompositeKeyReflectionTest): @classmethod @@ -504,11 +548,93 @@ class ComponentReflectionTestExtra(_ComponentReflectionTestExtra): # that can reflect these, since alembic looks for this opts = insp.get_foreign_keys("table")[0]["options"] - eq_(dict((k, opts[k]) for k in opts if opts[k]), {}) + eq_({k: opts[k] for k in opts if opts[k]}, {}) opts = insp.get_foreign_keys("user")[0]["options"] eq_(opts, expected) + @testing.requires.indexes_with_expressions + def test_reflect_expression_based_indexes(self, metadata, connection): + t = Table( + "t", + metadata, + Column("x", String(30)), + Column("y", String(30)), + Column("z", String(30)), + ) + + Index("t_idx", func.lower(t.c.x), t.c.z, func.lower(t.c.y)) + long_str = "long string " * 100 + Index("t_idx_long", func.coalesce(t.c.x, long_str)) + Index("t_idx_2", t.c.x) + + metadata.create_all(connection) + + insp = inspect(connection) + + expected = [ + { + "name": "t_idx_2", + "unique": False, + "column_names": ["x"], + } + ] + + def completeIndex(entry): + if testing.requires.index_reflects_included_columns.enabled: + entry["include_columns"] = [] + entry["dialect_options"] = { + f"{connection.engine.name}_include": [] + } + else: + entry.setdefault("dialect_options", {}) + + class lower_index_str(str): + def __eq__(self, other): + ol = other.lower() + # test that lower and x or y are in the string + return "lower" in ol and ("x" in ol or "y" in ol) + + class coalesce_index_str(str): + def __eq__(self, other): + # test that coalesce and the string is in other + return "coalesce" in other.lower() and long_str in other + + if testing.requires.reflect_indexes_with_expressions.enabled: + expr_index = { + "name": "t_idx", + "unique": False, + "column_names": [None, "z", None], + "expressions": [ + lower_index_str("lower(x)"), + "z", + lower_index_str("lower(y)"), + ], + } + expected.insert(0, expr_index) + + expr_index_long = { + "name": "t_idx_long", + "unique": False, + "column_names": [None], + "expressions": [ + coalesce_index_str(f"coalesce(x, '{long_str}' ::character varying))") + ], + } + expected.append(expr_index_long) + + eq_(insp.get_indexes("t"), expected) + m2 = MetaData() + t2 = Table("t", m2, autoload_with=connection) + else: + eq_(insp.get_indexes("t"), expected) + m2 = MetaData() + t2 = Table("t", m2, autoload_with=connection) + + self.compare_table_index_with_expected( + t2, expected, connection.engine.name + ) + class QuotedNameArgumentTest(_QuotedNameArgumentTest): @classmethod -- Gitee From 85d1e67134084cff825d026f129b11e5bb9c22ef Mon Sep 17 00:00:00 2001 From: h30054849 Date: Thu, 12 Sep 2024 09:19:50 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-2=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opengauss_sqlalchemy/psycopg2.py | 129 ++++++++++++++++++++ opengauss_sqlalchemy/requirements.py | 5 + setup.cfg | 4 + setup.py | 2 +- test/test_suite.py | 168 ++++++++++++++++++++++++++- 5 files changed, 304 insertions(+), 4 deletions(-) diff --git a/opengauss_sqlalchemy/psycopg2.py b/opengauss_sqlalchemy/psycopg2.py index a519d26..dddb40e 100644 --- a/opengauss_sqlalchemy/psycopg2.py +++ b/opengauss_sqlalchemy/psycopg2.py @@ -10,6 +10,8 @@ from sqlalchemy import schema from sqlalchemy import util from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2 from sqlalchemy.ext.compiler import compiles +from collections import defaultdict +from sqlalchemy.engine.reflection import ReflectionDefaults from opengauss_sqlalchemy.base import OpenGaussDDLCompiler, OpenGaussIdentifierPreparer, OpenGaussCompiler @@ -132,5 +134,132 @@ class OpenGaussDialect_psycopg2(PGDialect_psycopg2): # most of opengauss features are same with postgres 9.2.4 return (9, 2, 4) + def get_multi_indexes( + self, connection, schema, filter_names, scope, kind, **kw + ): + table_oids = self._get_table_oids( + connection, schema, filter_names, scope, kind, **kw + ) + + indexes = defaultdict(list) + default = ReflectionDefaults.indexes + + batches = list(table_oids) + + while batches: + batch = batches[0:3000] + batches[0:3000] = [] + + result = connection.execute( + self._index_query, {"oids": [r[0] for r in batch]} + ).mappings() + + result_by_oid = defaultdict(list) + for row_dict in result: + result_by_oid[row_dict["indrelid"]].append(row_dict) + + for oid, table_name in batch: + if oid not in result_by_oid: + # ensure that each table has an entry, even if reflection + # is skipped because not supported + indexes[(schema, table_name)] = default() + continue + + for row in result_by_oid[oid]: + index_name = row["relname_index"] + + table_indexes = indexes[(schema, table_name)] + + all_elements = row["elements"] + all_elements_is_expr = row["elements_is_expr"] + indnkeyatts = row["indnkeyatts"] + # "The number of key columns in the index, not counting any + # included columns, which are merely stored and do not + # participate in the index semantics" + if indnkeyatts and len(all_elements) > indnkeyatts: + # this is a "covering index" which has INCLUDE columns + # as well as regular index columns + inc_cols = all_elements[indnkeyatts:] + idx_elements = all_elements[:indnkeyatts] + idx_elements_is_expr = all_elements_is_expr[ + :indnkeyatts + ] + # postgresql does not support expression on included + # columns as of v14: "ERROR: expressions are not + # supported in included columns". + assert all( + not is_expr + for is_expr in all_elements_is_expr[indnkeyatts:] + ) + else: + idx_elements = all_elements + idx_elements_is_expr = all_elements_is_expr + inc_cols = [] + + index = {"name": index_name, "unique": row["indisunique"]} + if any(idx_elements_is_expr): + index["column_names"] = [ + None if is_expr else expr + for expr, is_expr in zip( + idx_elements, idx_elements_is_expr + ) + ] + index["expressions"] = idx_elements + else: + index["column_names"] = idx_elements + + sorting = {} + for col_index, col_flags in enumerate(row["indoption"]): + col_sorting = () + # try to set flags only if they differ from PG + # defaults... + if col_flags & 0x01: + col_sorting += ("desc",) + if not (col_flags & 0x02): + col_sorting += ("nulls_last",) + else: + if col_flags & 0x02: + col_sorting += ("nulls_first",) + if col_sorting: + sorting[idx_elements[col_index]] = col_sorting + if sorting: + index["column_sorting"] = sorting + if row["has_constraint"]: + index["duplicates_constraint"] = index_name + + dialect_options = {} + if row["reloptions"]: + dialect_options["postgresql_with"] = dict( + [option.split("=") for option in row["reloptions"]] + ) + # it *might* be nice to include that this is 'btree' in the + # reflection info. But we don't want an Index object + # to have a ``postgresql_using`` in it that is just the + # default, so for the moment leaving this out. + amname = row["amname"] + if amname != "btree": + dialect_options["postgresql_using"] = row["amname"] + if row["filter_definition"]: + dialect_options["postgresql_where"] = row[ + "filter_definition" + ] + if self.server_version_info >= (11,): + # NOTE: this is legacy, this is part of + # dialect_options now as of #7382 + index["include_columns"] = inc_cols + dialect_options["postgresql_include"] = inc_cols + if row["indnullsnotdistinct"]: + # the default is False, so ignore it. + dialect_options["postgresql_nulls_not_distinct"] = row[ + "indnullsnotdistinct" + ] + + if dialect_options: + index["dialect_options"] = dialect_options + + table_indexes.append(index) + return indexes.items() + + dialect = OpenGaussDialect_psycopg2 diff --git a/opengauss_sqlalchemy/requirements.py b/opengauss_sqlalchemy/requirements.py index 7fcbdcd..4e1646e 100644 --- a/opengauss_sqlalchemy/requirements.py +++ b/opengauss_sqlalchemy/requirements.py @@ -1059,3 +1059,8 @@ class Requirements(SuiteRequirements): """target database supports reflection of indexes with SQL expressions.""" return exclusions.open() + + @property + def index_reflection(self): + return exclusions.open() + diff --git a/setup.cfg b/setup.cfg index d6ea2c9..04a0613 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,3 +14,7 @@ default = opengauss://hct:Huawei12#$@localhost:36783/t2 opengauss = opengauss+psycopg2://hct:Huawei12#$@localhost:36783/t2 opengauss_psycopg2 = opengauss+psycopg2://hct:Huawei12#$@localhost:36783/t2 opengauss_dc_psycopg2 = opengauss+dc_psycopg2://hct:Huawei12#$@localhost:36783/t2 + + +[easy_install] +index_url = http://mirrors.aliyun.com/pypi/simple/ \ No newline at end of file diff --git a/setup.py b/setup.py index 8087019..ff6d899 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ setup( ], packages=["opengauss_sqlalchemy"], include_package_data=True, - install_requires=["SQLAlchemy<=2.0.23", "psycopg2>=2.8"], + install_requires=["sqlalchemy<=2.0.23", "psycopg2>=2.8"], entry_points={ "sqlalchemy.dialects": [ "opengauss = opengauss_sqlalchemy.psycopg2:OpenGaussDialect_psycopg2", diff --git a/test/test_suite.py b/test/test_suite.py index f665b84..7b95706 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -25,6 +25,8 @@ from sqlalchemy.testing.suite.test_results import ServerSideCursorsTest as _Serv from sqlalchemy.testing.suite.test_select import FetchLimitOffsetTest as _FetchLimitOffsetTest from sqlalchemy.testing.suite.test_select import JoinTest as _JoinTest from sqlalchemy.testing.suite.test_unicode_ddl import UnicodeSchemaTest as _UnicodeSchemaTest +from sqlalchemy.engine import ObjectScope, ObjectKind, Inspector +from sqlalchemy.testing import mock class CTETest(_CTETest): @@ -79,6 +81,34 @@ class LongNameBlowoutTest(_LongNameBlowoutTest): else: eq_(overlap, reflected_name) +def _multi_combination(fn): + schema = testing.combinations( + None, + ( + lambda: config.test_schema, + testing.requires.schemas, + ), + argnames="schema", + ) + scope = testing.combinations( + ObjectScope.DEFAULT, + ObjectScope.TEMPORARY, + ObjectScope.ANY, + argnames="scope", + ) + kind = testing.combinations( + ObjectKind.TABLE, + ObjectKind.VIEW, + ObjectKind.MATERIALIZED_VIEW, + ObjectKind.ANY, + ObjectKind.ANY_VIEW, + ObjectKind.TABLE | ObjectKind.VIEW, + ObjectKind.TABLE | ObjectKind.MATERIALIZED_VIEW, + argnames="kind", + ) + filter_names = testing.combinations(True, False, argnames="use_filter") + + return schema(scope(kind(filter_names(fn)))) class ComponentReflectionTest(_ComponentReflectionTest): @classmethod @@ -294,8 +324,6 @@ class ComponentReflectionTest(_ComponentReflectionTest): if testing.requires.view_column_reflection.enabled: cls.define_views(metadata, schema) - if not schema and testing.requires.temp_table_reflection.enabled: - cls.define_temp_tables(metadata) @classmethod def define_temp_tables(cls, metadata): @@ -398,6 +426,7 @@ class ComponentReflectionTest(_ComponentReflectionTest): dupe = refl.pop("duplicates_index", None) if dupe: names_that_duplicate_index.add(dupe) + eq_(refl.pop("comment", None), None) eq_(orig, refl) reflected_metadata = MetaData() @@ -425,7 +454,140 @@ class ComponentReflectionTest(_ComponentReflectionTest): eq_(uq_names, set()) no_cst = self.tables.no_constraints.name - eq_(insp.get_unique_constraints(no_cst, schema=schema), []) + eq_(inspector.get_unique_constraints(no_cst, schema=schema), []) + @testing.requires.index_reflection + @_multi_combination + def test_get_multi_indexes( + self, get_multi_exp, schema, scope, kind, use_filter + ): + insp, kws, exp = get_multi_exp( + schema, + scope, + kind, + use_filter, + Inspector.get_indexes, + self.exp_indexes, + ) + for kw in kws: + insp.clear_cache() + result = insp.get_multi_indexes(**kw) + eq_(result, exp) + self._check_table_dict(result, exp, self._required_index_keys) + + def exp_indexes( + self, + schema=None, + scope=ObjectScope.ANY, + kind=ObjectKind.ANY, + filter_names=None, + ): + def idx( + *cols, + name, + unique=False, + column_sorting=None, + duplicates=False, + fk=False, + ): + fk_req = testing.requires.foreign_keys_reflect_as_index + dup_req = testing.requires.unique_constraints_reflect_as_index + sorting_expression = ( + testing.requires.reflect_indexes_with_ascdesc_as_expression + ) + + if (fk and not fk_req.enabled) or ( + duplicates and not dup_req.enabled + ): + return () + res = { + "name": name, + "unique": unique, + "column_names": list(cols), + } + if column_sorting: + res["column_sorting"] = column_sorting + if sorting_expression.enabled: + res["expressions"] = orig = res["column_names"] + res["column_names"] = [ + None if c in column_sorting else c for c in orig + ] + + if duplicates: + res["duplicates_constraint"] = name + return [res] + + materialized = {(schema, "dingalings_v"): []} + views = { + (schema, "email_addresses_v"): [], + (schema, "users_v"): [], + (schema, "user_tmp_v"): [], + } + self._resolve_views(views, materialized) + if materialized: + materialized[(schema, "dingalings_v")].extend( + idx("data", name="mat_index") + ) + tables = { + (schema, "users"): [ + *idx("parent_user_id", name="user_id_fk", fk=True), + *idx("user_id", "test2", "test1", name="users_all_idx"), + *idx("test1", "test2", name="users_t_idx", unique=True), + ], + (schema, "dingalings"): [ + *idx("data", name=mock.ANY, unique=True, duplicates=True), + *idx("id_user", name=mock.ANY, fk=True), + *idx( + "address_id", + "dingaling_id", + name="zz_dingalings_multiple", + unique=True, + duplicates=True, + ), + ], + (schema, "email_addresses"): [ + *idx("email_address", name=mock.ANY), + *idx("remote_user_id", name=mock.ANY, fk=True), + ], + (schema, "comment_test"): [], + (schema, "no_constraints"): [], + (schema, "local_table"): [ + *idx("remote_id", name=mock.ANY, fk=True) + ], + (schema, "remote_table"): [ + *idx("local_id", name=mock.ANY, fk=True) + ], + (schema, "remote_table_2"): [], + (schema, "noncol_idx_test_nopk"): [ + *idx( + "q", + name="noncol_idx_nopk", + column_sorting={"q": ("desc",)}, + ) + ], + (schema, "noncol_idx_test_pk"): [ + *idx( + "q", name="noncol_idx_pk", column_sorting={"q": ("desc",)} + ) + ], + (schema, self.temp_table_name()): [ + *idx("foo", name="user_tmp_ix"), + *idx( + "name", + name=f"user_tmp_uq_{config.ident}", + duplicates=True, + unique=True, + ), + ], + } + if ( + not testing.requires.indexes_with_ascdesc.enabled + or not testing.requires.reflect_indexes_with_ascdesc.enabled + ): + tables[(schema, "noncol_idx_test_nopk")].clear() + tables[(schema, "noncol_idx_test_pk")].clear() + res = self._resolve_kind(kind, tables, views, materialized) + res = self._resolve_names(schema, scope, filter_names, res) + return res class CompositeKeyReflectionTest(_CompositeKeyReflectionTest): -- Gitee From f0c114fc8270d75ae04a85c7ffe128b2b8979a59 Mon Sep 17 00:00:00 2001 From: h30054849 Date: Thu, 12 Sep 2024 10:32:54 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-5=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opengauss_sqlalchemy/psycopg2.py | 5 +-- opengauss_sqlalchemy/requirements.py | 24 ++++++++--- test/test_suite.py | 63 +++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 10 deletions(-) diff --git a/opengauss_sqlalchemy/psycopg2.py b/opengauss_sqlalchemy/psycopg2.py index dddb40e..3c25837 100644 --- a/opengauss_sqlalchemy/psycopg2.py +++ b/opengauss_sqlalchemy/psycopg2.py @@ -123,10 +123,10 @@ class OpenGaussDialect_psycopg2(PGDialect_psycopg2): extensions = self._psycopg2_extensions return { - "AUTOCOMMIT": extensions.ISOLATION_LEVEL_AUTOCOMMIT, "READ COMMITTED": extensions.ISOLATION_LEVEL_READ_COMMITTED, - "READ UNCOMMITTED": extensions.ISOLATION_LEVEL_READ_UNCOMMITTED, + "AUTOCOMMIT": extensions.ISOLATION_LEVEL_AUTOCOMMIT, "REPEATABLE READ": extensions.ISOLATION_LEVEL_REPEATABLE_READ, + "READ UNCOMMITTED": extensions.ISOLATION_LEVEL_READ_UNCOMMITTED, # opengauss does NOT support SERIALIZABLE } @@ -261,5 +261,4 @@ class OpenGaussDialect_psycopg2(PGDialect_psycopg2): return indexes.items() - dialect = OpenGaussDialect_psycopg2 diff --git a/opengauss_sqlalchemy/requirements.py b/opengauss_sqlalchemy/requirements.py index 4e1646e..df52b50 100644 --- a/opengauss_sqlalchemy/requirements.py +++ b/opengauss_sqlalchemy/requirements.py @@ -292,12 +292,26 @@ class Requirements(SuiteRequirements): def legacy_isolation_level(self): return exclusions.open() - def get_isolation_levels(self, config): - levels = set(config.db.dialect._isolation_lookup) - default = "READ COMMITTED" - levels.add("AUTOCOMMIT") + @property + def get_isolation_level_values(self): + """target dialect supports the + :meth:`_engine.Dialect.get_isolation_level_values` + method added in SQLAlchemy 2.0. + + """ - return {"default": default, "supported": levels} + def go(config): + with config.db.connect() as conn: + try: + conn.dialect.get_isolation_level_values( + conn.connection.dbapi_connection + ) + except NotImplementedError: + return False + else: + return True + + return exclusions.only_if(go) @property def autocommit(self): diff --git a/test/test_suite.py b/test/test_suite.py index 7b95706..f5729f1 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -26,8 +26,11 @@ from sqlalchemy.testing.suite.test_select import FetchLimitOffsetTest as _FetchL from sqlalchemy.testing.suite.test_select import JoinTest as _JoinTest from sqlalchemy.testing.suite.test_unicode_ddl import UnicodeSchemaTest as _UnicodeSchemaTest from sqlalchemy.engine import ObjectScope, ObjectKind, Inspector -from sqlalchemy.testing import mock - +from sqlalchemy.testing import mock, assert_raises_message +from sqlalchemy.testing.suite.test_dialect import IsolationLevelTest as _IsolationLevelTest +from sqlalchemy.testing.suite.test_types import TrueDivTest as _TrueDivTest +from sqlalchemy.testing.engines import testing_engine +import opengauss_sqlalchemy.requirements as ogsa class CTETest(_CTETest): @classmethod @@ -325,6 +328,9 @@ class ComponentReflectionTest(_ComponentReflectionTest): if testing.requires.view_column_reflection.enabled: cls.define_views(metadata, schema) + # if not schema and testing.requires.temp_table_reflection.enabled: + # cls.define_temp_tables(metadata) + @classmethod def define_temp_tables(cls, metadata): kw = temp_table_keyword_args(config, config.db) @@ -1166,3 +1172,56 @@ class UnicodeSchemaTest(_UnicodeSchemaTest): ).fetchall(), [(2, 7, 2, 2), (1, 5, 1, 1)], ) + +from sqlalchemy import literal_column, func, select, Integer + +class TrueDivTest(_TrueDivTest): + @testing.combinations( + ("15", "10", 1), ("-15", "5", -3), argnames="left, right, expected" + ) + def test_floordiv_integer(self, connection, left, right, expected): + """test #4926""" + + eq_( + connection.scalar( + select( + func.floor(literal_column(left, type_=Integer()) // literal_column(right, type_=Integer())) + + ) + ), + expected, + ) + + def test_floordiv_integer_bound(self, connection): + """test #4926""" + + eq_( + connection.scalar(select(func.floor(literal(15) // literal(10)))), + 1, + ) + +class IsolationLevelTest(_IsolationLevelTest): + def test_all_levels(self): + levels = requirements.get_isolation_levels(config) + + all_levels = levels["supported"] + + for level in set(all_levels).difference(["AUTOCOMMIT"]): + + if level == 'SERIALIZABLE': + continue + with config.db.connect() as conn: + conn.execution_options(isolation_level=level) + + eq_(conn.get_isolation_level(), level) + + trans = conn.begin() + trans.rollback() + + eq_(conn.get_isolation_level(), level) + + with config.db.connect() as conn: + eq_( + conn.get_isolation_level(), + levels["default"], + ) \ No newline at end of file -- Gitee From 97c2e6b04f78616843c256799d3040248925bb9a Mon Sep 17 00:00:00 2001 From: h30054849 Date: Thu, 12 Sep 2024 14:37:38 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-5=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opengauss_sqlalchemy/requirements.py | 22 ++++++++ test/test_suite.py | 75 +++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/opengauss_sqlalchemy/requirements.py b/opengauss_sqlalchemy/requirements.py index df52b50..81f9d61 100644 --- a/opengauss_sqlalchemy/requirements.py +++ b/opengauss_sqlalchemy/requirements.py @@ -1078,3 +1078,25 @@ class Requirements(SuiteRequirements): def index_reflection(self): return exclusions.open() + @property + def indexes_with_ascdesc(self): + """target database supports CREATE INDEX with per-column ASC/DESC.""" + return exclusions.open() + + @property + def reflect_indexes_with_ascdesc(self): + """target database supports reflecting INDEX with per-column + ASC/DESC.""" + return exclusions.open() + + @property + def unique_constraints_reflect_as_index(self): + """Target database reflects unique constraints as indexes.""" + + return exclusions.open() + + @property + def unique_constraints_reflect_as_index(self): + """Target database reflects unique constraints as indexes.""" + + return exclusions.open() \ No newline at end of file diff --git a/test/test_suite.py b/test/test_suite.py index f5729f1..c7948a5 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -167,7 +167,7 @@ class ComponentReflectionTest(_ComponentReflectionTest): sa.Integer, ForeignKey("%susers.user_id" % schema_prefix), ), - Column("data", sa.String(30)), + Column("data", sa.String(30), unique=True), sa.CheckConstraint( "address_id > 0 AND address_id < 1000", name="address_id_gt_zero", @@ -206,7 +206,7 @@ class ComponentReflectionTest(_ComponentReflectionTest): "id_user", sa.Integer, ), - Column("data", sa.String(30)), + Column("data", sa.String(30), unique=True), sa.CheckConstraint( "address_id > 0 AND address_id < 1000", name="address_id_gt_zero", @@ -477,6 +477,7 @@ class ComponentReflectionTest(_ComponentReflectionTest): for kw in kws: insp.clear_cache() result = insp.get_multi_indexes(**kw) + eq_(result, exp) self._check_table_dict(result, exp, self._required_index_keys) @@ -595,6 +596,76 @@ class ComponentReflectionTest(_ComponentReflectionTest): res = self._resolve_names(schema, scope, filter_names, res) return res + def exp_ucs( + self, + schema=None, + scope=ObjectScope.ANY, + kind=ObjectKind.ANY, + filter_names=None, + all_=False, + ): + def uc( + *cols, name, duplicates_index=None, is_index=False, comment=None + ): + req = testing.requires.unique_index_reflect_as_unique_constraints + if is_index and not req.enabled: + return () + res = { + "column_names": list(cols), + "name": name, + "comment": comment, + } + if duplicates_index: + res["duplicates_index"] = duplicates_index + return [res] + + materialized = {(schema, "dingalings_v"): []} + views = { + (schema, "email_addresses_v"): [], + (schema, "users_v"): [], + (schema, "user_tmp_v"): [], + } + self._resolve_views(views, materialized) + tables = { + (schema, "users"): [ + *uc( + "test1", + "test2", + name="users_t_idx", + duplicates_index="users_t_idx", + is_index=True, + ) + ], + (schema, "dingalings"): [ + *uc("data", name=mock.ANY, duplicates_index=mock.ANY), + *uc( + "address_id", + "dingaling_id", + name="zz_dingalings_multiple", + duplicates_index="zz_dingalings_multiple", + comment="di unique comment", + ), + ], + (schema, "email_addresses"): [], + (schema, "comment_test"): [], + (schema, "no_constraints"): [], + (schema, "local_table"): [], + (schema, "remote_table"): [], + (schema, "remote_table_2"): [], + (schema, "noncol_idx_test_nopk"): [], + (schema, "noncol_idx_test_pk"): [], + (schema, self.temp_table_name()): [ + *uc("name", name=f"user_tmp_uq_{config.ident}") + ], + } + if all_: + return {**materialized, **views, **tables} + else: + res = self._resolve_kind(kind, tables, views, materialized) + res = self._resolve_names(schema, scope, filter_names, res) + return res + + class CompositeKeyReflectionTest(_CompositeKeyReflectionTest): @classmethod -- Gitee From bafaaebc5efe7fc6d3e937e2f879f15ae47e2780 Mon Sep 17 00:00:00 2001 From: h30054849 Date: Thu, 12 Sep 2024 16:11:59 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-6=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opengauss_sqlalchemy/requirements.py | 3 +- test/test_suite.py | 63 +++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/opengauss_sqlalchemy/requirements.py b/opengauss_sqlalchemy/requirements.py index 81f9d61..fea7b13 100644 --- a/opengauss_sqlalchemy/requirements.py +++ b/opengauss_sqlalchemy/requirements.py @@ -433,8 +433,7 @@ class Requirements(SuiteRequirements): @property def temp_table_names(self): """target dialect supports listing of temporary table names""" - - return exclusions.closed() + return exclusions.open() @property def temporary_views(self): diff --git a/test/test_suite.py b/test/test_suite.py index c7948a5..d44bce9 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -328,8 +328,8 @@ class ComponentReflectionTest(_ComponentReflectionTest): if testing.requires.view_column_reflection.enabled: cls.define_views(metadata, schema) - # if not schema and testing.requires.temp_table_reflection.enabled: - # cls.define_temp_tables(metadata) + if not schema and testing.requires.temp_table_reflection.enabled: + cls.define_temp_tables(metadata) @classmethod def define_temp_tables(cls, metadata): @@ -666,6 +666,65 @@ class ComponentReflectionTest(_ComponentReflectionTest): return res + def exp_ccs( + self, + schema=None, + scope=ObjectScope.ANY, + kind=ObjectKind.ANY, + filter_names=None, + ): + class tt(str): + def __eq__(self, other): + res = ( + other.lower() + .replace("(", "") + .replace(")", "") + .replace("`", "") + ) + return self in res + + def cc(text, name, comment=None): + return {"sqltext": tt(text), "name": name, "comment": comment} + + # print({1: "test2 > (0)::double precision"} == {1: tt("test2 > 0")}) + # assert 0 + materialized = {(schema, "dingalings_v"): []} + views = { + (schema, "email_addresses_v"): [], + (schema, "users_v"): [], + (schema, "user_tmp_v"): [], + } + self._resolve_views(views, materialized) + tables = { + (schema, "users"): [ + cc("test2 <= 1000", mock.ANY), + cc( + "test2 > 0", + "zz_test2_gt_zero", + comment="users check constraint", + ), + ], + (schema, "dingalings"): [ + cc( + "address_id > 0 and address_id < 1000", + name="address_id_gt_zero", + ), + ], + (schema, "email_addresses"): [], + (schema, "comment_test"): [], + (schema, "no_constraints"): [], + (schema, "local_table"): [], + (schema, "remote_table"): [], + (schema, "remote_table_2"): [], + (schema, "noncol_idx_test_nopk"): [], + (schema, "noncol_idx_test_pk"): [], + (schema, self.temp_table_name()): [], + } + res = self._resolve_kind(kind, tables, views, materialized) + res = self._resolve_names(schema, scope, filter_names, res) + return res + + class CompositeKeyReflectionTest(_CompositeKeyReflectionTest): @classmethod -- Gitee From 88f83ec4046f14925ecbb1dcc2a0c69afa785dc3 Mon Sep 17 00:00:00 2001 From: h30054849 Date: Thu, 12 Sep 2024 17:05:57 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-7=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_suite.py | 252 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 198 insertions(+), 54 deletions(-) diff --git a/test/test_suite.py b/test/test_suite.py index d44bce9..ea47f20 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -724,6 +724,153 @@ class ComponentReflectionTest(_ComponentReflectionTest): res = self._resolve_names(schema, scope, filter_names, res) return res + def exp_columns( + self, + schema=None, + scope=ObjectScope.ANY, + kind=ObjectKind.ANY, + filter_names=None, + ): + def col( + name, auto=False, default=mock.ANY, comment=None, nullable=True + ): + res = { + "name": name, + "autoincrement": auto, + "type": mock.ANY, + "default": default, + "comment": comment, + "nullable": nullable, + } + if auto == "omit": + res.pop("autoincrement") + return res + + def pk(name, **kw): + kw = {"auto": True, "default": mock.ANY, "nullable": False, **kw} + return col(name, **kw) + + materialized = { + (schema, "dingalings_v"): [ + col("dingaling_id", auto="omit", nullable=mock.ANY), + col("address_id"), + col("id_user"), + col("data"), + ] + } + views = { + (schema, "email_addresses_v"): [ + col("address_id", auto="omit", nullable=mock.ANY), + col("remote_user_id"), + col("email_address"), + ], + (schema, "users_v"): [ + col("user_id", auto="omit", nullable=mock.ANY), + col("test1", nullable=mock.ANY), + col("test2", nullable=mock.ANY), + col("parent_user_id"), + ], + (schema, "user_tmp_v"): [ + col("id", auto="omit", nullable=mock.ANY), + col("name"), + col("foo"), + ], + } + self._resolve_views(views, materialized) + tables = { + (schema, "users"): [ + pk("user_id"), + col("test1", nullable=False), + col("test2", nullable=False), + col("parent_user_id"), + ], + (schema, "dingalings"): [ + pk("dingaling_id"), + col("address_id"), + col("id_user"), + col("data"), + ], + (schema, "email_addresses"): [ + pk("address_id"), + col("remote_user_id"), + col("email_address"), + ], + (schema, "comment_test"): [ + pk("id", comment="id comment"), + col("data", comment="data % comment"), + col( + "d2", + comment=r"""Comment types type speedily ' " \ '' Fun!""", + ), + col("d3", comment="Comment\nwith\rescapes"), + ], + (schema, "no_constraints"): [col("data")], + (schema, "local_table"): [pk("id"), col("data"), col("remote_id")], + (schema, "remote_table"): [pk("id"), col("local_id"), col("data")], + (schema, "remote_table_2"): [pk("id"), col("data")], + (schema, "noncol_idx_test_nopk"): [col("q")], + (schema, "noncol_idx_test_pk"): [pk("id"), col("q")], + (schema, self.temp_table_name()): [ + pk("id", nullable=True, auto=False), + col("name", nullable=True, auto=False), + col("foo", nullable=True, auto=False), + ], + } + res = self._resolve_kind(kind, tables, views, materialized) + res = self._resolve_names(schema, scope, filter_names, res) + return res + + @property + def _required_column_keys(self): + return {"name", "type", "nullable", "default"} + + def exp_pks( + self, + schema=None, + scope=ObjectScope.ANY, + kind=ObjectKind.ANY, + filter_names=None, + ): + def pk(*cols, name=mock.ANY, comment=None): + return { + "constrained_columns": list(cols), + "name": name, + "comment": comment, + } + + empty = pk(name=None) + if testing.requires.materialized_views_reflect_pk.enabled: + materialized = {(schema, "dingalings_v"): pk("dingaling_id")} + else: + materialized = {(schema, "dingalings_v"): empty} + views = { + (schema, "email_addresses_v"): empty, + (schema, "users_v"): empty, + (schema, "user_tmp_v"): empty, + } + self._resolve_views(views, materialized) + tables = { + (schema, "users"): pk("user_id"), + (schema, "dingalings"): pk("dingaling_id"), + (schema, "email_addresses"): pk( + "address_id", name="email_ad_pk", comment="ea pk comment" + ), + (schema, "comment_test"): pk("id"), + (schema, "no_constraints"): empty, + (schema, "local_table"): pk("id"), + (schema, "remote_table"): pk("id"), + (schema, "remote_table_2"): pk("id"), + (schema, "noncol_idx_test_nopk"): empty, + (schema, "noncol_idx_test_pk"): pk("id"), + (schema, self.temp_table_name()): pk(name=None), + } + if not testing.requires.reflects_pk_names.enabled: + for val in tables.values(): + if val["name"] is not None: + val["name"] = mock.ANY + res = self._resolve_kind(kind, tables, views, materialized) + res = self._resolve_names(schema, scope, filter_names, res) + return res class CompositeKeyReflectionTest(_CompositeKeyReflectionTest): @@ -1162,21 +1309,21 @@ class UnicodeSchemaTest(_UnicodeSchemaTest): global t1, t2, t3 t1 = Table( - u("unitable1"), + "unitable1", metadata, - Column(u("méil"), Integer, primary_key=True), - Column(ue("\u6e2c\u8a66"), Integer), + Column("méil", Integer, primary_key=True), + Column("\u6e2c\u8a66", Integer), test_needs_fk=True, ) if testing.requires.foreign_keys.enabled: t2 = Table( - u("Unitéble2"), + "Unitéble2", metadata, - Column(u("méil"), Integer, primary_key=True, key="a"), + Column("méil", Integer, primary_key=True, key="a"), Column( - ue("\u6e2c\u8a66"), + "\u6e2c\u8a66", Integer, - ForeignKey(u("unitable1.méil")), + ForeignKey("unitable1.méil"), key="b", ), test_needs_fk=True, @@ -1184,11 +1331,11 @@ class UnicodeSchemaTest(_UnicodeSchemaTest): else: t2 = Table( - u("Unitéble2"), + "Unitéble2", metadata, - Column(u("méil"), Integer, primary_key=True, key="a"), + Column("méil", Integer, primary_key=True, key="a"), Column( - ue("\u6e2c\u8a66"), + "\u6e2c\u8a66", Integer, key="b", ), @@ -1196,30 +1343,30 @@ class UnicodeSchemaTest(_UnicodeSchemaTest): ) t3 = Table( - ue("\u6e2c\u8a66"), + "\u6e2c\u8a66", metadata, Column( - ue("\u6e2c\u8a66_id"), + "\u6e2c\u8a66_id", Integer, primary_key=True, autoincrement=False, ), - Column(ue("unitable1_\u6e2c\u8a66"), Integer), - Column(u("Unitéble2_b"), Integer), - Column(ue("\u6e2c\u8a66_self"), Integer), + Column("unitable1_\u6e2c\u8a66", Integer), + Column("Unitéble2_b", Integer), + Column("\u6e2c\u8a66_self", Integer), test_needs_fk=True, ) def test_insert(self, connection): - connection.execute(t1.insert(), {u("méil"): 1, ue("\u6e2c\u8a66"): 5}) - connection.execute(t2.insert(), {u("a"): 1, u("b"): 1}) + connection.execute(t1.insert(), {"méil": 1, "\u6e2c\u8a66": 5}) + connection.execute(t2.insert(), {"a": 1, "b": 1}) connection.execute( t3.insert(), { - ue("\u6e2c\u8a66_id"): 1, - ue("unitable1_\u6e2c\u8a66"): 5, - u("Unitéble2_b"): 1, - ue("\u6e2c\u8a66_self"): 1, + "\u6e2c\u8a66_id": 1, + "unitable1_\u6e2c\u8a66": 5, + "Unitéble2_b": 1, + "\u6e2c\u8a66_self": 1, }, ) @@ -1228,42 +1375,42 @@ class UnicodeSchemaTest(_UnicodeSchemaTest): eq_(connection.execute(t3.select()).fetchall(), [(1, 5, 1, 1)]) def test_col_targeting(self, connection): - connection.execute(t1.insert(), {u("méil"): 1, ue("\u6e2c\u8a66"): 5}) - connection.execute(t2.insert(), {u("a"): 1, u("b"): 1}) + connection.execute(t1.insert(), {"méil": 1, "\u6e2c\u8a66": 5}) + connection.execute(t2.insert(), {"a": 1, "b": 1}) connection.execute( t3.insert(), { - ue("\u6e2c\u8a66_id"): 1, - ue("unitable1_\u6e2c\u8a66"): 5, - u("Unitéble2_b"): 1, - ue("\u6e2c\u8a66_self"): 1, + "\u6e2c\u8a66_id": 1, + "unitable1_\u6e2c\u8a66": 5, + "Unitéble2_b": 1, + "\u6e2c\u8a66_self": 1, }, ) row = connection.execute(t1.select()).first() - eq_(row._mapping[t1.c[u("méil")]], 1) - eq_(row._mapping[t1.c[ue("\u6e2c\u8a66")]], 5) + eq_(row._mapping[t1.c["méil"]], 1) + eq_(row._mapping[t1.c["\u6e2c\u8a66"]], 5) row = connection.execute(t2.select()).first() - eq_(row._mapping[t2.c[u("a")]], 1) - eq_(row._mapping[t2.c[u("b")]], 1) + eq_(row._mapping[t2.c["a"]], 1) + eq_(row._mapping[t2.c["b"]], 1) row = connection.execute(t3.select()).first() - eq_(row._mapping[t3.c[ue("\u6e2c\u8a66_id")]], 1) - eq_(row._mapping[t3.c[ue("unitable1_\u6e2c\u8a66")]], 5) - eq_(row._mapping[t3.c[u("Unitéble2_b")]], 1) - eq_(row._mapping[t3.c[ue("\u6e2c\u8a66_self")]], 1) + eq_(row._mapping[t3.c["\u6e2c\u8a66_id"]], 1) + eq_(row._mapping[t3.c["unitable1_\u6e2c\u8a66"]], 5) + eq_(row._mapping[t3.c["Unitéble2_b"]], 1) + eq_(row._mapping[t3.c["\u6e2c\u8a66_self"]], 1) def test_reflect(self, connection): - connection.execute(t1.insert(), {u("méil"): 2, ue("\u6e2c\u8a66"): 7}) - connection.execute(t2.insert(), {u("a"): 2, u("b"): 2}) + connection.execute(t1.insert(), {"méil": 2, "\u6e2c\u8a66": 7}) + connection.execute(t2.insert(), {"a": 2, "b": 2}) connection.execute( t3.insert(), { - ue("\u6e2c\u8a66_id"): 2, - ue("unitable1_\u6e2c\u8a66"): 7, - u("Unitéble2_b"): 2, - ue("\u6e2c\u8a66_self"): 2, + "\u6e2c\u8a66_id": 2, + "unitable1_\u6e2c\u8a66": 7, + "Unitéble2_b": 2, + "\u6e2c\u8a66_self": 2, }, ) @@ -1272,37 +1419,34 @@ class UnicodeSchemaTest(_UnicodeSchemaTest): tt2 = Table(t2.name, meta, autoload_with=connection) tt3 = Table(t3.name, meta, autoload_with=connection) - connection.execute(tt1.insert(), {u("méil"): 1, ue("\u6e2c\u8a66"): 5}) - connection.execute(tt2.insert(), {u("méil"): 1, ue("\u6e2c\u8a66"): 1}) + connection.execute(tt1.insert(), {"méil": 1, "\u6e2c\u8a66": 5}) + connection.execute(tt2.insert(), {"méil": 1, "\u6e2c\u8a66": 1}) connection.execute( tt3.insert(), { - ue("\u6e2c\u8a66_id"): 1, - ue("unitable1_\u6e2c\u8a66"): 5, - u("Unitéble2_b"): 1, - ue("\u6e2c\u8a66_self"): 1, + "\u6e2c\u8a66_id": 1, + "unitable1_\u6e2c\u8a66": 5, + "Unitéble2_b": 1, + "\u6e2c\u8a66_self": 1, }, ) eq_( - connection.execute( - tt1.select().order_by(desc(u("méil"))) - ).fetchall(), + connection.execute(tt1.select().order_by(desc("méil"))).fetchall(), [(2, 7), (1, 5)], ) eq_( - connection.execute( - tt2.select().order_by(desc(u("méil"))) - ).fetchall(), + connection.execute(tt2.select().order_by(desc("méil"))).fetchall(), [(2, 2), (1, 1)], ) eq_( connection.execute( - tt3.select().order_by(desc(ue("\u6e2c\u8a66_id"))) + tt3.select().order_by(desc("\u6e2c\u8a66_id")) ).fetchall(), [(2, 7, 2, 2), (1, 5, 1, 1)], ) + from sqlalchemy import literal_column, func, select, Integer class TrueDivTest(_TrueDivTest): -- Gitee From fe30990b21b54c1686d29c2a63c450932694df5b Mon Sep 17 00:00:00 2001 From: h30054849 Date: Fri, 13 Sep 2024 09:26:56 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-8=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opengauss_sqlalchemy/psycopg2.py | 31 +++++++++++++++++++++++++++++++ setup.cfg | 1 - test/test_suite.py | 3 +-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/opengauss_sqlalchemy/psycopg2.py b/opengauss_sqlalchemy/psycopg2.py index 3c25837..0b179e7 100644 --- a/opengauss_sqlalchemy/psycopg2.py +++ b/opengauss_sqlalchemy/psycopg2.py @@ -12,6 +12,7 @@ from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2 from sqlalchemy.ext.compiler import compiles from collections import defaultdict from sqlalchemy.engine.reflection import ReflectionDefaults +import functools from opengauss_sqlalchemy.base import OpenGaussDDLCompiler, OpenGaussIdentifierPreparer, OpenGaussCompiler @@ -260,5 +261,35 @@ class OpenGaussDialect_psycopg2(PGDialect_psycopg2): table_indexes.append(index) return indexes.items() + def _set_connection_characteristics(self, connection, characteristics): + characteristic_values = [ + (name, self.connection_characteristics[name], value) + for name, value in characteristics.items() + ] + + if connection.in_transaction(): + trans_objs = [ + (name, obj) + for name, obj, value in characteristic_values + if obj.transactional + ] + if trans_objs: + raise exc.InvalidRequestError( + "This connection has already initialized a SQLAlchemy " + "Transaction() object via begin() or autobegin; " + "%s may not be altered unless rollback() or commit() " + "is called first." + % (", ".join(name for name, obj in trans_objs)) + ) + + dbapi_connection = connection.connection.dbapi_connection + for name, characteristic, value in characteristic_values: + # openGauss does not support SERIALIZABLE + if value != 'SERIALIZABLE': + characteristic.set_characteristic(self, dbapi_connection, value) + connection.connection._connection_record.finalize_callback.append( + functools.partial(self._reset_characteristics, characteristics) + ) + dialect = OpenGaussDialect_psycopg2 diff --git a/setup.cfg b/setup.cfg index 04a0613..d816cc5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,5 @@ opengauss = opengauss+psycopg2://hct:Huawei12#$@localhost:36783/t2 opengauss_psycopg2 = opengauss+psycopg2://hct:Huawei12#$@localhost:36783/t2 opengauss_dc_psycopg2 = opengauss+dc_psycopg2://hct:Huawei12#$@localhost:36783/t2 - [easy_install] index_url = http://mirrors.aliyun.com/pypi/simple/ \ No newline at end of file diff --git a/test/test_suite.py b/test/test_suite.py index ea47f20..5221b68 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -29,8 +29,6 @@ from sqlalchemy.engine import ObjectScope, ObjectKind, Inspector from sqlalchemy.testing import mock, assert_raises_message from sqlalchemy.testing.suite.test_dialect import IsolationLevelTest as _IsolationLevelTest from sqlalchemy.testing.suite.test_types import TrueDivTest as _TrueDivTest -from sqlalchemy.testing.engines import testing_engine -import opengauss_sqlalchemy.requirements as ogsa class CTETest(_CTETest): @classmethod @@ -1482,6 +1480,7 @@ class IsolationLevelTest(_IsolationLevelTest): for level in set(all_levels).difference(["AUTOCOMMIT"]): + # openGauss does not support SERIALIZABLE if level == 'SERIALIZABLE': continue with config.db.connect() as conn: -- Gitee From dae382d71f06668cf7c23a749484e50b52c788ef Mon Sep 17 00:00:00 2001 From: h30054849 Date: Fri, 13 Sep 2024 09:47:23 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-9=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opengauss_sqlalchemy/psycopg2.py | 162 ++----------------------------- test/test_suite.py | 65 ++++--------- 2 files changed, 27 insertions(+), 200 deletions(-) diff --git a/opengauss_sqlalchemy/psycopg2.py b/opengauss_sqlalchemy/psycopg2.py index 0b179e7..306e344 100644 --- a/opengauss_sqlalchemy/psycopg2.py +++ b/opengauss_sqlalchemy/psycopg2.py @@ -135,160 +135,14 @@ class OpenGaussDialect_psycopg2(PGDialect_psycopg2): # most of opengauss features are same with postgres 9.2.4 return (9, 2, 4) - def get_multi_indexes( - self, connection, schema, filter_names, scope, kind, **kw - ): - table_oids = self._get_table_oids( - connection, schema, filter_names, scope, kind, **kw - ) - - indexes = defaultdict(list) - default = ReflectionDefaults.indexes - - batches = list(table_oids) - - while batches: - batch = batches[0:3000] - batches[0:3000] = [] - - result = connection.execute( - self._index_query, {"oids": [r[0] for r in batch]} - ).mappings() - - result_by_oid = defaultdict(list) - for row_dict in result: - result_by_oid[row_dict["indrelid"]].append(row_dict) - - for oid, table_name in batch: - if oid not in result_by_oid: - # ensure that each table has an entry, even if reflection - # is skipped because not supported - indexes[(schema, table_name)] = default() - continue - - for row in result_by_oid[oid]: - index_name = row["relname_index"] - - table_indexes = indexes[(schema, table_name)] - - all_elements = row["elements"] - all_elements_is_expr = row["elements_is_expr"] - indnkeyatts = row["indnkeyatts"] - # "The number of key columns in the index, not counting any - # included columns, which are merely stored and do not - # participate in the index semantics" - if indnkeyatts and len(all_elements) > indnkeyatts: - # this is a "covering index" which has INCLUDE columns - # as well as regular index columns - inc_cols = all_elements[indnkeyatts:] - idx_elements = all_elements[:indnkeyatts] - idx_elements_is_expr = all_elements_is_expr[ - :indnkeyatts - ] - # postgresql does not support expression on included - # columns as of v14: "ERROR: expressions are not - # supported in included columns". - assert all( - not is_expr - for is_expr in all_elements_is_expr[indnkeyatts:] - ) - else: - idx_elements = all_elements - idx_elements_is_expr = all_elements_is_expr - inc_cols = [] - - index = {"name": index_name, "unique": row["indisunique"]} - if any(idx_elements_is_expr): - index["column_names"] = [ - None if is_expr else expr - for expr, is_expr in zip( - idx_elements, idx_elements_is_expr - ) - ] - index["expressions"] = idx_elements - else: - index["column_names"] = idx_elements - - sorting = {} - for col_index, col_flags in enumerate(row["indoption"]): - col_sorting = () - # try to set flags only if they differ from PG - # defaults... - if col_flags & 0x01: - col_sorting += ("desc",) - if not (col_flags & 0x02): - col_sorting += ("nulls_last",) - else: - if col_flags & 0x02: - col_sorting += ("nulls_first",) - if col_sorting: - sorting[idx_elements[col_index]] = col_sorting - if sorting: - index["column_sorting"] = sorting - if row["has_constraint"]: - index["duplicates_constraint"] = index_name - - dialect_options = {} - if row["reloptions"]: - dialect_options["postgresql_with"] = dict( - [option.split("=") for option in row["reloptions"]] - ) - # it *might* be nice to include that this is 'btree' in the - # reflection info. But we don't want an Index object - # to have a ``postgresql_using`` in it that is just the - # default, so for the moment leaving this out. - amname = row["amname"] - if amname != "btree": - dialect_options["postgresql_using"] = row["amname"] - if row["filter_definition"]: - dialect_options["postgresql_where"] = row[ - "filter_definition" - ] - if self.server_version_info >= (11,): - # NOTE: this is legacy, this is part of - # dialect_options now as of #7382 - index["include_columns"] = inc_cols - dialect_options["postgresql_include"] = inc_cols - if row["indnullsnotdistinct"]: - # the default is False, so ignore it. - dialect_options["postgresql_nulls_not_distinct"] = row[ - "indnullsnotdistinct" - ] - - if dialect_options: - index["dialect_options"] = dialect_options - - table_indexes.append(index) - return indexes.items() - - def _set_connection_characteristics(self, connection, characteristics): - characteristic_values = [ - (name, self.connection_characteristics[name], value) - for name, value in characteristics.items() - ] - - if connection.in_transaction(): - trans_objs = [ - (name, obj) - for name, obj, value in characteristic_values - if obj.transactional - ] - if trans_objs: - raise exc.InvalidRequestError( - "This connection has already initialized a SQLAlchemy " - "Transaction() object via begin() or autobegin; " - "%s may not be altered unless rollback() or commit() " - "is called first." - % (", ".join(name for name, obj in trans_objs)) - ) - - dbapi_connection = connection.connection.dbapi_connection - for name, characteristic, value in characteristic_values: - # openGauss does not support SERIALIZABLE - if value != 'SERIALIZABLE': - characteristic.set_characteristic(self, dbapi_connection, value) - connection.connection._connection_record.finalize_callback.append( - functools.partial(self._reset_characteristics, characteristics) + def get_isolation_level_values(self, dbapi_conn): + # note the generic dialect doesn't have AUTOCOMMIT, however + # all postgresql dialects should include AUTOCOMMIT. + return ( + "READ COMMITTED", + "AUTOCOMMIT", + "REPEATABLE READ", + "READ UNCOMMITTED", ) diff --git a/test/test_suite.py b/test/test_suite.py index 5221b68..34e9b7a 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -459,25 +459,25 @@ class ComponentReflectionTest(_ComponentReflectionTest): no_cst = self.tables.no_constraints.name eq_(inspector.get_unique_constraints(no_cst, schema=schema), []) - @testing.requires.index_reflection - @_multi_combination - def test_get_multi_indexes( - self, get_multi_exp, schema, scope, kind, use_filter - ): - insp, kws, exp = get_multi_exp( - schema, - scope, - kind, - use_filter, - Inspector.get_indexes, - self.exp_indexes, - ) - for kw in kws: - insp.clear_cache() - result = insp.get_multi_indexes(**kw) - - eq_(result, exp) - self._check_table_dict(result, exp, self._required_index_keys) + # @testing.requires.index_reflection + # @_multi_combination + # def test_get_multi_indexes( + # self, get_multi_exp, schema, scope, kind, use_filter + # ): + # insp, kws, exp = get_multi_exp( + # schema, + # scope, + # kind, + # use_filter, + # Inspector.get_indexes, + # self.exp_indexes, + # ) + # for kw in kws: + # insp.clear_cache() + # result = insp.get_multi_indexes(**kw) + # + # eq_(result, exp) + # self._check_table_dict(result, exp, self._required_index_keys) def exp_indexes( self, @@ -1471,30 +1471,3 @@ class TrueDivTest(_TrueDivTest): connection.scalar(select(func.floor(literal(15) // literal(10)))), 1, ) - -class IsolationLevelTest(_IsolationLevelTest): - def test_all_levels(self): - levels = requirements.get_isolation_levels(config) - - all_levels = levels["supported"] - - for level in set(all_levels).difference(["AUTOCOMMIT"]): - - # openGauss does not support SERIALIZABLE - if level == 'SERIALIZABLE': - continue - with config.db.connect() as conn: - conn.execution_options(isolation_level=level) - - eq_(conn.get_isolation_level(), level) - - trans = conn.begin() - trans.rollback() - - eq_(conn.get_isolation_level(), level) - - with config.db.connect() as conn: - eq_( - conn.get_isolation_level(), - levels["default"], - ) \ No newline at end of file -- Gitee From 9a15559c39d9567f3420519661995972c1d238d4 Mon Sep 17 00:00:00 2001 From: h30054849 Date: Fri, 13 Sep 2024 09:51:57 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-10=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_suite.py | 134 --------------------------------------------- 1 file changed, 134 deletions(-) diff --git a/test/test_suite.py b/test/test_suite.py index 34e9b7a..f4db89b 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -459,140 +459,6 @@ class ComponentReflectionTest(_ComponentReflectionTest): no_cst = self.tables.no_constraints.name eq_(inspector.get_unique_constraints(no_cst, schema=schema), []) - # @testing.requires.index_reflection - # @_multi_combination - # def test_get_multi_indexes( - # self, get_multi_exp, schema, scope, kind, use_filter - # ): - # insp, kws, exp = get_multi_exp( - # schema, - # scope, - # kind, - # use_filter, - # Inspector.get_indexes, - # self.exp_indexes, - # ) - # for kw in kws: - # insp.clear_cache() - # result = insp.get_multi_indexes(**kw) - # - # eq_(result, exp) - # self._check_table_dict(result, exp, self._required_index_keys) - - def exp_indexes( - self, - schema=None, - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - filter_names=None, - ): - def idx( - *cols, - name, - unique=False, - column_sorting=None, - duplicates=False, - fk=False, - ): - fk_req = testing.requires.foreign_keys_reflect_as_index - dup_req = testing.requires.unique_constraints_reflect_as_index - sorting_expression = ( - testing.requires.reflect_indexes_with_ascdesc_as_expression - ) - - if (fk and not fk_req.enabled) or ( - duplicates and not dup_req.enabled - ): - return () - res = { - "name": name, - "unique": unique, - "column_names": list(cols), - } - if column_sorting: - res["column_sorting"] = column_sorting - if sorting_expression.enabled: - res["expressions"] = orig = res["column_names"] - res["column_names"] = [ - None if c in column_sorting else c for c in orig - ] - - if duplicates: - res["duplicates_constraint"] = name - return [res] - - materialized = {(schema, "dingalings_v"): []} - views = { - (schema, "email_addresses_v"): [], - (schema, "users_v"): [], - (schema, "user_tmp_v"): [], - } - self._resolve_views(views, materialized) - if materialized: - materialized[(schema, "dingalings_v")].extend( - idx("data", name="mat_index") - ) - tables = { - (schema, "users"): [ - *idx("parent_user_id", name="user_id_fk", fk=True), - *idx("user_id", "test2", "test1", name="users_all_idx"), - *idx("test1", "test2", name="users_t_idx", unique=True), - ], - (schema, "dingalings"): [ - *idx("data", name=mock.ANY, unique=True, duplicates=True), - *idx("id_user", name=mock.ANY, fk=True), - *idx( - "address_id", - "dingaling_id", - name="zz_dingalings_multiple", - unique=True, - duplicates=True, - ), - ], - (schema, "email_addresses"): [ - *idx("email_address", name=mock.ANY), - *idx("remote_user_id", name=mock.ANY, fk=True), - ], - (schema, "comment_test"): [], - (schema, "no_constraints"): [], - (schema, "local_table"): [ - *idx("remote_id", name=mock.ANY, fk=True) - ], - (schema, "remote_table"): [ - *idx("local_id", name=mock.ANY, fk=True) - ], - (schema, "remote_table_2"): [], - (schema, "noncol_idx_test_nopk"): [ - *idx( - "q", - name="noncol_idx_nopk", - column_sorting={"q": ("desc",)}, - ) - ], - (schema, "noncol_idx_test_pk"): [ - *idx( - "q", name="noncol_idx_pk", column_sorting={"q": ("desc",)} - ) - ], - (schema, self.temp_table_name()): [ - *idx("foo", name="user_tmp_ix"), - *idx( - "name", - name=f"user_tmp_uq_{config.ident}", - duplicates=True, - unique=True, - ), - ], - } - if ( - not testing.requires.indexes_with_ascdesc.enabled - or not testing.requires.reflect_indexes_with_ascdesc.enabled - ): - tables[(schema, "noncol_idx_test_nopk")].clear() - tables[(schema, "noncol_idx_test_pk")].clear() - res = self._resolve_kind(kind, tables, views, materialized) - res = self._resolve_names(schema, scope, filter_names, res) - return res def exp_ucs( self, -- Gitee From 55c76508b3488c57e1dde4ec6eea02fc82f15091 Mon Sep 17 00:00:00 2001 From: h30054849 Date: Fri, 13 Sep 2024 09:55:34 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=82=E9=85=8D-11=20sqlalchemy2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 ++++++ setup.py | 2 +- test/test_suite.py | 128 --------------------------------------------- 3 files changed, 18 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 63f1aad..370ac15 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,23 @@ > https://github.com/opengauss-mirror/openGauss-connector-python-psycopg2 +### 注意: +使用psycopg2 for opengauss会导致Sqlalchemy2.0部分autoescape相关测试用例不通过。 + +若需要使用autoescape相关功能,请保证环境中正确安装openGauss,并且通过修改变量LD_LIBRARY_PATH保证pg_config来自openGauss. + +``` +# 配置环境变量 +$ export LD_LIBRARY_PATH=/home/omm/openGauss-server/mppdb_temp_install/lib:$LD_LIBRARY_PATH + +$ which pg_config +# 预期结果 +'/home/omm/openGauss-server/mppdb_temp_install/bin/pg_config' + +# 删除psycopg2 for opengauss +$ rm -rf /home/omm/.local/lib/python3.8/site-packages/psycopg2 +``` + ## 安装 diff --git a/setup.py b/setup.py index ff6d899..418c184 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ setup( ], packages=["opengauss_sqlalchemy"], include_package_data=True, - install_requires=["sqlalchemy<=2.0.23", "psycopg2>=2.8"], + install_requires=["sqlalchemy<=2.0.23", "psycopg2>=2.9.9"], entry_points={ "sqlalchemy.dialects": [ "opengauss = opengauss_sqlalchemy.psycopg2:OpenGaussDialect_psycopg2", diff --git a/test/test_suite.py b/test/test_suite.py index f4db89b..fa491fd 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -460,134 +460,6 @@ class ComponentReflectionTest(_ComponentReflectionTest): no_cst = self.tables.no_constraints.name eq_(inspector.get_unique_constraints(no_cst, schema=schema), []) - def exp_ucs( - self, - schema=None, - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - filter_names=None, - all_=False, - ): - def uc( - *cols, name, duplicates_index=None, is_index=False, comment=None - ): - req = testing.requires.unique_index_reflect_as_unique_constraints - if is_index and not req.enabled: - return () - res = { - "column_names": list(cols), - "name": name, - "comment": comment, - } - if duplicates_index: - res["duplicates_index"] = duplicates_index - return [res] - - materialized = {(schema, "dingalings_v"): []} - views = { - (schema, "email_addresses_v"): [], - (schema, "users_v"): [], - (schema, "user_tmp_v"): [], - } - self._resolve_views(views, materialized) - tables = { - (schema, "users"): [ - *uc( - "test1", - "test2", - name="users_t_idx", - duplicates_index="users_t_idx", - is_index=True, - ) - ], - (schema, "dingalings"): [ - *uc("data", name=mock.ANY, duplicates_index=mock.ANY), - *uc( - "address_id", - "dingaling_id", - name="zz_dingalings_multiple", - duplicates_index="zz_dingalings_multiple", - comment="di unique comment", - ), - ], - (schema, "email_addresses"): [], - (schema, "comment_test"): [], - (schema, "no_constraints"): [], - (schema, "local_table"): [], - (schema, "remote_table"): [], - (schema, "remote_table_2"): [], - (schema, "noncol_idx_test_nopk"): [], - (schema, "noncol_idx_test_pk"): [], - (schema, self.temp_table_name()): [ - *uc("name", name=f"user_tmp_uq_{config.ident}") - ], - } - if all_: - return {**materialized, **views, **tables} - else: - res = self._resolve_kind(kind, tables, views, materialized) - res = self._resolve_names(schema, scope, filter_names, res) - return res - - - def exp_ccs( - self, - schema=None, - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - filter_names=None, - ): - class tt(str): - def __eq__(self, other): - res = ( - other.lower() - .replace("(", "") - .replace(")", "") - .replace("`", "") - ) - return self in res - - def cc(text, name, comment=None): - return {"sqltext": tt(text), "name": name, "comment": comment} - - # print({1: "test2 > (0)::double precision"} == {1: tt("test2 > 0")}) - # assert 0 - materialized = {(schema, "dingalings_v"): []} - views = { - (schema, "email_addresses_v"): [], - (schema, "users_v"): [], - (schema, "user_tmp_v"): [], - } - self._resolve_views(views, materialized) - tables = { - (schema, "users"): [ - cc("test2 <= 1000", mock.ANY), - cc( - "test2 > 0", - "zz_test2_gt_zero", - comment="users check constraint", - ), - ], - (schema, "dingalings"): [ - cc( - "address_id > 0 and address_id < 1000", - name="address_id_gt_zero", - ), - ], - (schema, "email_addresses"): [], - (schema, "comment_test"): [], - (schema, "no_constraints"): [], - (schema, "local_table"): [], - (schema, "remote_table"): [], - (schema, "remote_table_2"): [], - (schema, "noncol_idx_test_nopk"): [], - (schema, "noncol_idx_test_pk"): [], - (schema, self.temp_table_name()): [], - } - res = self._resolve_kind(kind, tables, views, materialized) - res = self._resolve_names(schema, scope, filter_names, res) - return res - def exp_columns( self, schema=None, -- Gitee