diff --git a/CHANGES.md b/CHANGES.md index f336ce8..6d48e70 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Version 2.0.5 Unreleased +- Re-work get_multi_columns() to + - include identity column info (#297) + - avoid parse error when reflecting ENUMs (#303) # Version 2.0.4 April 23, 2026 diff --git a/sqlalchemy_cockroachdb/base.py b/sqlalchemy_cockroachdb/base.py index 8d74348..d595305 100644 --- a/sqlalchemy_cockroachdb/base.py +++ b/sqlalchemy_cockroachdb/base.py @@ -1,70 +1,26 @@ import collections -import re import threading from sqlalchemy import text +from sqlalchemy import ARRAY +from sqlalchemy import BIGINT +from sqlalchemy import BLOB +from sqlalchemy import DECIMAL +from sqlalchemy import DOUBLE_PRECISION +from sqlalchemy import FLOAT +from sqlalchemy import INTEGER +from sqlalchemy import NUMERIC +from sqlalchemy import REAL +from sqlalchemy import SMALLINT +from sqlalchemy import TEXT +from sqlalchemy import VARCHAR from sqlalchemy.dialects.postgresql.base import PGDialect -from sqlalchemy.dialects.postgresql import ARRAY -from sqlalchemy.dialects.postgresql import INET -from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.dialects.postgresql import BYTEA from sqlalchemy.ext.compiler import compiles -from sqlalchemy.util import warn - -import sqlalchemy.types as sqltypes from .stmt_compiler import CockroachCompiler, CockroachIdentifierPreparer from .ddl_compiler import CockroachDDLCompiler -# Map type names (as returned by information_schema) to sqlalchemy type -# objects. -# -# TODO(bdarnell): test more of these. The stock test suite only covers -# a few basic ones. -_type_map = { - "bool": sqltypes.BOOLEAN, # introspection returns "BOOL" not boolean - "boolean": sqltypes.BOOLEAN, - "bigint": sqltypes.INT, - "int": sqltypes.INT, - "int2": sqltypes.INT, - "int4": sqltypes.INT, - "int64": sqltypes.INT, - "int8": sqltypes.INT, - "integer": sqltypes.INT, - "smallint": sqltypes.INT, - "double precision": sqltypes.FLOAT, - "float": sqltypes.FLOAT, - "float4": sqltypes.FLOAT, - "float8": sqltypes.FLOAT, - "real": sqltypes.FLOAT, - "dec": sqltypes.DECIMAL, - "decimal": sqltypes.DECIMAL, - "numeric": sqltypes.DECIMAL, - "date": sqltypes.DATE, - "time": sqltypes.Time, - "time without time zone": sqltypes.Time, - "timestamp": sqltypes.TIMESTAMP, - "timestamptz": sqltypes.TIMESTAMP(timezone=True), - "timestamp with time zone": sqltypes.TIMESTAMP(timezone=True), - "timestamp without time zone": sqltypes.TIMESTAMP, - "interval": sqltypes.Interval, - "char": sqltypes.CHAR, - "char varying": sqltypes.VARCHAR, - "character": sqltypes.CHAR, - "character varying": sqltypes.VARCHAR, - "string": sqltypes.VARCHAR, - "text": sqltypes.VARCHAR, - "varchar": sqltypes.VARCHAR, - "blob": sqltypes.BLOB, - "bytea": sqltypes.BLOB, - "bytes": sqltypes.BLOB, - "json": sqltypes.JSON, - "jsonb": JSONB, - "uuid": UUID, - "inet": INET, -} - - class _SavepointState(threading.local): """Hack to override names used in savepoint statements. @@ -152,7 +108,7 @@ def _get_server_version_info(self, conn): # PGDialect expects a postgres server version number here, # although we've overridden most of the places where it's # used. - return (9, 5, 0) + return (12, 0, 0) def get_table_names(self, conn, schema=None, **kw): # Upstream implementation needs correlated subqueries. @@ -175,126 +131,88 @@ def has_table(self, conn, table, schema=None, info_cache=None): return any(t == table for t in self.get_table_names(conn, schema=schema)) def get_multi_columns(self, connection, schema, filter_names, scope, kind, **kw): - if not filter_names: - filter_names = self.get_table_names(connection, schema) - return { - (schema, table_name): self.get_columns(connection, table_name, schema, **kw) - for table_name in filter_names - } - - # The upstream implementations of the reflection functions below depend on - # correlated subqueries which are not yet supported. - def get_columns(self, conn, table_name, schema=None, **kw): _include_hidden = kw.get("include_hidden", False) - if not self._is_v191plus: - # v2.x does not have is_generated or generation_expression - sql = ( - "SELECT column_name, data_type, is_nullable::bool, column_default," - "numeric_precision, numeric_scale, character_maximum_length, " - "NULL AS is_generated, NULL AS generation_expression, is_hidden::bool," - "column_comment AS comment " - "FROM information_schema.columns " - "WHERE table_schema = :table_schema AND table_name = :table_name " - ) - sql += "" if _include_hidden else "AND NOT is_hidden::bool" - rows = conn.execute( - text(sql), - {"table_schema": schema or self.default_schema_name, "table_name": table_name}, - ) - else: - # v19.1 or later. Information schema columns are all usable. - sql = ( - "SELECT column_name, data_type, is_nullable::bool, column_default, " - "numeric_precision, numeric_scale, character_maximum_length, " - "CASE is_generated WHEN 'ALWAYS' THEN true WHEN 'NEVER' THEN false " - "ELSE is_generated::bool END AS is_generated, " - "generation_expression, is_hidden::bool, crdb_sql_type, column_comment AS comment " - "FROM information_schema.columns " - "WHERE table_schema = :table_schema AND table_name = :table_name " - ) - sql += "" if _include_hidden else "AND NOT is_hidden::bool" - rows = conn.execute( - text(sql), - {"table_schema": schema or self.default_schema_name, "table_name": table_name}, - ) - - res = [] - for row in rows: - name, type_str, nullable, default = row[:4] - if type_str == "ARRAY": - is_array = True - type_str, _ = row.crdb_sql_type.split("[", maxsplit=1) - else: - is_array = False - # When there are type parameters, attach them to the - # returned type object. - m = re.match(r"^(\w+(?: \w+)*)(?:\(([0-9, ]*)\))?$", type_str) - if m is None: - warn("Could not parse type name '%s'" % type_str) - typ = sqltypes.NULLTYPE - else: - type_name, type_args = m.groups() - try: - type_class = _type_map[type_name.lower()] - except KeyError: - warn(f"Did not recognize type '{type_name}' of column '{name}'") - type_class = sqltypes.NULLTYPE - if type_args: - typ = type_class(*[int(s.strip()) for s in type_args.split(",")]) - elif type_class is sqltypes.DECIMAL: - typ = type_class( - precision=row.numeric_precision, - scale=row.numeric_scale, - ) - elif type_class is sqltypes.VARCHAR or type_class is sqltypes.CHAR: - typ = type_class(length=row.character_maximum_length) - else: - typ = type_class - if row.is_generated: - # Currently, all computed columns are persisted. - computed = dict(sqltext=row.generation_expression, persisted=True) - default = None - else: - computed = None - # Check if a sequence is being used and adjust the default value. - autoincrement = False - if default is not None: - nextval_match = re.search(r"""(nextval\(')([^']+)('.*$)""", default) - unique_rowid_match = re.search(r"""unique_rowid\(""", default) - if nextval_match is not None or unique_rowid_match is not None: - if issubclass(type_class, sqltypes.Integer): - autoincrement = True - # the default is related to a Sequence - sch = schema - if ( - nextval_match is not None - and "." not in nextval_match.group(2) - and sch is not None - ): - # unconditionally quote the schema name. this could - # later be enhanced to obey quoting rules / - # "quote schema" - default = ( - nextval_match.group(1) - + ('"%s"' % sch) - + "." - + nextval_match.group(2) - + nextval_match.group(3) + multi_columns = super().get_multi_columns( + connection, schema, filter_names, scope, kind, **kw + ) + to_return = [] + if multi_columns: + current = connection.execute( + text("select current_database() as db, current_schema() as schema") + ).one() + for table, columns in multi_columns: + if table not in [ + (None, "geography_columns"), + (None, "geometry_columns"), + (None, "spatial_ref_sys"), + ]: + table_columns = ( + connection.execute( + text( + "select column_name, is_hidden::bool " + "from information_schema.columns " + "where table_catalog = :tc " + "and table_schema = :ts and table_name = :tn" + ), + dict(tc=current.db, ts=table[0] or current.schema, tn=table[1]), ) - - column_info = dict( - name=name, - type=ARRAY(typ) if is_array else typ, - nullable=nullable, - default=default, - autoincrement=autoincrement, - is_hidden=row.is_hidden, - comment=row.comment, - ) - if computed is not None: - column_info["computed"] = computed - res.append(column_info) - return res + .mappings() + .all() + ) + is_hidden = {x["column_name"]: x["is_hidden"] for x in table_columns} + for col in columns[:]: + if is_hidden[col["name"]] and not _include_hidden: + columns.remove(col) + else: + col["is_hidden"] = is_hidden[col["name"]] + if col["default"] == "unique_rowid()": + col["autoincrement"] = True + if isinstance(col["type"], BIGINT): + col["type"] = INTEGER() + elif isinstance(col["type"], ARRAY) and isinstance( + col["type"].item_type, BIGINT + ): + col["type"].item_type = INTEGER() + elif isinstance(col["type"], BYTEA): + col["type"] = BLOB() + elif isinstance(col["type"], ARRAY) and isinstance( + col["type"].item_type, BYTEA + ): + col["type"].item_type = BLOB() + elif isinstance(col["type"], DOUBLE_PRECISION): + col["type"] = FLOAT() + elif isinstance(col["type"], ARRAY) and isinstance( + col["type"].item_type, DOUBLE_PRECISION + ): + col["type"].item_type = FLOAT() + elif isinstance(col["type"], NUMERIC): + col["type"] = DECIMAL(col["type"].precision, col["type"].scale) + elif isinstance(col["type"], ARRAY) and isinstance( + col["type"].item_type, NUMERIC + ): + col["type"].item_type = DECIMAL( + col["type"].item_type.precision, col["type"].item_type.scale + ) + elif isinstance(col["type"], REAL): + col["type"] = FLOAT() + elif isinstance(col["type"], ARRAY) and isinstance( + col["type"].item_type, REAL + ): + col["type"].item_type = FLOAT() + elif isinstance(col["type"], SMALLINT): + col["type"] = INTEGER() + elif isinstance(col["type"], ARRAY) and isinstance( + col["type"].item_type, SMALLINT + ): + col["type"].item_type = INTEGER() + elif isinstance(col["type"], TEXT): + col["type"] = VARCHAR() + elif isinstance(col["type"], ARRAY) and isinstance( + col["type"].item_type, TEXT + ): + col["type"].item_type = VARCHAR() + to_return.append((table, columns)) + return to_return def get_indexes(self, conn, table_name, schema=None, **kw): if self._is_v192plus: @@ -348,12 +266,8 @@ def get_indexes(self, conn, table_name, schema=None, **kw): ) return result - def get_multi_indexes( - self, connection, schema, filter_names, scope, kind, **kw - ): - result = super().get_multi_indexes( - connection, schema, filter_names, scope, kind, **kw - ) + def get_multi_indexes(self, connection, schema, filter_names, scope, kind, **kw): + result = super().get_multi_indexes(connection, schema, filter_names, scope, kind, **kw) if schema is None: result = dict(result) for k in [ @@ -418,9 +332,7 @@ def get_unique_constraints(self, conn, table_name, schema=None, **kw): res.append(index) return res - def get_multi_check_constraints( - self, connection, schema, filter_names, scope, kind, **kw - ): + def get_multi_check_constraints(self, connection, schema, filter_names, scope, kind, **kw): result = super().get_multi_check_constraints( connection, schema, filter_names, scope, kind, **kw ) diff --git a/test/test_column_reflect.py b/test/test_column_reflect.py index 4853cdb..8431efd 100644 --- a/test/test_column_reflect.py +++ b/test/test_column_reflect.py @@ -1,4 +1,15 @@ -from sqlalchemy import MetaData, Table, Column, Integer, String, testing, inspect +from sqlalchemy import ( + MetaData, + Table, + Column, + Integer, + String, + testing, + inspect, + BigInteger, + Identity, + Computed, +) from sqlalchemy.testing import fixtures, eq_ meta = MetaData() @@ -10,12 +21,44 @@ Column("txt", String), ) +with_pk_other_schema = Table( + "with_pk_other_schema", + meta, + Column("id", Integer, primary_key=True), + Column("txt", String), + schema="test_schema", +) + without_pk = Table( "without_pk", meta, Column("txt", String), ) +with_identity = Table( + "with_identity", + meta, + Column("id", BigInteger, Identity(), primary_key=True), + Column("txt", String), +) + +with_identity_always = Table( + "with_identity_always", + meta, + Column("id", BigInteger, Identity(always=True), primary_key=True), + Column("txt", String), +) + +with_computed_stored = Table( + "with_computed_stored", + meta, + Column("id", Integer, primary_key=True), + Column("id2", Integer, Computed("id + 1", persisted=True)), +) + +# Note: CockroachDB computed columns do not support 'virtual' persistence; +# set the 'persisted' flag to None or True for CockroachDB support. + class ReflectHiddenColumnsTest(fixtures.TestBase): __requires__ = ("sync_driver",) @@ -96,3 +139,123 @@ def test_reflect_hidden_columns(self): }, ], ) + + def test_reflect_identity(self): + eq_( + self._get_col_info("with_identity"), + [ + { + "autoincrement": True, + "comment": None, + "default": "nextval('public.with_identity_id_seq'::REGCLASS)", + "identity": { + "always": False, + "cache": 1, + "cycle": False, + "increment": 1, + "maxvalue": 9223372036854775807, + "minvalue": 1, + "start": 1, + }, + "is_hidden": False, + "name": "id", + "nullable": False, + "type": "INTEGER", + }, + { + "autoincrement": False, + "comment": None, + "default": None, + "is_hidden": False, + "name": "txt", + "nullable": True, + "type": "VARCHAR", + }, + ], + ) + + eq_( + self._get_col_info("with_identity_always"), + [ + { + "autoincrement": True, + "comment": None, + "default": "nextval('public.with_identity_always_id_seq'::REGCLASS)", + "identity": { + "always": True, + "cache": 1, + "cycle": False, + "increment": 1, + "maxvalue": 9223372036854775807, + "minvalue": 1, + "start": 1, + }, + "is_hidden": False, + "name": "id", + "nullable": False, + "type": "INTEGER", + }, + { + "autoincrement": False, + "comment": None, + "default": None, + "is_hidden": False, + "name": "txt", + "nullable": True, + "type": "VARCHAR", + }, + ], + ) + + def test_reflect_computed_stored(self): + eq_( + self._get_col_info("with_computed_stored"), + [ + { + "autoincrement": True, + "comment": None, + "default": "unique_rowid()", + "is_hidden": False, + "name": "id", + "nullable": False, + "type": "INTEGER", + }, + { + "autoincrement": False, + "comment": None, + "computed": {"persisted": True, "sqltext": "id + 1"}, + "default": None, + "is_hidden": False, + "name": "id2", + "nullable": True, + "type": "INTEGER", + }, + ], + ) + + # def test_reflect_other_schema(self): + # # TODO: uncomment when resolved + # # verify https://github.com/cockroachdb/cockroach/issues/170049 + # eq_( + # self._get_col_info("with_pk_other_schema"), + # [ + # { + # "autoincrement": True, + # "comment": None, + # "default": "unique_rowid()", + # "is_hidden": False, + # "name": "id", + # "nullable": False, + # "type": "INTEGER", + # }, + # { + # "autoincrement": False, + # "comment": None, + # "default": None, + # "is_hidden": False, + # "name": "txt", + # "nullable": True, + # "type": "VARCHAR", + # }, + # ], + # ) diff --git a/test/test_introspection.py b/test/test_introspection.py index b7caba8..9e515b8 100644 --- a/test/test_introspection.py +++ b/test/test_introspection.py @@ -1,5 +1,3 @@ -import contextlib - from sqlalchemy import ( Table, Column, @@ -14,6 +12,7 @@ import sqlalchemy.types as sqltypes from sqlalchemy.testing import fixtures from sqlalchemy.dialects.postgresql import INET +from sqlalchemy.dialects.postgresql import INTERVAL from sqlalchemy.dialects.postgresql import UUID meta = MetaData() @@ -100,24 +99,42 @@ def test_array(self): self._test("timestamp[]", sqltypes.ARRAY, sqltypes.TIMESTAMP) self._test("varchar(10)[]", sqltypes.ARRAY, sqltypes.VARCHAR) + def test_blob(self): + for t in ["blob", "bytea", "bytes"]: + self._test(t, sqltypes.BLOB) + def test_boolean(self): for t in ["bool", "boolean"]: self._test(t, sqltypes.BOOLEAN) - def test_int(self): - for t in ["bigint", "int", "int2", "int4", "int64", "int8", "integer", "smallint"]: - self._test(t, sqltypes.INT) + def test_char(self): + for t in ["char", "character"]: + self._test(t, sqltypes.CHAR) - def test_float(self): - for t in ["double precision", "float", "float4", "float8", "real"]: - self._test(t, sqltypes.FLOAT) + def test_date(self): + self._test("date", sqltypes.DATE) def test_decimal(self): for t in ["dec", "decimal", "numeric"]: self._test(t, sqltypes.DECIMAL) - def test_date(self): - self._test("date", sqltypes.DATE) + def test_float(self): + for t in ["double precision", "float", "float4", "float8", "real"]: + self._test(t, sqltypes.FLOAT) + + def test_inet(self): + self._test("inet", INET) + + def test_int(self): + for t in ["bigint", "int", "int2", "int4", "int64", "int8", "integer", "smallint"]: + self._test(t, sqltypes.INT) + + def test_interval(self): + self._test("interval", INTERVAL) + + def test_json(self): + for t in ["json", "jsonb"]: + self._test(t, sqltypes.JSON) def test_time(self): for t in ["time", "time without time zone"]: @@ -133,16 +150,8 @@ def test_timestamp(self): for t in types: self._test(t, sqltypes.TIMESTAMP) - def test_interval(self): - self._test("interval", sqltypes.Interval) - - def test_char(self): - types = [ - "char", - "character", - ] - for t in types: - self._test(t, sqltypes.CHAR) + def test_uuid(self): + self._test("uuid", UUID) def test_varchar(self): types = [ @@ -154,46 +163,3 @@ def test_varchar(self): ] for t in types: self._test(t, sqltypes.VARCHAR) - - def test_blob(self): - for t in ["blob", "bytea", "bytes"]: - self._test(t, sqltypes.BLOB) - - def test_json(self): - for t in ["json", "jsonb"]: - self._test(t, sqltypes.JSON) - - def test_uuid(self): - self._test("uuid", UUID) - - def test_inet(self): - self._test("inet", INET) - - -class UnknownTypeTest(fixtures.TestBase): - __requires__ = ("sync_driver",) - - def setup_method(self): - with testing.db.begin() as conn: - conn.execute(text("CREATE TABLE t2 (c bool)")) - - def teardown_method(self): - with testing.db.begin() as conn: - conn.execute(text("DROP TABLE t2")) - - @testing.expect_warnings("Did not recognize type 'boolean'") - def test_unknown_type(self): - @contextlib.contextmanager - def make_bool_unknown(): - import sqlalchemy_cockroachdb - - t = sqlalchemy_cockroachdb.base._type_map.pop("bool") - sqlalchemy_cockroachdb.base._type_map.pop("boolean") - yield - sqlalchemy_cockroachdb.base._type_map["bool"] = t - sqlalchemy_cockroachdb.base._type_map["boolean"] = t - - with make_bool_unknown(): - meta2 = MetaData() - t = Table("t2", meta2, autoload_with=testing.db) - assert t.c["c"].type == sqltypes.NULLTYPE diff --git a/test/test_suite_sqlalchemy.py b/test/test_suite_sqlalchemy.py index b96f9e2..56edab9 100644 --- a/test/test_suite_sqlalchemy.py +++ b/test/test_suite_sqlalchemy.py @@ -1,4 +1,4 @@ -from sqlalchemy import FLOAT, INTEGER, VARCHAR +from sqlalchemy import INTEGER, VARCHAR, CHAR, DOUBLE_PRECISION from sqlalchemy.testing import skip from sqlalchemy.testing.suite import * # noqa from sqlalchemy.testing.suite import ( @@ -33,189 +33,189 @@ def test_get_multi_columns(self): insp = inspect(config.db) actual = insp.get_multi_columns() expected = { - (None, "users"): [ + (None, "comment_test"): [ { - "name": "user_id", - "type": INTEGER(), - "nullable": False, - "default": "unique_rowid()", "autoincrement": True, + "comment": "id comment", + "default": "unique_rowid()", "is_hidden": False, - "comment": None, + "name": "id", + "nullable": False, + "type": INTEGER(), }, { - "name": "test1", - "type": VARCHAR(length=5), - "nullable": False, - "default": None, "autoincrement": False, + "comment": "data % comment", + "default": None, "is_hidden": False, - "comment": None, + "name": "data", + "nullable": True, + "type": VARCHAR(length=20), }, { - "name": "test2", - "type": FLOAT(), - "nullable": False, - "default": None, "autoincrement": False, + "comment": "Comment types type speedily ' \" \ '' Fun!", # noqa + "default": None, "is_hidden": False, - "comment": None, + "name": "d2", + "nullable": True, + "type": VARCHAR(length=20), }, { - "name": "parent_user_id", - "type": INTEGER(), - "nullable": True, - "default": None, "autoincrement": False, + "comment": "Comment\nwith\nescapes", + "default": None, "is_hidden": False, - "comment": None, + "name": "d3", + "nullable": True, + "type": VARCHAR(length=42), }, ], - (None, "comment_test"): [ + (None, "dingalings"): [ { - "name": "id", - "type": INTEGER(), - "nullable": False, - "default": "unique_rowid()", "autoincrement": True, + "comment": None, + "default": "unique_rowid()", "is_hidden": False, - "comment": "id comment", + "name": "dingaling_id", + "nullable": False, + "type": INTEGER(), }, { - "name": "data", - "type": VARCHAR(length=20), - "nullable": True, - "default": None, "autoincrement": False, + "comment": None, + "default": None, "is_hidden": False, - "comment": "data % comment", + "name": "address_id", + "nullable": True, + "type": INTEGER(), }, { - "name": "d2", - "type": VARCHAR(length=20), - "nullable": True, - "default": None, "autoincrement": False, + "comment": None, + "default": None, "is_hidden": False, - "comment": "Comment types type speedily ' \" \\ '' Fun!", + "name": "id_user", + "nullable": True, + "type": INTEGER(), }, { - "name": "d3", - "type": VARCHAR(length=42), + "autoincrement": False, + "comment": None, + "default": None, + "is_hidden": False, + "name": "data", "nullable": True, + "type": VARCHAR(length=30), + }, + ], + (None, "email_addresses"): [ + { + "autoincrement": True, + "comment": None, + "default": "unique_rowid()", + "is_hidden": False, + "name": "address_id", + "nullable": False, + "type": INTEGER(), + }, + { + "autoincrement": False, + "comment": None, "default": None, + "is_hidden": False, + "name": "remote_user_id", + "nullable": True, + "type": INTEGER(), + }, + { "autoincrement": False, + "comment": None, + "default": None, "is_hidden": False, - "comment": "Comment\nwith\rescapes", + "name": "email_address", + "nullable": True, + "type": VARCHAR(length=20), }, ], (None, "no_constraints"): [ { - "name": "data", - "type": VARCHAR(length=20), - "nullable": True, - "default": None, "autoincrement": False, - "is_hidden": False, "comment": None, + "default": None, + "is_hidden": False, + "name": "data", + "nullable": True, + "type": VARCHAR(length=20), } ], (None, "noncol_idx_test_nopk"): [ { - "name": "q", - "type": VARCHAR(length=5), - "nullable": True, - "default": None, "autoincrement": False, - "is_hidden": False, "comment": None, + "default": None, + "is_hidden": False, + "name": "q", + "nullable": True, + "type": VARCHAR(length=5), } ], (None, "noncol_idx_test_pk"): [ { - "name": "id", - "type": INTEGER(), - "nullable": False, - "default": "unique_rowid()", "autoincrement": True, - "is_hidden": False, "comment": None, + "default": "unique_rowid()", + "is_hidden": False, + "name": "id", + "nullable": False, + "type": INTEGER(), }, { - "name": "q", - "type": VARCHAR(length=5), - "nullable": True, - "default": None, "autoincrement": False, - "is_hidden": False, "comment": None, + "default": None, + "is_hidden": False, + "name": "q", + "nullable": True, + "type": VARCHAR(length=5), }, ], - (None, "email_addresses"): [ + (None, "users"): [ { - "name": "address_id", - "type": INTEGER(), - "nullable": False, - "default": "unique_rowid()", "autoincrement": True, - "is_hidden": False, "comment": None, + "default": "unique_rowid()", + "is_hidden": False, + "name": "user_id", + "nullable": False, + "type": INTEGER(), }, { - "name": "remote_user_id", - "type": INTEGER(), - "nullable": True, - "default": None, "autoincrement": False, - "is_hidden": False, "comment": None, - }, - { - "name": "email_address", - "type": VARCHAR(length=20), - "nullable": True, "default": None, - "autoincrement": False, "is_hidden": False, - "comment": None, - }, - ], - (None, "dingalings"): [ - { - "name": "dingaling_id", - "type": INTEGER(), + "name": "test1", "nullable": False, - "default": "unique_rowid()", - "autoincrement": True, - "is_hidden": False, - "comment": None, + "type": CHAR(length=5), }, { - "name": "address_id", - "type": INTEGER(), - "nullable": True, - "default": None, "autoincrement": False, - "is_hidden": False, "comment": None, - }, - { - "name": "id_user", - "type": INTEGER(), - "nullable": True, "default": None, - "autoincrement": False, "is_hidden": False, - "comment": None, + "name": "test2", + "nullable": False, + "type": DOUBLE_PRECISION(precision=53), }, { - "name": "data", - "type": VARCHAR(length=30), - "nullable": True, - "default": None, "autoincrement": False, - "is_hidden": False, "comment": None, + "default": None, + "is_hidden": False, + "name": "parent_user_id", + "nullable": True, + "type": INTEGER(), }, ], } @@ -242,12 +242,12 @@ def test_get_multi_indexes(self): "column_names": ["data"], "column_sorting": {"data": ("nulls_first",)}, "dialect_options": { - "postgresql_ops": { - "data": None, - }, + "postgresql_include": [], + "postgresql_ops": {"data": None}, "postgresql_using": "prefix", }, "duplicates_constraint": "dingalings_data_key", + "include_columns": [], "name": "dingalings_data_key", "unique": True, }, @@ -258,13 +258,12 @@ def test_get_multi_indexes(self): "dingaling_id": ("nulls_first",), }, "dialect_options": { - "postgresql_ops": { - "address_id": None, - "dingaling_id": None, - }, + "postgresql_include": [], + "postgresql_ops": {"address_id": None, "dingaling_id": None}, "postgresql_using": "prefix", }, "duplicates_constraint": "zz_dingalings_multiple", + "include_columns": [], "name": "zz_dingalings_multiple", "unique": True, }, @@ -274,11 +273,11 @@ def test_get_multi_indexes(self): "column_names": ["email_address"], "column_sorting": {"email_address": ("nulls_first",)}, "dialect_options": { - "postgresql_ops": { - "email_address": None, - }, + "postgresql_include": [], + "postgresql_ops": {"email_address": None}, "postgresql_using": "prefix", }, + "include_columns": [], "name": "ix_email_addresses_email_address", "unique": False, } @@ -289,11 +288,11 @@ def test_get_multi_indexes(self): "column_names": ["q"], "column_sorting": {"q": ("desc", "nulls_last")}, "dialect_options": { - "postgresql_ops": { - "q": None, - }, + "postgresql_include": [], + "postgresql_ops": {"q": None}, "postgresql_using": "prefix", }, + "include_columns": [], "name": "noncol_idx_nopk", "unique": False, } @@ -303,11 +302,11 @@ def test_get_multi_indexes(self): "column_names": ["q"], "column_sorting": {"q": ("desc", "nulls_last")}, "dialect_options": { - "postgresql_ops": { - "q": None, - }, + "postgresql_include": [], + "postgresql_ops": {"q": None}, "postgresql_using": "prefix", }, + "include_columns": [], "name": "noncol_idx_pk", "unique": False, } @@ -321,13 +320,11 @@ def test_get_multi_indexes(self): "user_id": ("nulls_first",), }, "dialect_options": { - "postgresql_ops": { - "test1": None, - "test2": None, - "user_id": None, - }, + "postgresql_include": [], + "postgresql_ops": {"test1": None, "test2": None, "user_id": None}, "postgresql_using": "prefix", }, + "include_columns": [], "name": "users_all_idx", "unique": False, }, @@ -335,13 +332,12 @@ def test_get_multi_indexes(self): "column_names": ["test1", "test2"], "column_sorting": {"test1": ("nulls_first",), "test2": ("nulls_first",)}, "dialect_options": { - "postgresql_ops": { - "test1": None, - "test2": None, - }, + "postgresql_include": [], + "postgresql_ops": {"test1": None, "test2": None}, "postgresql_using": "prefix", }, "duplicates_constraint": "users_t_idx", + "include_columns": [], "name": "users_t_idx", "unique": True, }, @@ -358,36 +354,43 @@ def test_get_multi_pk_constraint(self): (None, "comment_test"): { "comment": None, "constrained_columns": ["id"], + "dialect_options": {"postgresql_include": []}, "name": "comment_test_pkey", }, (None, "dingalings"): { "comment": None, "constrained_columns": ["dingaling_id"], + "dialect_options": {"postgresql_include": []}, "name": "dingalings_pkey", }, (None, "email_addresses"): { "comment": "ea pk comment", "constrained_columns": ["address_id"], + "dialect_options": {"postgresql_include": []}, "name": "email_ad_pk", }, (None, "no_constraints"): { "comment": None, "constrained_columns": ["rowid"], + "dialect_options": {"postgresql_include": []}, "name": "no_constraints_pkey", }, (None, "noncol_idx_test_nopk"): { "comment": None, "constrained_columns": ["rowid"], + "dialect_options": {"postgresql_include": []}, "name": "noncol_idx_test_nopk_pkey", }, (None, "noncol_idx_test_pk"): { "comment": None, "constrained_columns": ["id"], + "dialect_options": {"postgresql_include": []}, "name": "noncol_idx_test_pk_pkey", }, (None, "users"): { "comment": None, "constrained_columns": ["user_id"], + "dialect_options": {"postgresql_include": []}, "name": "users_pkey", }, },