forked from SAP/sqlalchemy-hana
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_hana_types.py
358 lines (255 loc) · 10.6 KB
/
test_hana_types.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# Copyright 2022 SAP SE.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http: //www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import datetime
import decimal
import random
import pytest
import sqlalchemy
import sqlalchemy.testing.suite.test_types
from sqlalchemy.testing.suite.test_types import _DateFixture
import sqlalchemy_hana
import sqlalchemy_hana.types
class HANATypesTest(sqlalchemy.testing.fixtures.TestBase):
__dialect__ = "hana"
# As documented in "SAP HANA SQL Reference Guide for SAP HANA Platform - Data Types"
# https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/20a1569875191014b507cf392724b7eb.html
DOCUMENTED_HANA_TYPES = (
"DATE", "TIME", "SECONDDATE", "TIMESTAMP",
"TINYINT", "SMALLINT", "INTEGER", "BIGINT", "SMALLDECIMAL", "DECIMAL", "REAL", "DOUBLE",
"BOOLEAN",
"VARCHAR", "NVARCHAR", "ALPHANUM", "SHORTTEXT",
"VARBINARY",
"BLOB", 'CLOB', "NCLOB", "TEXT",
# Not yet supported types:
# "ARRAY",
# "ST_GEOMETRY", "ST_POINT"
)
# Some types are not documented but still usable and could occur during table reflection
UNDOCUMENTED_HANA_TYPES = (
"CHAR", "NCHAR",
)
@pytest.mark.parametrize("type_name", DOCUMENTED_HANA_TYPES)
def test_documented_hana_types_are_available_as_class(self, type_name):
assert hasattr(sqlalchemy_hana.types, type_name)
@pytest.mark.parametrize("type_name", UNDOCUMENTED_HANA_TYPES)
def test_undocumented_hana_types_are_available_as_class(self, type_name):
assert hasattr(sqlalchemy_hana.types, type_name)
class HANASECONDDATETest(_DateFixture, sqlalchemy.testing.fixtures.TablesTest):
__backend__ = True
datatype = sqlalchemy_hana.types.SECONDDATE
data = datetime.datetime(2012, 10, 15, 12, 57, 18, 12345)
compare = datetime.datetime(2012, 10, 15, 12, 57, 18)
class _HANATypeTest(sqlalchemy.testing.fixtures.TablesTest):
compare = None
hana_version_skip = None
@classmethod
def _init_class(cls):
super()._init_class()
if cls.hana_version_skip:
if cls.bind.dialect.server_version_info[0] == cls.hana_version_skip:
pytest.skip(
(
"Test/Feature is incompatible with SAP HANA version {} "
"(connected to SAP HANA {})"
).format(
cls.hana_version_skip,
'.'.join((str(i) for i in cls.bind.dialect.server_version_info))
)
)
@classmethod
def define_tables(cls, metadata):
sqlalchemy.Table(
"test_type",
metadata,
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
sqlalchemy.Column("data", cls.column_type)
)
@property
def reflected_column_type(self):
return self.column_type
def test_round_trip(self, connection):
test_type = self.tables.test_type
connection.execute(
test_type.insert(), {"id": 1, "data": self.data}
)
row = connection.execute(
sqlalchemy.select([test_type.c.data])
).first()
compare = self.compare or self.data
assert row == (compare,)
assert isinstance(row[0], type(compare))
def test_reflection(self, connection):
columns = sqlalchemy.inspect(connection).get_columns("test_type")
assert isinstance(columns[1]['type'], self.reflected_column_type.__class__)
assert repr(columns[1]['type']) == repr(self.reflected_column_type)
class _HANAIntegerTypeTest(_HANATypeTest):
def test_round_trip(self, connection):
self.data = random.randint(self.min_value, self.max_value)
super().test_round_trip(connection)
def test_underflow_int(self, connection):
test_type = self.tables.test_type
with pytest.raises(sqlalchemy.exc.DBAPIError):
connection.execute(
test_type.insert(), {"id": 1, "data": self.min_value - 1}
)
def test_overflow_int(self, connection):
test_type = self.tables.test_type
with pytest.raises(sqlalchemy.exc.DBAPIError):
connection.execute(
test_type.insert(), {"id": 1, "data": self.max_value + 1}
)
class HANATINYINTTest(_HANAIntegerTypeTest):
column_type = sqlalchemy_hana.types.TINYINT()
min_value = 0
max_value = 255
class HANASMALLINTTest(_HANAIntegerTypeTest):
column_type = sqlalchemy_hana.types.SMALLINT()
min_value = -32768
max_value = 32767
class HANAINTEGERTest(_HANAIntegerTypeTest):
column_type = sqlalchemy_hana.types.INTEGER()
min_value = -2147483648
max_value = 2147483647
class HANABIGINTTest(_HANAIntegerTypeTest):
column_type = sqlalchemy_hana.types.BIGINT()
min_value = -9_223_372_036_854_775_808
max_value = 9_223_372_036_854_775_807
class _HANADecimalTypeTest(_HANATypeTest):
pass
class HANASMALLDECIMALTest(_HANADecimalTypeTest):
column_type = sqlalchemy_hana.types.SMALLDECIMAL()
data = decimal.Decimal("3.14")
class HANADECIMALTest(_HANADecimalTypeTest):
column_type = sqlalchemy_hana.types.DECIMAL(5, 4)
data = decimal.Decimal("3.141592")
compare = decimal.Decimal("3.1415")
class HANAREALTest(_HANADecimalTypeTest):
column_type = sqlalchemy_hana.types.REAL()
data = 3.141592
compare = 3.141592025756836
class HANADOUBLETest(_HANADecimalTypeTest):
column_type = sqlalchemy_hana.types.DOUBLE()
data = 3.141592
class HANABOOLEANTest(_HANATypeTest):
column_type = sqlalchemy_hana.types.BOOLEAN()
data = True
class HANAVARCHARTest(_HANATypeTest):
column_type = sqlalchemy_hana.types.VARCHAR(length=15)
data = "Some text"
@property
def reflected_column_type(self):
if self.bind.dialect.server_version_info[0] == 4:
return sqlalchemy_hana.types.NVARCHAR(length=15)
else:
return self.column_type
class HANANVARCHARTest(_HANATypeTest):
column_type = sqlalchemy_hana.types.NVARCHAR(length=15)
data = "Some text"
class HANANVARCHAROperatorTest(sqlalchemy.testing.fixtures.TablesTest, sqlalchemy.testing.AssertsCompiledSQL):
__only_on__ = "hana"
__backend__ = True
@classmethod
def define_tables(cls, metadata):
sqlalchemy.Table(
"data_table",
metadata,
sqlalchemy.Column("id", sqlalchemy.types.Integer, primary_key=True),
sqlalchemy.Column("data", sqlalchemy_hana.types.NVARCHAR(50)),
)
@classmethod
def insert_data(cls, connection):
connection.execute(
cls.tables.data_table.insert(),
[
{"id": 1, "data": "abc123"},
{"id": 2, "data": "abc"},
{"id": 3, "data": "123"}
],
)
def test_nvarchar_supports_like_regex(self):
data_table = self.tables.data_table
query = sqlalchemy.select([data_table]).where(data_table.c.data.like_regex(r'^[a-z]+\d+$'))
self.assert_compile(
query,
"SELECT data_table.id, data_table.data FROM data_table WHERE data_table.data LIKE_REGEXPR ?"
)
assert sqlalchemy.testing.db.execute(query).fetchall() == [(1, 'abc123'),]
def test_nvarchar_supports_negate_like_regex(self):
data_table = self.tables.data_table
query = sqlalchemy.select([data_table]).where(~data_table.c.data.like_regex(r'\d+'))
self.assert_compile(
query,
"SELECT data_table.id, data_table.data FROM data_table WHERE data_table.data NOT LIKE_REGEXPR ?"
)
assert sqlalchemy.testing.db.execute(query).fetchall() == [(2, 'abc'),]
def test_nvarchar_supports_not_like_regex(self):
data_table = self.tables.data_table
query = sqlalchemy.select([data_table]).where(data_table.c.data.notlike_regex(r'[a-z]+'))
self.assert_compile(
query,
"SELECT data_table.id, data_table.data FROM data_table WHERE data_table.data NOT LIKE_REGEXPR ?"
)
assert sqlalchemy.testing.db.execute(query).fetchall() == [(3, '123'),]
def test_nvarchar_supports_negate_not_like_regex(self):
data_table = self.tables.data_table
query = sqlalchemy.select([data_table]).where(~data_table.c.data.notlike_regex(r'\s+'))
self.assert_compile(
query,
"SELECT data_table.id, data_table.data FROM data_table WHERE data_table.data LIKE_REGEXPR ?"
)
assert sqlalchemy.testing.db.execute(query).fetchall() == []
class HANAALPHANUMTest(_HANATypeTest):
hana_version_skip = 4
column_type = sqlalchemy_hana.types.ALPHANUM(length=60)
data = "Some alpha numeric text with numbers 1234567899"
class HANASHORTTEXTTest(_HANATypeTest):
hana_version_skip = 4
column_type = sqlalchemy_hana.types.SHORTTEXT(length=60)
data = "Some data for a HANA short text"
class HANACHARTest(_HANATypeTest):
column_type = sqlalchemy_hana.types.CHAR(length=60)
data = "Some data for a HANA short text"
@property
def reflected_column_type(self):
if self.bind.dialect.server_version_info[0] == 4:
return sqlalchemy_hana.types.NCHAR(length=60)
else:
return self.column_type
class HANANCHARTest(_HANATypeTest):
column_type = sqlalchemy_hana.types.NCHAR(length=60)
data = "Some data for a HANA short text"
class HANAVARBINARYTest(_HANATypeTest):
column_type = sqlalchemy_hana.types.VARBINARY(length=60)
data = b"Some binary for a HANA VARBINARY"
class _HANALOBTest(_HANATypeTest):
pass
class HANABLOBTest(_HANALOBTest):
column_type = sqlalchemy_hana.types.BLOB()
data = b"some binary data"
class HANACLOBTest(_HANALOBTest):
column_type = sqlalchemy_hana.types.CLOB()
data = "some test text"
@property
def reflected_column_type(self):
if self.bind.dialect.server_version_info[0] == 4:
return sqlalchemy_hana.types.NCLOB()
else:
return self.column_type
class HANANCBLOBTest(_HANALOBTest):
column_type = sqlalchemy_hana.types.NCLOB()
data = "some test text"
class HANATEXTTest(_HANALOBTest):
hana_version_skip = 4
column_type = sqlalchemy_hana.types.TEXT()
data = "some test text"