singlestoredb 0.3.3__py3-none-any.whl → 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of singlestoredb might be problematic. Click here for more details.

Files changed (121) hide show
  1. singlestoredb/__init__.py +33 -2
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +6 -4
  4. singlestoredb/config.py +116 -16
  5. singlestoredb/connection.py +489 -523
  6. singlestoredb/converters.py +275 -26
  7. singlestoredb/exceptions.py +30 -4
  8. singlestoredb/functions/__init__.py +1 -0
  9. singlestoredb/functions/decorator.py +142 -0
  10. singlestoredb/functions/dtypes.py +1639 -0
  11. singlestoredb/functions/ext/__init__.py +2 -0
  12. singlestoredb/functions/ext/arrow.py +375 -0
  13. singlestoredb/functions/ext/asgi.py +661 -0
  14. singlestoredb/functions/ext/json.py +427 -0
  15. singlestoredb/functions/ext/mmap.py +306 -0
  16. singlestoredb/functions/ext/rowdat_1.py +744 -0
  17. singlestoredb/functions/signature.py +673 -0
  18. singlestoredb/fusion/__init__.py +11 -0
  19. singlestoredb/fusion/graphql.py +213 -0
  20. singlestoredb/fusion/handler.py +621 -0
  21. singlestoredb/fusion/handlers/__init__.py +0 -0
  22. singlestoredb/fusion/handlers/stage.py +257 -0
  23. singlestoredb/fusion/handlers/utils.py +162 -0
  24. singlestoredb/fusion/handlers/workspace.py +412 -0
  25. singlestoredb/fusion/registry.py +164 -0
  26. singlestoredb/fusion/result.py +399 -0
  27. singlestoredb/http/__init__.py +27 -0
  28. singlestoredb/http/connection.py +1192 -0
  29. singlestoredb/management/__init__.py +3 -2
  30. singlestoredb/management/billing_usage.py +148 -0
  31. singlestoredb/management/cluster.py +19 -14
  32. singlestoredb/management/manager.py +100 -40
  33. singlestoredb/management/organization.py +188 -0
  34. singlestoredb/management/region.py +6 -8
  35. singlestoredb/management/utils.py +253 -4
  36. singlestoredb/management/workspace.py +1153 -35
  37. singlestoredb/mysql/__init__.py +177 -0
  38. singlestoredb/mysql/_auth.py +298 -0
  39. singlestoredb/mysql/charset.py +214 -0
  40. singlestoredb/mysql/connection.py +1814 -0
  41. singlestoredb/mysql/constants/CLIENT.py +38 -0
  42. singlestoredb/mysql/constants/COMMAND.py +32 -0
  43. singlestoredb/mysql/constants/CR.py +78 -0
  44. singlestoredb/mysql/constants/ER.py +474 -0
  45. singlestoredb/mysql/constants/FIELD_TYPE.py +32 -0
  46. singlestoredb/mysql/constants/FLAG.py +15 -0
  47. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  48. singlestoredb/mysql/constants/__init__.py +0 -0
  49. singlestoredb/mysql/converters.py +271 -0
  50. singlestoredb/mysql/cursors.py +713 -0
  51. singlestoredb/mysql/err.py +92 -0
  52. singlestoredb/mysql/optionfile.py +20 -0
  53. singlestoredb/mysql/protocol.py +388 -0
  54. singlestoredb/mysql/tests/__init__.py +19 -0
  55. singlestoredb/mysql/tests/base.py +126 -0
  56. singlestoredb/mysql/tests/conftest.py +37 -0
  57. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  58. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  59. singlestoredb/mysql/tests/test_basic.py +452 -0
  60. singlestoredb/mysql/tests/test_connection.py +851 -0
  61. singlestoredb/mysql/tests/test_converters.py +58 -0
  62. singlestoredb/mysql/tests/test_cursor.py +141 -0
  63. singlestoredb/mysql/tests/test_err.py +16 -0
  64. singlestoredb/mysql/tests/test_issues.py +514 -0
  65. singlestoredb/mysql/tests/test_load_local.py +75 -0
  66. singlestoredb/mysql/tests/test_nextset.py +88 -0
  67. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  68. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  69. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  70. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  71. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  72. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  73. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  74. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  75. singlestoredb/mysql/times.py +23 -0
  76. singlestoredb/pytest.py +283 -0
  77. singlestoredb/tests/empty.sql +0 -0
  78. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  79. singlestoredb/tests/test.sql +210 -0
  80. singlestoredb/tests/test2.sql +1 -0
  81. singlestoredb/tests/test_basics.py +482 -117
  82. singlestoredb/tests/test_config.py +13 -15
  83. singlestoredb/tests/test_connection.py +241 -289
  84. singlestoredb/tests/test_dbapi.py +27 -0
  85. singlestoredb/tests/test_exceptions.py +0 -2
  86. singlestoredb/tests/test_ext_func.py +1193 -0
  87. singlestoredb/tests/test_ext_func_data.py +1101 -0
  88. singlestoredb/tests/test_fusion.py +465 -0
  89. singlestoredb/tests/test_http.py +32 -28
  90. singlestoredb/tests/test_management.py +588 -10
  91. singlestoredb/tests/test_plugin.py +33 -0
  92. singlestoredb/tests/test_results.py +11 -14
  93. singlestoredb/tests/test_types.py +0 -2
  94. singlestoredb/tests/test_udf.py +687 -0
  95. singlestoredb/tests/test_xdict.py +0 -2
  96. singlestoredb/tests/utils.py +3 -4
  97. singlestoredb/types.py +4 -5
  98. singlestoredb/utils/config.py +71 -12
  99. singlestoredb/utils/convert_rows.py +0 -2
  100. singlestoredb/utils/debug.py +13 -0
  101. singlestoredb/utils/mogrify.py +151 -0
  102. singlestoredb/utils/results.py +4 -3
  103. singlestoredb/utils/xdict.py +12 -12
  104. singlestoredb-1.0.3.dist-info/METADATA +139 -0
  105. singlestoredb-1.0.3.dist-info/RECORD +112 -0
  106. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/WHEEL +1 -1
  107. singlestoredb-1.0.3.dist-info/entry_points.txt +2 -0
  108. singlestoredb/drivers/__init__.py +0 -46
  109. singlestoredb/drivers/base.py +0 -200
  110. singlestoredb/drivers/cymysql.py +0 -40
  111. singlestoredb/drivers/http.py +0 -49
  112. singlestoredb/drivers/mariadb.py +0 -42
  113. singlestoredb/drivers/mysqlconnector.py +0 -51
  114. singlestoredb/drivers/mysqldb.py +0 -62
  115. singlestoredb/drivers/pymysql.py +0 -39
  116. singlestoredb/drivers/pyodbc.py +0 -67
  117. singlestoredb/http.py +0 -794
  118. singlestoredb-0.3.3.dist-info/METADATA +0 -105
  119. singlestoredb-0.3.3.dist-info/RECORD +0 -46
  120. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/LICENSE +0 -0
  121. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,33 @@
