From 4732e0765b6aa01147f2655a99d65ef6a4004404 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Tue, 16 Jan 2024 00:58:10 +0100 Subject: [PATCH] Types: Improve type mappings - Consequently use upper-case type definitions from `sqlalchemy.types` - Add `timestamp without time zone` types (scalar and array) - On SQLAlchemy 2, map `real` and `double{_precision}` types to the newly introduced `sqltypes.{DOUBLE,DOUBLE_PRECISION}` types All of this is intended to improve reverse type lookups / reflections. --- docs/inspection-reflection.rst | 4 +-- src/sqlalchemy_cratedb/dialect.py | 57 ++++++++++++++++++------------- tests/integration.py | 12 +++++-- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/docs/inspection-reflection.rst b/docs/inspection-reflection.rst index 3faa609..253baa0 100644 --- a/docs/inspection-reflection.rst +++ b/docs/inspection-reflection.rst @@ -87,10 +87,10 @@ Create a SQLAlchemy table object: Reflect column data types from the table metadata: >>> table.columns.get('name') - Column('name', String(), table=) + Column('name', VARCHAR(), table=) >>> table.primary_key - PrimaryKeyConstraint(Column('id', String(), table=, primary_key=True... + PrimaryKeyConstraint(Column('id', VARCHAR(), table=, primary_key=True... CrateDialect diff --git a/src/sqlalchemy_cratedb/dialect.py b/src/sqlalchemy_cratedb/dialect.py index 6944f2c..61b4237 100644 --- a/src/sqlalchemy_cratedb/dialect.py +++ b/src/sqlalchemy_cratedb/dialect.py @@ -37,41 +37,52 @@ from .type import FloatVector, ObjectArray, ObjectType TYPES_MAP = { - "boolean": sqltypes.Boolean, - "short": sqltypes.SmallInteger, - "smallint": sqltypes.SmallInteger, + "boolean": sqltypes.BOOLEAN, + "short": sqltypes.SMALLINT, + "smallint": sqltypes.SMALLINT, "timestamp": sqltypes.TIMESTAMP, "timestamp with time zone": sqltypes.TIMESTAMP, + "timestamp without time zone": sqltypes.TIMESTAMP, "object": ObjectType, - "integer": sqltypes.Integer, - "long": sqltypes.NUMERIC, - "bigint": sqltypes.NUMERIC, + "object_array": ObjectArray, # TODO: Can this also be improved to use `sqltypes.ARRAY`? + "integer": sqltypes.INTEGER, + "long": sqltypes.BIGINT, + "bigint": sqltypes.BIGINT, + "float": sqltypes.FLOAT, "double": sqltypes.DECIMAL, "double precision": sqltypes.DECIMAL, - "object_array": ObjectArray, - "float": sqltypes.Float, - "real": sqltypes.Float, - "string": sqltypes.String, - "text": sqltypes.String, + "real": sqltypes.REAL, + "string": sqltypes.VARCHAR, + "text": sqltypes.VARCHAR, "float_vector": FloatVector, } try: # SQLAlchemy >= 1.1 from sqlalchemy.types import ARRAY - TYPES_MAP["integer_array"] = ARRAY(sqltypes.Integer) - TYPES_MAP["boolean_array"] = ARRAY(sqltypes.Boolean) - TYPES_MAP["short_array"] = ARRAY(sqltypes.SmallInteger) - TYPES_MAP["smallint_array"] = ARRAY(sqltypes.SmallInteger) + TYPES_MAP["integer_array"] = ARRAY(sqltypes.INTEGER) + TYPES_MAP["boolean_array"] = ARRAY(sqltypes.BOOLEAN) + TYPES_MAP["short_array"] = ARRAY(sqltypes.SMALLINT) + TYPES_MAP["smallint_array"] = ARRAY(sqltypes.SMALLINT) TYPES_MAP["timestamp_array"] = ARRAY(sqltypes.TIMESTAMP) TYPES_MAP["timestamp with time zone_array"] = ARRAY(sqltypes.TIMESTAMP) - TYPES_MAP["long_array"] = ARRAY(sqltypes.NUMERIC) - TYPES_MAP["bigint_array"] = ARRAY(sqltypes.NUMERIC) - TYPES_MAP["double_array"] = ARRAY(sqltypes.DECIMAL) - TYPES_MAP["double precision_array"] = ARRAY(sqltypes.DECIMAL) - TYPES_MAP["float_array"] = ARRAY(sqltypes.Float) - TYPES_MAP["real_array"] = ARRAY(sqltypes.Float) - TYPES_MAP["string_array"] = ARRAY(sqltypes.String) - TYPES_MAP["text_array"] = ARRAY(sqltypes.String) + TYPES_MAP["timestamp without time zone_array"] = ARRAY(sqltypes.TIMESTAMP) + TYPES_MAP["long_array"] = ARRAY(sqltypes.BIGINT) + TYPES_MAP["bigint_array"] = ARRAY(sqltypes.BIGINT) + TYPES_MAP["float_array"] = ARRAY(sqltypes.FLOAT) + TYPES_MAP["real_array"] = ARRAY(sqltypes.REAL) + TYPES_MAP["string_array"] = ARRAY(sqltypes.VARCHAR) + TYPES_MAP["text_array"] = ARRAY(sqltypes.VARCHAR) +except Exception: + pass +try: + # SQLAlchemy >= 2.0 + from sqlalchemy.types import DOUBLE, DOUBLE_PRECISION + TYPES_MAP["real"] = DOUBLE + TYPES_MAP["real_array"] = ARRAY(DOUBLE) + TYPES_MAP["double"] = DOUBLE + TYPES_MAP["double_array"] = ARRAY(DOUBLE) + TYPES_MAP["double precision"] = DOUBLE_PRECISION + TYPES_MAP["double precision_array"] = ARRAY(DOUBLE_PRECISION) except Exception: pass diff --git a/tests/integration.py b/tests/integration.py index 80d155e..3786377 100644 --- a/tests/integration.py +++ b/tests/integration.py @@ -29,7 +29,7 @@ import logging from crate.client import connect -from sqlalchemy_cratedb import SA_VERSION, SA_2_0 +from sqlalchemy_cratedb import SA_VERSION, SA_2_0, SA_1_4 from tests.settings import crate_host log = logging.getLogger() @@ -184,16 +184,22 @@ def create_test_suite(): 'docs/crud.rst', 'docs/working-with-types.rst', 'docs/advanced-querying.rst', - 'docs/inspection-reflection.rst', ] - # Don't run DataFrame integration tests on SQLAlchemy 1.3 and Python 3.7. + # Don't run DataFrame integration tests on SQLAlchemy 1.4 and earlier, or Python 3.7. skip_dataframe = SA_VERSION < SA_2_0 or sys.version_info < (3, 8) if not skip_dataframe: sqlalchemy_integration_tests += [ 'docs/dataframe.rst', ] + # Don't run reflection integration tests on SQLAlchemy 1.3 and earlier and Python 3.10 and 3.11. + skip_reflection = SA_VERSION < SA_1_4 and (3, 10) <= sys.version_info < (3, 12) + if not skip_reflection: + sqlalchemy_integration_tests += [ + 'docs/inspection-reflection.rst', + ] + s = doctest.DocFileSuite( *sqlalchemy_integration_tests, module_relative=False,