diff options
Diffstat (limited to 'test/sql')
| -rw-r--r-- | test/sql/test_functions.py | 8 | ||||
| -rw-r--r-- | test/sql/test_operators.py | 149 | ||||
| -rw-r--r-- | test/sql/test_types.py | 109 |
3 files changed, 222 insertions, 44 deletions
diff --git a/test/sql/test_functions.py b/test/sql/test_functions.py index 51cfcb919..0074d789b 100644 --- a/test/sql/test_functions.py +++ b/test/sql/test_functions.py @@ -2,7 +2,7 @@ from sqlalchemy.testing import eq_, is_ import datetime from sqlalchemy import func, select, Integer, literal, DateTime, Table, \ Column, Sequence, MetaData, extract, Date, String, bindparam, \ - literal_column, Array, Numeric + literal_column, ARRAY, Numeric from sqlalchemy.sql import table, column from sqlalchemy import sql, util from sqlalchemy.sql.compiler import BIND_TEMPLATES @@ -558,7 +558,7 @@ class ReturnTypeTest(fixtures.TestBase): def test_array_agg(self): expr = func.array_agg(column('data', Integer)) - is_(expr.type._type_affinity, Array) + is_(expr.type._type_affinity, ARRAY) is_(expr.type.item_type._type_affinity, Integer) def test_mode(self): @@ -573,13 +573,13 @@ class ReturnTypeTest(fixtures.TestBase): def test_percentile_cont_array(self): expr = func.percentile_cont(0.5, 0.7).within_group( column('data', Integer)) - is_(expr.type._type_affinity, Array) + is_(expr.type._type_affinity, ARRAY) is_(expr.type.item_type._type_affinity, Integer) def test_percentile_cont_array_desc(self): expr = func.percentile_cont(0.5, 0.7).within_group( column('data', Integer).desc()) - is_(expr.type._type_affinity, Array) + is_(expr.type._type_affinity, ARRAY) is_(expr.type.item_type._type_affinity, Integer) def test_cume_dist(self): diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index 03c0f89be..6a6c749a4 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -15,7 +15,7 @@ from sqlalchemy.sql.elements import _literal_as_text from sqlalchemy.schema import Column, Table, MetaData from sqlalchemy.sql import compiler from sqlalchemy.types import TypeEngine, TypeDecorator, UserDefinedType, \ - Boolean, NullType, MatchType, Indexable, Concatenable, Array + Boolean, NullType, MatchType, Indexable, Concatenable, ARRAY, JSON from sqlalchemy.dialects import mysql, firebird, postgresql, oracle, \ sqlite, mssql from sqlalchemy import util @@ -632,7 +632,125 @@ class ExtensionOperatorTest(fixtures.TestBase, testing.AssertsCompiledSQL): ) -class IndexableTest(fixtures.TestBase, testing.AssertsCompiledSQL): +class JSONIndexOpTest(fixtures.TestBase, testing.AssertsCompiledSQL): + def setUp(self): + class MyTypeCompiler(compiler.GenericTypeCompiler): + def visit_mytype(self, type, **kw): + return "MYTYPE" + + def visit_myothertype(self, type, **kw): + return "MYOTHERTYPE" + + class MyCompiler(compiler.SQLCompiler): + + def visit_json_getitem_op_binary(self, binary, operator, **kw): + return self._generate_generic_binary( + binary, " -> ", **kw + ) + + def visit_json_path_getitem_op_binary( + self, binary, operator, **kw): + return self._generate_generic_binary( + binary, " #> ", **kw + ) + + def visit_getitem_binary(self, binary, operator, **kw): + raise NotImplementedError() + + class MyDialect(default.DefaultDialect): + statement_compiler = MyCompiler + type_compiler = MyTypeCompiler + + class MyType(JSON): + __visit_name__ = 'mytype' + + pass + + self.MyType = MyType + self.__dialect__ = MyDialect() + + def test_setup_getitem(self): + col = Column('x', self.MyType()) + + is_( + col[5].type._type_affinity, JSON + ) + is_( + col[5]['foo'].type._type_affinity, JSON + ) + is_( + col[('a', 'b', 'c')].type._type_affinity, JSON + ) + + def test_getindex_literal_integer(self): + + col = Column('x', self.MyType()) + + self.assert_compile( + col[5], + "x -> :x_1", + checkparams={'x_1': 5} + ) + + def test_getindex_literal_string(self): + + col = Column('x', self.MyType()) + + self.assert_compile( + col['foo'], + "x -> :x_1", + checkparams={'x_1': 'foo'} + ) + + def test_path_getindex_literal(self): + + col = Column('x', self.MyType()) + + self.assert_compile( + col[('a', 'b', 3, 4, 'd')], + "x #> :x_1", + checkparams={'x_1': ('a', 'b', 3, 4, 'd')} + ) + + def test_getindex_sqlexpr(self): + + col = Column('x', self.MyType()) + col2 = Column('y', Integer()) + + self.assert_compile( + col[col2], + "x -> y", + checkparams={} + ) + + self.assert_compile( + col[col2 + 8], + "x -> (y + :y_1)", + checkparams={'y_1': 8} + ) + + def test_override_operators(self): + special_index_op = operators.custom_op('$$>') + + class MyOtherType(JSON, TypeEngine): + __visit_name__ = 'myothertype' + + class Comparator(TypeEngine.Comparator): + + def _adapt_expression(self, op, other_comparator): + return special_index_op, MyOtherType() + + comparator_factory = Comparator + + col = Column('x', MyOtherType()) + self.assert_compile( + col[5], + "x $$> :x_1", + checkparams={'x_1': 5} + ) + + +class ArrayIndexOpTest(fixtures.TestBase, testing.AssertsCompiledSQL): def setUp(self): class MyTypeCompiler(compiler.GenericTypeCompiler): def visit_mytype(self, type, **kw): @@ -658,31 +776,14 @@ class IndexableTest(fixtures.TestBase, testing.AssertsCompiledSQL): statement_compiler = MyCompiler type_compiler = MyTypeCompiler - class MyType(Indexable, TypeEngine): + class MyType(ARRAY): __visit_name__ = 'mytype' def __init__(self, zero_indexes=False, dimensions=1): if zero_indexes: self.zero_indexes = zero_indexes self.dimensions = dimensions - - class Comparator(Indexable.Comparator): - def _setup_getitem(self, index): - if isinstance(index, slice): - return_type = self.type - elif self.type.dimensions is None or \ - self.type.dimensions == 1: - return_type = Integer() - else: - adapt_kw = {'dimensions': self.type.dimensions - 1} - # this is also testing the behavior of adapt() - # that we can pass kw that override constructor kws. - # required a small change to util.constructor_copy(). - return_type = self.type.adapt( - self.type.__class__, **adapt_kw) - - return operators.getitem, index, return_type - comparator_factory = Comparator + self.item_type = Integer() self.MyType = MyType self.__dialect__ = MyDialect() @@ -694,13 +795,13 @@ class IndexableTest(fixtures.TestBase, testing.AssertsCompiledSQL): col = Column('x', self.MyType(dimensions=3)) is_( - col[5].type._type_affinity, self.MyType + col[5].type._type_affinity, ARRAY ) eq_( col[5].type.dimensions, 2 ) is_( - col[5][6].type._type_affinity, self.MyType + col[5][6].type._type_affinity, ARRAY ) eq_( col[5][6].type.dimensions, 1 @@ -2273,7 +2374,7 @@ class AnyAllTest(fixtures.TestBase, testing.AssertsCompiledSQL): t = Table( 'tab1', m, - Column('arrval', Array(Integer)), + Column('arrval', ARRAY(Integer)), Column('data', Integer) ) return t diff --git a/test/sql/test_types.py b/test/sql/test_types.py index f1fb611fb..bb227bc5d 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -10,14 +10,14 @@ from sqlalchemy import ( and_, func, Date, LargeBinary, literal, cast, text, Enum, type_coerce, VARCHAR, Time, DateTime, BigInteger, SmallInteger, BOOLEAN, BLOB, NCHAR, NVARCHAR, CLOB, TIME, DATE, DATETIME, TIMESTAMP, SMALLINT, - INTEGER, DECIMAL, NUMERIC, FLOAT, REAL, Array) + INTEGER, DECIMAL, NUMERIC, FLOAT, REAL, ARRAY, JSON) from sqlalchemy.sql import ddl from sqlalchemy.sql import visitors from sqlalchemy import inspection from sqlalchemy import exc, types, util, dialects for name in dialects.__all__: __import__("sqlalchemy.dialects.%s" % name) -from sqlalchemy.sql import operators, column, table +from sqlalchemy.sql import operators, column, table, null from sqlalchemy.schema import CheckConstraint, AddConstraint from sqlalchemy.engine import default from sqlalchemy.testing.schema import Table, Column @@ -140,7 +140,7 @@ class AdaptTest(fixtures.TestBase): for is_down_adaption, typ, target_adaptions in adaptions(): if typ in (types.TypeDecorator, types.TypeEngine, types.Variant): continue - elif issubclass(typ, Array): + elif issubclass(typ, ARRAY): t1 = typ(String) else: t1 = typ() @@ -148,6 +148,8 @@ class AdaptTest(fixtures.TestBase): if not issubclass(typ, types.Enum) and \ issubclass(cls, types.Enum): continue + if cls.__module__.startswith("test"): + continue # print("ADAPT %s -> %s" % (t1.__class__, cls)) t2 = t1.adapt(cls) @@ -190,7 +192,7 @@ class AdaptTest(fixtures.TestBase): for typ in self._all_types(): if typ in (types.TypeDecorator, types.TypeEngine, types.Variant): continue - elif issubclass(typ, Array): + elif issubclass(typ, ARRAY): t1 = typ(String) else: t1 = typ() @@ -1406,23 +1408,98 @@ class BinaryTest(fixtures.TestBase, AssertsExecutionResults): return o.read() +class JSONTest(fixtures.TestBase): + + def setup(self): + metadata = MetaData() + self.test_table = Table('test_table', metadata, + Column('id', Integer, primary_key=True), + Column('test_column', JSON), + ) + self.jsoncol = self.test_table.c.test_column + + self.dialect = default.DefaultDialect() + self.dialect._json_serializer = None + self.dialect._json_deserializer = None + + def test_bind_serialize_default(self): + proc = self.test_table.c.test_column.type._cached_bind_processor( + self.dialect) + eq_( + proc({"A": [1, 2, 3, True, False]}), + '{"A": [1, 2, 3, true, false]}' + ) + + def test_bind_serialize_None(self): + proc = self.test_table.c.test_column.type._cached_bind_processor( + self.dialect) + eq_( + proc(None), + 'null' + ) + + def test_bind_serialize_none_as_null(self): + proc = JSON(none_as_null=True)._cached_bind_processor( + self.dialect) + eq_( + proc(None), + None + ) + eq_( + proc(null()), + None + ) + + def test_bind_serialize_null(self): + proc = self.test_table.c.test_column.type._cached_bind_processor( + self.dialect) + eq_( + proc(null()), + None + ) + + def test_result_deserialize_default(self): + proc = self.test_table.c.test_column.type._cached_result_processor( + self.dialect, None) + eq_( + proc('{"A": [1, 2, 3, true, false]}'), + {"A": [1, 2, 3, True, False]} + ) + + def test_result_deserialize_null(self): + proc = self.test_table.c.test_column.type._cached_result_processor( + self.dialect, None) + eq_( + proc('null'), + None + ) + + def test_result_deserialize_None(self): + proc = self.test_table.c.test_column.type._cached_result_processor( + self.dialect, None) + eq_( + proc(None), + None + ) + + class ArrayTest(fixtures.TestBase): def _myarray_fixture(self): - class MyArray(Array): + class MyArray(ARRAY): pass return MyArray def test_array_index_map_dimensions(self): - col = column('x', Array(Integer, dimensions=3)) + col = column('x', ARRAY(Integer, dimensions=3)) is_( - col[5].type._type_affinity, Array + col[5].type._type_affinity, ARRAY ) eq_( col[5].type.dimensions, 2 ) is_( - col[5][6].type._type_affinity, Array + col[5][6].type._type_affinity, ARRAY ) eq_( col[5][6].type.dimensions, 1 @@ -1435,8 +1512,8 @@ class ArrayTest(fixtures.TestBase): m = MetaData() arrtable = Table( 'arrtable', m, - Column('intarr', Array(Integer)), - Column('strarr', Array(String)), + Column('intarr', ARRAY(Integer)), + Column('strarr', ARRAY(String)), ) is_(arrtable.c.intarr[1].type._type_affinity, Integer) is_(arrtable.c.strarr[1].type._type_affinity, String) @@ -1445,11 +1522,11 @@ class ArrayTest(fixtures.TestBase): m = MetaData() arrtable = Table( 'arrtable', m, - Column('intarr', Array(Integer)), - Column('strarr', Array(String)), + Column('intarr', ARRAY(Integer)), + Column('strarr', ARRAY(String)), ) - is_(arrtable.c.intarr[1:3].type._type_affinity, Array) - is_(arrtable.c.strarr[1:3].type._type_affinity, Array) + is_(arrtable.c.intarr[1:3].type._type_affinity, ARRAY) + is_(arrtable.c.strarr[1:3].type._type_affinity, ARRAY) def test_array_getitem_slice_type_dialect_level(self): MyArray = self._myarray_fixture() @@ -1459,8 +1536,8 @@ class ArrayTest(fixtures.TestBase): Column('intarr', MyArray(Integer)), Column('strarr', MyArray(String)), ) - is_(arrtable.c.intarr[1:3].type._type_affinity, Array) - is_(arrtable.c.strarr[1:3].type._type_affinity, Array) + is_(arrtable.c.intarr[1:3].type._type_affinity, ARRAY) + is_(arrtable.c.strarr[1:3].type._type_affinity, ARRAY) # but the slice returns the actual type assert isinstance(arrtable.c.intarr[1:3].type, MyArray) |