1
1
  #!/usr/bin/env python
2
2
  # type: ignore
3
3
  """Basic SingleStoreDB connection testing."""
4
- from __future__ import annotations
5
-
6
4
  import datetime
7
5
  import decimal
6
+ import math
8
7
  import os
9
8
  import unittest
10
9
 
10
+ from requests.exceptions import InvalidJSONError
11
+
12
+ try:
13
+ import numpy as np
14
+ has_numpy = True
15
+ except ImportError:
16
+ has_numpy = False
17
+
18
+ try:
19
+ import shapely.wkt
20
+ has_shapely = True
21
+ except ImportError:
22
+ has_shapely = False
23
+
24
+ try:
25
+ import pygeos
26
+ from pygeos.testing import assert_geometries_equal
27
+ has_pygeos = True
28
+ except ImportError:
29
+ has_pygeos = False
30
+
11
31
  import singlestoredb as s2
12
32
  from . import utils
13
33
  # import traceback
@@ -31,7 +51,6 @@ class TestBasics(unittest.TestCase):
31
51
  def setUp(self):
32
52
  self.conn = s2.connect(database=type(self).dbname)
33
53
  self.cur = self.conn.cursor()
34
- self.driver = self.conn._driver.dbapi.__name__
35
54
 
36
55
  def tearDown(self):
37
56
  try:
@@ -71,7 +90,7 @@ class TestBasics(unittest.TestCase):
71
90
  ('e', 'elephants', 0),
72
91
  ]), out
73
92
 
74
- assert rowcount == 5, rowcount
93
+ assert rowcount in (5, -1), rowcount
75
94
  assert rownumber == 5, rownumber
76
95
  assert lastrowid is None, lastrowid
77
96
  assert len(desc) == 3, desc
@@ -106,7 +125,7 @@ class TestBasics(unittest.TestCase):
106
125
  ('e', 'elephants', 0),
107
126
  ]), out
108
127
 
109
- assert rowcount == 5, rowcount
128
+ assert rowcount in (5, -1), rowcount
110
129
  assert rownumber == 5, rownumber
111
130
  assert lastrowid is None, lastrowid
112
131
  assert len(desc) == 3, desc
@@ -142,7 +161,7 @@ class TestBasics(unittest.TestCase):
142
161
  ('e', 'elephants', 0),
143
162
  ]), out
144
163
 
145
- assert rowcount == 5, rowcount
164
+ assert rowcount in (5, -1), rowcount
146
165
  assert rownumber == 5, rownumber
147
166
  assert lastrowid is None, lastrowid
148
167
  assert len(desc) == 3, desc
@@ -179,7 +198,7 @@ class TestBasics(unittest.TestCase):
179
198
  assert self.cur.rownumber == 5, self.cur.rownumber
180
199
 
181
200
  def test_execute_with_dict_params(self):
182
- self.cur.execute('select * from data where id < :name', dict(name='d'))
201
+ self.cur.execute('select * from data where id < %(name)s', dict(name='d'))
183
202
  out = self.cur.fetchall()
184
203
 
185
204
  desc = self.cur.description
@@ -192,7 +211,7 @@ class TestBasics(unittest.TestCase):
192
211
  ('c', 'cats', 5),
193
212
  ]), out
194
213
 
195
- assert rowcount == 3, rowcount
214
+ assert rowcount in (3, -1), rowcount
196
215
  assert lastrowid is None, lastrowid
197
216
  assert len(desc) == 3, desc
198
217
  assert desc[0].name == 'id', desc[0].name
@@ -202,8 +221,11 @@ class TestBasics(unittest.TestCase):
202
221
  assert desc[2].name == 'value', desc[2].name
203
222
  assert desc[2].type_code == 8, desc[2].type_code
204
223
 
224
+ with self.assertRaises(KeyError):
225
+ self.cur.execute('select * from data where id < %(name)s', dict(foo='d'))
226
+
205
227
  def test_execute_with_positional_params(self):
206
- self.cur.execute('select * from data where id < :1', ['d'])
228
+ self.cur.execute('select * from data where id < %s', ['d'])
207
229
  out = self.cur.fetchall()
208
230
 
209
231
  desc = self.cur.description
@@ -216,7 +238,7 @@ class TestBasics(unittest.TestCase):
216
238
  ('c', 'cats', 5),
217
239
  ]), out
218
240
 
219
- assert rowcount == 3, rowcount
241
+ assert rowcount in (3, -1), rowcount
220
242
  assert lastrowid is None, lastrowid
221
243
  assert len(desc) == 3, desc
222
244
  assert desc[0].name == 'id', desc[0].name
@@ -226,74 +248,67 @@ class TestBasics(unittest.TestCase):
226
248
  assert desc[2].name == 'value', desc[2].name
227
249
  assert desc[2].type_code == 8, desc[2].type_code
228
250
 
251
+ with self.assertRaises(TypeError):
252
+ self.cur.execute(
253
+ 'select * from data where id < %s and id > %s', ['d', 'e', 'f'],
254
+ )
255
+
256
+ with self.assertRaises(TypeError):
257
+ self.cur.execute('select * from data where id < %s and id > %s', ['d'])
258
+
229
259
  def test_execute_with_escaped_positional_substitutions(self):
230
260
  self.cur.execute(
231
- 'select `id`, `time` from alltypes where `time` = :1', ['00:07:00'],
261
+ 'select `id`, `time` from alltypes where `time` = %s', ['00:07:00'],
232
262
  )
233
263
  out = self.cur.fetchall()
234
- if self.driver == 'pyodbc':
235
- assert out[0] == (0, datetime.time(0, 7)), out[0]
236
- else:
237
- assert out[0] == (0, datetime.timedelta(seconds=420)), out[0]
264
+ assert out[0] == (0, datetime.timedelta(seconds=420)), out[0]
238
265
 
239
266
  self.cur.execute('select `id`, `time` from alltypes where `time` = "00:07:00"')
240
267
  out = self.cur.fetchall()
241
- if self.driver == 'pyodbc':
242
- assert out[0] == (0, datetime.time(0, 7)), out[0]
243
- else:
244
- assert out[0] == (0, datetime.timedelta(seconds=420)), out[0]
268
+ assert out[0] == (0, datetime.timedelta(seconds=420)), out[0]
245
269
 
246
- with self.assertRaises(IndexError):
247
- self.cur.execute(
248
- 'select `id`, `time` from alltypes where `id` = :1 '
249
- 'or `time` = "00:07:00"', [0],
250
- )
270
+ # with self.assertRaises(IndexError):
271
+ # self.cur.execute(
272
+ # 'select `id`, `time` from alltypes where `id` = %1s '
273
+ # 'or `time` = "00:07:00"', [0],
274
+ # )
251
275
 
252
276
  self.cur.execute(
253
- 'select `id`, `time` from alltypes where `id` = :1 '
254
- 'or `time` = "00::07::00"', [0],
277
+ 'select `id`, `time` from alltypes where `id` = %s '
278
+ 'or `time` = "00:07:00"', [0],
255
279
  )
256
280
  out = self.cur.fetchall()
257
- if self.driver == 'pyodbc':
258
- assert out[0] == (0, datetime.time(0, 7)), out[0]
259
- else:
260
- assert out[0] == (0, datetime.timedelta(seconds=420)), out[0]
281
+ assert out[0] == (0, datetime.timedelta(seconds=420)), out[0]
261
282
 
262
283
  def test_execute_with_escaped_substitutions(self):
263
284
  self.cur.execute(
264
- 'select `id`, `time` from alltypes where `time` = :time',
285
+ 'select `id`, `time` from alltypes where `time` = %(time)s',
265
286
  dict(time='00:07:00'),
266
287
  )
267
288
  out = self.cur.fetchall()
268
- if self.driver == 'pyodbc':
269
- assert out[0] == (0, datetime.time(0, 7)), out[0]
270
- else:
271
- assert out[0] == (0, datetime.timedelta(seconds=420)), out[0]
289
+ assert out[0] == (0, datetime.timedelta(seconds=420)), out[0]
272
290
 
273
291
  self.cur.execute(
274
- 'select `id`, `time` from alltypes where `time` = :time',
275
- dict(time='00::07::00'),
292
+ 'select `id`, `time` from alltypes where `time` = %(time)s',
293
+ dict(time='00:07:00'),
276
294
  )
277
295
  out = self.cur.fetchall()
278
- assert len(out) == 0, out
296
+ assert len(out) == 1, out
279
297
 
280
298
  with self.assertRaises(KeyError):
281
299
  self.cur.execute(
282
300
  'select `id`, `time`, `char_100` from alltypes '
283
- 'where `time` = :time or `char_100` like "foo:bar"',
284
- dict(time='00:07:00'),
301
+ 'where `time` = %(time)s or `char_100` like "foo:bar"',
302
+ dict(x='00:07:00'),
285
303
  )
286
304
 
287
305
  self.cur.execute(
288
306
  'select `id`, `time`, `char_100` from alltypes '
289
- 'where `time` = :time or `char_100` like "foo::bar"',
307
+ 'where `time` = %(time)s or `char_100` like "foo::bar"',
290
308
  dict(time='00:07:00'),
291
309
  )
292
310
  out = self.cur.fetchall()
293
- if self.driver == 'pyodbc':
294
- assert out[0][:2] == (0, datetime.time(0, 7)), out[0]
295
- else:
296
- assert out[0][:2] == (0, datetime.timedelta(seconds=420)), out[0]
311
+ assert out[0][:2] == (0, datetime.timedelta(seconds=420)), out[0]
297
312
 
298
313
  def test_is_connected(self):
299
314
  assert self.conn.is_connected()
@@ -313,7 +328,7 @@ class TestBasics(unittest.TestCase):
313
328
  def test_executemany(self):
314
329
  # NOTE: Doesn't actually do anything since no rows match
315
330
  self.cur.executemany(
316
- 'delete from data where id > :name',
331
+ 'delete from data where id > %(name)s',
317
332
  [dict(name='z'), dict(name='y')],
318
333
  )
319
334
 
@@ -484,10 +499,147 @@ class TestBasics(unittest.TestCase):
484
499
  self.cur.execute('garbage syntax')
485
500
 
486
501
  exc = cm.exception
487
- if self.driver != 'pyodbc':
488
- assert exc.errno == 1064, exc.errno
502
+ assert exc.errno == 1064, exc.errno
489
503
  assert 'You have an error in your SQL syntax' in exc.errmsg, exc.errmsg
490
504
 
505
+ def test_extended_types(self):
506
+ if not has_numpy or not has_pygeos or not has_shapely:
507
+ self.skipTest('Test requires numpy, pygeos, and shapely')
508
+
509
+ import uuid
510
+
511
+ key = str(uuid.uuid4())
512
+
513
+ # shapely data
514
+ data = [
515
+ (
516
+ 1, 'POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))', 'POINT(1.5 1.5)',
517
+ [0.5, 0.6], datetime.datetime(1950, 1, 2, 12, 13, 14),
518
+ datetime.date(1950, 1, 2), datetime.time(12, 13, 14),
519
+ datetime.timedelta(seconds=123456), key,
520
+ ),
521
+ (
522
+ 2, 'POLYGON((5 1, 6 1, 6 2, 5 2, 5 1))', 'POINT(5.5 1.5)',
523
+ [1.3, 2.5], datetime.datetime(1960, 3, 4, 15, 16, 17),
524
+ datetime.date(1960, 3, 4), datetime.time(15, 16, 17),
525
+ datetime.timedelta(seconds=2), key,
526
+ ),
527
+ (
528
+ 3, 'POLYGON((5 5, 6 5, 6 6, 5 6, 5 5))', 'POINT(5.5 5.5)',
529
+ [10.3, 11.1], datetime.datetime(1970, 6, 7, 18, 19, 20),
530
+ datetime.date(1970, 5, 6), datetime.time(18, 19, 20),
531
+ datetime.timedelta(seconds=-2), key,
532
+ ),
533
+ (
534
+ 4, 'POLYGON((1 5, 2 5, 2 6, 1 6, 1 5))', 'POINT(1.5 5.5)',
535
+ [3.3, 3.4], datetime.datetime(1980, 8, 9, 21, 22, 23),
536
+ datetime.date(1980, 7, 8), datetime.time(21, 22, 23),
537
+ datetime.timedelta(seconds=-123456), key,
538
+ ),
539
+ (
540
+ 5, 'POLYGON((3 3, 4 3, 4 4, 3 4, 3 3))', 'POINT(3.5 3.5)',
541
+ [2.9, 9.5], datetime.datetime(2010, 10, 11, 1, 2, 3),
542
+ datetime.date(2010, 8, 9), datetime.time(1, 2, 3),
543
+ datetime.timedelta(seconds=0), key,
544
+ ),
545
+ ]
546
+
547
+ new_data = []
548
+ for i, row in enumerate(data):
549
+ row = list(row)
550
+ row[1] = shapely.wkt.loads(row[1])
551
+ row[2] = shapely.wkt.loads(row[2])
552
+ if 'http' in self.conn.driver:
553
+ row[3] = ''
554
+ else:
555
+ row[3] = np.array(row[3], dtype='<f4')
556
+ new_data.append(row)
557
+
558
+ self.cur.executemany(
559
+ 'INSERT INTO extended_types '
560
+ '(id, geography, geographypoint, vectors, dt, d, t, td, testkey) '
561
+ 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)', new_data,
562
+ )
563
+
564
+ self.cur.execute(
565
+ 'SELECT * FROM extended_types WHERE testkey = %s ORDER BY id', [key],
566
+ )
567
+
568
+ for data_row, row in zip(new_data, self.cur):
569
+ assert data_row[0] == row[0]
570
+ assert data_row[1].equals_exact(shapely.wkt.loads(row[1]), 1e-4)
571
+ assert data_row[2].equals_exact(shapely.wkt.loads(row[2]), 1e-4)
572
+ if 'http' in self.conn.driver:
573
+ assert row[3] == b''
574
+ else:
575
+ assert (data_row[3] == np.frombuffer(row[3], dtype='<f4')).all()
576
+
577
+ # pygeos data
578
+ data = [
579
+ (
580
+ 6, 'POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))', 'POINT(1.5 1.5)',
581
+ [0.5, 0.6], datetime.datetime(1950, 1, 2, 12, 13, 14),
582
+ datetime.date(1950, 1, 2), datetime.time(12, 13, 14),
583
+ datetime.timedelta(seconds=123456), key,
584
+ ),
585
+ (
586
+ 7, 'POLYGON((5 1, 6 1, 6 2, 5 2, 5 1))', 'POINT(5.5 1.5)',
587
+ [1.3, 2.5], datetime.datetime(1960, 3, 4, 15, 16, 17),
588
+ datetime.date(1960, 3, 4), datetime.time(15, 16, 17),
589
+ datetime.timedelta(seconds=2), key,
590
+ ),
591
+ (
592
+ 8, 'POLYGON((5 5, 6 5, 6 6, 5 6, 5 5))', 'POINT(5.5 5.5)',
593
+ [10.3, 11.1], datetime.datetime(1970, 6, 7, 18, 19, 20),
594
+ datetime.date(1970, 5, 6), datetime.time(18, 19, 20),
595
+ datetime.timedelta(seconds=-2), key,
596
+ ),
597
+ (
598
+ 9, 'POLYGON((1 5, 2 5, 2 6, 1 6, 1 5))', 'POINT(1.5 5.5)',
599
+ [3.3, 3.4], datetime.datetime(1980, 8, 9, 21, 22, 23),
600
+ datetime.date(1980, 7, 8), datetime.time(21, 22, 23),
601
+ datetime.timedelta(seconds=-123456), key,
602
+ ),
603
+ (
604
+ 10, 'POLYGON((3 3, 4 3, 4 4, 3 4, 3 3))', 'POINT(3.5 3.5)',
605
+ [2.9, 9.5], datetime.datetime(2010, 10, 11, 1, 2, 3),
606
+ datetime.date(2010, 8, 9), datetime.time(1, 2, 3),
607
+ datetime.timedelta(seconds=0), key,
608
+ ),
609
+ ]
610
+
611
+ new_data = []
612
+ for i, row in enumerate(data):
613
+ row = list(row)
614
+ row[1] = pygeos.io.from_wkt(row[1])
615
+ row[2] = pygeos.io.from_wkt(row[2])
616
+ if 'http' in self.conn.driver:
617
+ row[3] = ''
618
+ else:
619
+ row[3] = np.array(row[3], dtype='<f4')
620
+ new_data.append(row)
621
+
622
+ self.cur.executemany(
623
+ 'INSERT INTO extended_types '
624
+ '(id, geography, geographypoint, vectors, dt, d, t, td, testkey) '
625
+ 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)', new_data,
626
+ )
627
+
628
+ self.cur.execute(
629
+ 'SELECT * FROM extended_types WHERE id >= 6 and testkey = %s ORDER BY id', [
630
+ key,
631
+ ],
632
+ )
633
+
634
+ for data_row, row in zip(new_data, self.cur):
635
+ assert data_row[0] == row[0]
636
+ assert_geometries_equal(data_row[1], pygeos.io.from_wkt(row[1]))
637
+ assert_geometries_equal(data_row[2], pygeos.io.from_wkt(row[2]))
638
+ if 'http' in self.conn.driver:
639
+ assert row[3] == b''
640
+ else:
641
+ assert (data_row[3] == np.frombuffer(row[3], dtype='<f4')).all()
642
+
491
643
  def test_alltypes(self):
492
644
  self.cur.execute('select * from alltypes where id = 0')
493
645
  names = [x[0] for x in self.cur.description]
@@ -498,30 +650,8 @@ class TestBasics(unittest.TestCase):
498
650
 
499
651
  bits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
500
652
 
501
- if self.driver == 'pyodbc':
502
- odbc_types = {
503
- # int -> bigint
504
- 3: 8, 1: 8, 2: 8, 9: 8,
505
- # float -> double
506
- 4: 5,
507
- # timestamp -> datetime
508
- 7: 12,
509
- # year -> bigint
510
- 13: 8,
511
- # char/binary -> varchar/varbinary
512
- 249: 15, 250: 15, 251: 15, 252: 15, 253: 15, 254: 15, 255: 15,
513
- # newdecimal -> decimal
514
- 246: 0,
515
- # json -> varchar
516
- 245: 15,
517
- # bit -> varchar
518
- 16: 15,
519
- }
520
- else:
521
- odbc_types = {}
522
-
523
653
  def otype(x):
524
- return odbc_types.get(x, x)
654
+ return x
525
655
 
526
656
  assert row['id'] == 0, row['id']
527
657
  assert typ['id'] == otype(3), typ['id']
@@ -577,21 +707,12 @@ class TestBasics(unittest.TestCase):
577
707
  assert row['date'] == datetime.date(8524, 11, 10), row['date']
578
708
  assert typ['date'] == 10, typ['date']
579
709
 
580
- # pyodbc uses time rather than timedelta. In addition, if you try to
581
- # put your own converter in, it changes the type code to 15!!!
582
- if self.driver == 'pyodbc':
583
- assert row['time'] == datetime.time(0, 7, 0), row['time']
584
- else:
585
- assert row['time'] == datetime.timedelta(minutes=7), row['time']
710
+ assert row['time'] == datetime.timedelta(minutes=7), row['time']
586
711
  assert typ['time'] == 11, typ['time']
587
712
 
588
- # Same as above
589
- if self.driver == 'pyodbc':
590
- assert row['time_6'] == datetime.time(0, 7, 0), row['time_6']
591
- else:
592
- assert row['time_6'] == datetime.timedelta(
593
- hours=1, minutes=10, microseconds=2,
594
- ), row['time_6']
713
+ assert row['time_6'] == datetime.timedelta(
714
+ hours=1, minutes=10, microseconds=2,
715
+ ), row['time_6']
595
716
  assert typ['time_6'] == 11, typ['time_6']
596
717
 
597
718
  assert row['datetime'] == datetime.datetime(
@@ -655,11 +776,7 @@ class TestBasics(unittest.TestCase):
655
776
  assert row['tinyblob'] == bytearray([10, 11, 12, 13, 14, 15]), row['tinyblob']
656
777
  assert typ['tinyblob'] == otype(249), typ['tinyblob']
657
778
 
658
- # pyodbc surfaces json as varchar
659
- if self.driver == 'pyodbc':
660
- assert row['json'] == '{"a":10,"b":2.75,"c":"hello world"}', row['json']
661
- else:
662
- assert row['json'] == {'a': 10, 'b': 2.75, 'c': 'hello world'}, row['json']
779
+ assert row['json'] == {'a': 10, 'b': 2.75, 'c': 'hello world'}, row['json']
663
780
  assert typ['json'] == otype(245), typ['json']
664
781
 
665
782
  assert row['enum'] == 'one', row['enum']
@@ -669,11 +786,7 @@ class TestBasics(unittest.TestCase):
669
786
  assert row['set'] in [{'two'}, 'two'], row['set']
670
787
  assert typ['set'] == otype(253), typ['set'] # mysql code: 248
671
788
 
672
- # pyodbc uses the opposite endianness of all other drivers
673
- if self.driver == 'pyodbc':
674
- assert row['bit'] == b'\x80\x00\x00\x00\x00\x00\x00\x00', row['bit']
675
- else:
676
- assert row['bit'] == b'\x00\x00\x00\x00\x00\x00\x00\x80', row['bit']
789
+ assert row['bit'] == b'\x00\x00\x00\x00\x00\x00\x00\x80', row['bit']
677
790
  assert typ['bit'] == otype(16), typ['bit']
678
791
 
679
792
  def test_alltypes_nulls(self):
@@ -684,30 +797,8 @@ class TestBasics(unittest.TestCase):
684
797
  row = dict(zip(names, out))
685
798
  typ = dict(zip(names, types))
686
799
 
687
- if self.driver == 'pyodbc':
688
- odbc_types = {
689
- # int -> bigint
690
- 3: 8, 1: 8, 2: 8, 9: 8,
691
- # float -> double
692
- 4: 5,
693
- # timestamp -> datetime
694
- 7: 12,
695
- # year -> bigint
696
- 13: 8,
697
- # char/binary -> varchar/varbinary
698
- 249: 15, 250: 15, 251: 15, 252: 15, 253: 15, 254: 15, 255: 15,
699
- # newdecimal -> decimal
700
- 246: 0,
701
- # json -> varchar
702
- 245: 15,
703
- # bit -> varchar
704
- 16: 15,
705
- }
706
- else:
707
- odbc_types = {}
708
-
709
800
  def otype(x):
710
- return odbc_types.get(x, x)
801
+ return x
711
802
 
712
803
  assert row['id'] == 1, row['id']
713
804
  assert typ['id'] == otype(3), typ['id']
@@ -832,6 +923,280 @@ class TestBasics(unittest.TestCase):
832
923
  assert row['bit'] is None, row['bit']
833
924
  assert typ['bit'] == otype(16), typ['bit']
834
925
 
926
+ def test_alltypes_mins(self):
927
+ self.cur.execute('select * from alltypes where id = 2')
928
+ names = [x[0] for x in self.cur.description]
929
+ out = self.cur.fetchone()
930
+ row = dict(zip(names, out))
931
+
932
+ expected = dict(
933
+ id=2,
934
+ tinyint=-128,
935
+ unsigned_tinyint=0,
936
+ bool=-128,
937
+ boolean=-128,
938
+ smallint=-32768,
939
+ unsigned_smallint=0,
940
+ mediumint=-8388608,
941
+ unsigned_mediumint=0,
942
+ int24=-8388608,
943
+ unsigned_int24=0,
944
+ int=-2147483648,
945
+ unsigned_int=0,
946
+ integer=-2147483648,
947
+ unsigned_integer=0,
948
+ bigint=-9223372036854775808,
949
+ unsigned_bigint=0,
950
+ float=0,
951
+ double=-1.7976931348623158e308,
952
+ real=-1.7976931348623158e308,
953
+ decimal=decimal.Decimal('-99999999999999.999999'),
954
+ dec=-decimal.Decimal('99999999999999.999999'),
955
+ fixed=decimal.Decimal('-99999999999999.999999'),
956
+ numeric=decimal.Decimal('-99999999999999.999999'),
957
+ date=datetime.date(1000, 1, 1),
958
+ time=-1 * datetime.timedelta(hours=838, minutes=59, seconds=59),
959
+ time_6=-1 * datetime.timedelta(hours=838, minutes=59, seconds=59),
960
+ datetime=datetime.datetime(1000, 1, 1, 0, 0, 0),
961
+ datetime_6=datetime.datetime(1000, 1, 1, 0, 0, 0, 0),
962
+ timestamp=datetime.datetime(1970, 1, 1, 0, 0, 1),
963
+ timestamp_6=datetime.datetime(1970, 1, 1, 0, 0, 1, 0),
964
+ year=1901,
965
+ char_100='',
966
+ binary_100=b'\x00' * 100,
967
+ varchar_200='',
968
+ varbinary_200=b'',
969
+ longtext='',
970
+ mediumtext='',
971
+ text='',
972
+ tinytext='',
973
+ longblob=b'',
974
+ mediumblob=b'',
975
+ blob=b'',
976
+ tinyblob=b'',
977
+ json={},
978
+ enum='one',
979
+ set='two',
980
+ bit=b'\x00\x00\x00\x00\x00\x00\x00\x00',
981
+ )
982
+
983
+ for k, v in sorted(row.items()):
984
+ assert v == expected[k], '{} != {} in key {}'.format(v, expected[k], k)
985
+
986
+ def test_alltypes_maxs(self):
987
+ self.cur.execute('select * from alltypes where id = 3')
988
+ names = [x[0] for x in self.cur.description]
989
+ out = self.cur.fetchone()
990
+ row = dict(zip(names, out))
991
+
992
+ expected = dict(
993
+ id=3,
994
+ tinyint=127,
995
+ unsigned_tinyint=255,
996
+ bool=127,
997
+ boolean=127,
998
+ smallint=32767,
999
+ unsigned_smallint=65535,
1000
+ mediumint=8388607,
1001
+ unsigned_mediumint=16777215,
1002
+ int24=8388607,
1003
+ unsigned_int24=16777215,
1004
+ int=2147483647,
1005
+ unsigned_int=4294967295,
1006
+ integer=2147483647,
1007
+ unsigned_integer=4294967295,
1008
+ bigint=9223372036854775807,
1009
+ unsigned_bigint=18446744073709551615,
1010
+ float=0,
1011
+ double=1.7976931348623158e308,
1012
+ real=1.7976931348623158e308,
1013
+ decimal=decimal.Decimal('99999999999999.999999'),
1014
+ dec=decimal.Decimal('99999999999999.999999'),
1015
+ fixed=decimal.Decimal('99999999999999.999999'),
1016
+ numeric=decimal.Decimal('99999999999999.999999'),
1017
+ date=datetime.date(9999, 12, 31),
1018
+ time=datetime.timedelta(hours=838, minutes=59, seconds=59),
1019
+ time_6=datetime.timedelta(hours=838, minutes=59, seconds=59),
1020
+ datetime=datetime.datetime(9999, 12, 31, 23, 59, 59),
1021
+ datetime_6=datetime.datetime(9999, 12, 31, 23, 59, 59, 999999),
1022
+ timestamp=datetime.datetime(2038, 1, 19, 3, 14, 7),
1023
+ timestamp_6=datetime.datetime(2038, 1, 19, 3, 14, 7, 999999),
1024
+ year=2155,
1025
+ char_100='',
1026
+ binary_100=b'\x00' * 100,
1027
+ varchar_200='',
1028
+ varbinary_200=b'',
1029
+ longtext='',
1030
+ mediumtext='',
1031
+ text='',
1032
+ tinytext='',
1033
+ longblob=b'',
1034
+ mediumblob=b'',
1035
+ blob=b'',
1036
+ tinyblob=b'',
1037
+ json={},
1038
+ enum='one',
1039
+ set='two',
1040
+ bit=b'\xff\xff\xff\xff\xff\xff\xff\xff',
1041
+ )
1042
+
1043
+ for k, v in sorted(row.items()):
1044
+ # TODO: Figure out how to get time zones working
1045
+ if 'timestamp' in k:
1046
+ continue
1047
+ assert v == expected[k], '{} != {} in key {}'.format(v, expected[k], k)
1048
+
1049
+ def test_alltypes_zeros(self):
1050
+ self.cur.execute('select * from alltypes where id = 4')
1051
+ names = [x[0] for x in self.cur.description]
1052
+ out = self.cur.fetchone()
1053
+ row = dict(zip(names, out))
1054
+
1055
+ expected = dict(
1056
+ id=4,
1057
+ tinyint=0,
1058
+ unsigned_tinyint=0,
1059
+ bool=0,
1060
+ boolean=0,
1061
+ smallint=0,
1062
+ unsigned_smallint=0,
1063
+ mediumint=0,
1064
+ unsigned_mediumint=0,
1065
+ int24=0,
1066
+ unsigned_int24=0,
1067
+ int=0,
1068
+ unsigned_int=0,
1069
+ integer=0,
1070
+ unsigned_integer=0,
1071
+ bigint=0,
1072
+ unsigned_bigint=0,
1073
+ float=0,
1074
+ double=0,
1075
+ real=0,
1076
+ decimal=decimal.Decimal('0.0'),
1077
+ dec=decimal.Decimal('0.0'),
1078
+ fixed=decimal.Decimal('0.0'),
1079
+ numeric=decimal.Decimal('0.0'),
1080
+ date=None,
1081
+ time=datetime.timedelta(hours=0, minutes=0, seconds=0),
1082
+ time_6=datetime.timedelta(hours=0, minutes=0, seconds=0, microseconds=0),
1083
+ datetime=None,
1084
+ datetime_6=None,
1085
+ timestamp=None,
1086
+ timestamp_6=None,
1087
+ year=None,
1088
+ char_100='',
1089
+ binary_100=b'\x00' * 100,
1090
+ varchar_200='',
1091
+ varbinary_200=b'',
1092
+ longtext='',
1093
+ mediumtext='',
1094
+ text='',
1095
+ tinytext='',
1096
+ longblob=b'',
1097
+ mediumblob=b'',
1098
+ blob=b'',
1099
+ tinyblob=b'',
1100
+ json={},
1101
+ enum='one',
1102
+ set='two',
1103
+ bit=b'\x00\x00\x00\x00\x00\x00\x00\x00',
1104
+ )
1105
+
1106
+ for k, v in sorted(row.items()):
1107
+ assert v == expected[k], '{} != {} in key {}'.format(v, expected[k], k)
1108
+
1109
+ def _test_MySQLdb(self):
1110
+ try:
1111
+ import json
1112
+ import MySQLdb
1113
+ except (ModuleNotFoundError, ImportError):
1114
+ self.skipTest('MySQLdb is not installed')
1115
+
1116
+ self.cur.execute('select * from alltypes order by id')
1117
+ s2_out = self.cur.fetchall()
1118
+
1119
+ port = self.conn.connection_params['port']
1120
+ if 'http' in self.conn.driver:
1121
+ port = 3306
1122
+
1123
+ args = dict(
1124
+ host=self.conn.connection_params['host'],
1125
+ port=port,
1126
+ user=self.conn.connection_params['user'],
1127
+ password=self.conn.connection_params['password'],
1128
+ database=type(self).dbname,
1129
+ )
1130
+
1131
+ with MySQLdb.connect(**args) as conn:
1132
+ conn.converter[245] = json.loads
1133
+ with conn.cursor() as cur:
1134
+ cur.execute('select * from alltypes order by id')
1135
+ mydb_out = cur.fetchall()
1136
+
1137
+ for a, b in zip(s2_out, mydb_out):
1138
+ assert a == b, (a, b)
1139
+
1140
+ def test_int_string(self):
1141
+ string = 'a' * 48
1142
+ self.cur.execute(f"SELECT 1, '{string}'")
1143
+ self.assertEqual((1, string), self.cur.fetchone())
1144
+
1145
+ def test_double_string(self):
1146
+ string = 'a' * 49
1147
+ self.cur.execute(f"SELECT 1.2 :> DOUBLE, '{string}'")
1148
+ self.assertEqual((1.2, string), self.cur.fetchone())
1149
+
1150
+ def test_year_string(self):
1151
+ string = 'a' * 49
1152
+ self.cur.execute(f"SELECT 1999 :> YEAR, '{string}'")
1153
+ self.assertEqual((1999, string), self.cur.fetchone())
1154
+
1155
+ def test_nan_as_null(self):
1156
+ with self.assertRaises((s2.ProgrammingError, InvalidJSONError)):
1157
+ self.cur.execute('SELECT %s :> DOUBLE AS X', [math.nan])
1158
+
1159
+ with s2.connect(database=type(self).dbname, nan_as_null=True) as conn:
1160
+ with conn.cursor() as cur:
1161
+ cur.execute('SELECT %s :> DOUBLE AS X', [math.nan])
1162
+ self.assertEqual(None, list(cur)[0][0])
1163
+
1164
+ with s2.connect(database=type(self).dbname, nan_as_null=True) as conn:
1165
+ with conn.cursor() as cur:
1166
+ cur.execute('SELECT %s :> DOUBLE AS X', [1.234])
1167
+ self.assertEqual(1.234, list(cur)[0][0])
1168
+
1169
+ def test_inf_as_null(self):
1170
+ with self.assertRaises((s2.ProgrammingError, InvalidJSONError)):
1171
+ self.cur.execute('SELECT %s :> DOUBLE AS X', [math.inf])
1172
+
1173
+ with s2.connect(database=type(self).dbname, inf_as_null=True) as conn:
1174
+ with conn.cursor() as cur:
1175
+ cur.execute('SELECT %s :> DOUBLE AS X', [math.inf])
1176
+ self.assertEqual(None, list(cur)[0][0])
1177
+
1178
+ with s2.connect(database=type(self).dbname, inf_as_null=True) as conn:
1179
+ with conn.cursor() as cur:
1180
+ cur.execute('SELECT %s :> DOUBLE AS X', [1.234])
1181
+ self.assertEqual(1.234, list(cur)[0][0])
1182
+
1183
+ def test_encoding_errors(self):
1184
+ with s2.connect(
1185
+ database=type(self).dbname,
1186
+ encoding_errors='strict',
1187
+ ) as conn:
1188
+ with conn.cursor() as cur:
1189
+ cur.execute('SELECT * FROM badutf8')
1190
+ list(cur)
1191
+
1192
+ with s2.connect(
1193
+ database=type(self).dbname,
1194
+ encoding_errors='backslashreplace',
1195
+ ) as conn:
1196
+ with conn.cursor() as cur:
1197
+ cur.execute('SELECT * FROM badutf8')
1198
+ list(cur)
1199
+
835
1200
 
836
1201
  if __name__ == '__main__':
837
1202
  import nose2