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
@@ -0,0 +1,465 @@
1
+ #!/usr/bin/env python
2
+ # type: ignore
3
+ """SingleStoreDB Fusion testing."""
4
+ import os
5
+ import random
6
+ import secrets
7
+ import time
8
+ import unittest
9
+ from typing import Any
10
+ from typing import List
11
+
12
+ import pytest
13
+
14
+ import singlestoredb as s2
15
+ from singlestoredb.tests import utils
16
+
17
+
18
+ class TestFusion(unittest.TestCase):
19
+
20
+ dbname: str = ''
21
+ dbexisted: bool = False
22
+
23
+ @classmethod
24
+ def setUpClass(cls):
25
+ sql_file = os.path.join(os.path.dirname(__file__), 'test.sql')
26
+ cls.dbname, cls.dbexisted = utils.load_sql(sql_file)
27
+
28
+ @classmethod
29
+ def tearDownClass(cls):
30
+ if not cls.dbexisted:
31
+ utils.drop_database(cls.dbname)
32
+
33
+ def setUp(self):
34
+ self.enabled = os.environ.get('SINGLESTOREDB_FUSION_ENABLED')
35
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = '1'
36
+ self.conn = s2.connect(database=type(self).dbname, local_infile=True)
37
+ self.cur = self.conn.cursor()
38
+
39
+ def tearDown(self):
40
+ if self.enabled:
41
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = self.enabled
42
+ else:
43
+ del os.environ['SINGLESTOREDB_FUSION_ENABLED']
44
+
45
+ try:
46
+ if self.cur is not None:
47
+ self.cur.close()
48
+ except Exception:
49
+ # traceback.print_exc()
50
+ pass
51
+
52
+ try:
53
+ if self.conn is not None:
54
+ self.conn.close()
55
+ except Exception:
56
+ # traceback.print_exc()
57
+ pass
58
+
59
+ def test_env_var(self):
60
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = '0'
61
+
62
+ with self.assertRaises(s2.ProgrammingError):
63
+ self.cur.execute('show fusion commands')
64
+
65
+ del os.environ['SINGLESTOREDB_FUSION_ENABLED']
66
+
67
+ with self.assertRaises(s2.ProgrammingError):
68
+ self.cur.execute('show fusion commands')
69
+
70
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = 'yes'
71
+
72
+ self.cur.execute('show fusion commands')
73
+ assert list(self.cur)
74
+
75
+ def test_show_commands(self):
76
+ self.cur.execute('show fusion commands')
77
+ cmds = [x[0] for x in self.cur.fetchall()]
78
+ assert cmds
79
+ assert [x for x in cmds if x.strip().startswith('SHOW FUSION GRAMMAR')], cmds
80
+
81
+ self.cur.execute('show fusion commands like "create%"')
82
+ cmds = [x[0] for x in self.cur.fetchall()]
83
+ assert cmds
84
+ assert [x for x in cmds if x.strip().startswith('CREATE')] == cmds, cmds
85
+
86
+ def test_show_grammar(self):
87
+ self.cur.execute('show fusion grammar for "create workspace"')
88
+ cmds = [x[0] for x in self.cur.fetchall()]
89
+ assert cmds
90
+ assert [x for x in cmds if x.strip().startswith('CREATE WORKSPACE')], cmds
91
+
92
+
93
+ @pytest.mark.management
94
+ class TestManagementAPIFusion(unittest.TestCase):
95
+
96
+ id: str = secrets.token_hex(8)
97
+ dbname: str = ''
98
+ dbexisted: bool = False
99
+ workspace_groups: List[Any] = []
100
+
101
+ @classmethod
102
+ def setUpClass(cls):
103
+ sql_file = os.path.join(os.path.dirname(__file__), 'test.sql')
104
+ cls.dbname, cls.dbexisted = utils.load_sql(sql_file)
105
+ mgr = s2.manage_workspaces()
106
+ us_regions = [x for x in mgr.regions if x.name.startswith('US')]
107
+ non_us_regions = [x for x in mgr.regions if not x.name.startswith('US')]
108
+ wg = mgr.create_workspace_group(
109
+ f'A Fusion Testing {cls.id}',
110
+ region=random.choice(us_regions),
111
+ firewall_ranges=[],
112
+ )
113
+ cls.workspace_groups.append(wg)
114
+ wg = mgr.create_workspace_group(
115
+ f'B Fusion Testing {cls.id}',
116
+ region=random.choice(us_regions),
117
+ firewall_ranges=[],
118
+ )
119
+ cls.workspace_groups.append(wg)
120
+ wg = mgr.create_workspace_group(
121
+ f'C Fusion Testing {cls.id}',
122
+ region=random.choice(non_us_regions),
123
+ firewall_ranges=[],
124
+ )
125
+ cls.workspace_groups.append(wg)
126
+
127
+ @classmethod
128
+ def tearDownClass(cls):
129
+ if not cls.dbexisted:
130
+ utils.drop_database(cls.dbname)
131
+ while cls.workspace_groups:
132
+ cls.workspace_groups.pop().terminate(force=True)
133
+
134
+ def setUp(self):
135
+ self.enabled = os.environ.get('SINGLESTOREDB_FUSION_ENABLED')
136
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = '1'
137
+ self.conn = s2.connect(database=type(self).dbname, local_infile=True)
138
+ self.cur = self.conn.cursor()
139
+
140
+ def tearDown(self):
141
+ if self.enabled:
142
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = self.enabled
143
+ else:
144
+ del os.environ['SINGLESTOREDB_FUSION_ENABLED']
145
+
146
+ try:
147
+ if self.cur is not None:
148
+ self.cur.close()
149
+ except Exception:
150
+ # traceback.print_exc()
151
+ pass
152
+
153
+ try:
154
+ if self.conn is not None:
155
+ self.conn.close()
156
+ except Exception:
157
+ # traceback.print_exc()
158
+ pass
159
+
160
+ def test_show_regions(self):
161
+ self.cur.execute('show regions')
162
+ regs = list(self.cur)
163
+ desc = self.cur.description
164
+
165
+ us_regs = [x for x in regs if x[0].startswith('US')]
166
+
167
+ assert len(desc) == 3
168
+ assert len(regs) > 5
169
+ assert len(us_regs) > 5
170
+
171
+ # LIKE
172
+ self.cur.execute('show regions like "US%"')
173
+ regs = list(self.cur)
174
+ assert regs == us_regs
175
+
176
+ # LIMIT
177
+ self.cur.execute('show regions like "US%" limit 3')
178
+ regs = list(self.cur)
179
+ assert len(regs) == 3
180
+
181
+ # ORDER BY
182
+ self.cur.execute('show regions like "US%" limit 3 order by name')
183
+ regs = list(self.cur)
184
+ assert len(regs) == 3
185
+ assert regs == list(sorted(regs, key=lambda x: x[0]))
186
+
187
+ # Wrong column
188
+ with self.assertRaises(KeyError):
189
+ self.cur.execute('show regions like "US%" limit 3 order by foo')
190
+
191
+ def test_show_workspace_groups(self):
192
+ self.cur.execute('show workspace groups')
193
+ wgs = list(self.cur)
194
+ desc = self.cur.description
195
+
196
+ assert len(desc) == 4
197
+ assert desc[0].name == 'Name'
198
+ assert desc[1].name == 'ID'
199
+ assert desc[2].name == 'Region'
200
+ assert desc[3].name == 'FirewallRanges'
201
+ assert len(wgs) >= 3
202
+
203
+ names = [x[0] for x in wgs]
204
+ assert f'A Fusion Testing {self.id}' in names
205
+ assert f'B Fusion Testing {self.id}' in names
206
+ assert f'C Fusion Testing {self.id}' in names
207
+
208
+ # LIKE clause
209
+ self.cur.execute(f'show workspace groups like "A%sion Testing {self.id}"')
210
+ wgs = list(self.cur)
211
+
212
+ names = [x[0] for x in wgs]
213
+ assert f'A Fusion Testing {self.id}' in names
214
+ assert f'B Fusion Testing {self.id}' not in names
215
+ assert f'C Fusion Testing {self.id}' not in names
216
+
217
+ # LIMIT clause
218
+ self.cur.execute('show workspace groups limit 2')
219
+ wgs = list(self.cur)
220
+ assert len(wgs) == 2
221
+
222
+ # EXTENDED attributes
223
+ self.cur.execute('show workspace groups extended')
224
+ wgs = list(self.cur)
225
+ desc = self.cur.description
226
+
227
+ assert len(desc) == 6
228
+ assert desc[4].name == 'CreatedAt'
229
+ assert desc[5].name == 'TerminatedAt'
230
+
231
+ # ORDER BY
232
+ self.cur.execute(
233
+ f'show workspace groups like "% Fusion Testing {self.id}" order by name desc',
234
+ )
235
+ wgs = list(self.cur)
236
+
237
+ names = [x[0] for x in wgs]
238
+ assert names == [
239
+ f'C Fusion Testing {self.id}',
240
+ f'B Fusion Testing {self.id}',
241
+ f'A Fusion Testing {self.id}',
242
+ ]
243
+
244
+ # All options
245
+ self.cur.execute(
246
+ f'show workspace groups like "% Fusion Testing {self.id}" '
247
+ 'extended order by name desc limit 2',
248
+ )
249
+ wgs = list(self.cur)
250
+ desc = self.cur.description
251
+ names = [x[0] for x in wgs]
252
+
253
+ assert len(desc) == 6
254
+ assert names == [f'C Fusion Testing {self.id}', f'B Fusion Testing {self.id}']
255
+
256
+ def test_show_workspaces(self):
257
+ mgr = s2.manage_workspaces()
258
+ wg = mgr.workspace_groups[f'B Fusion Testing {self.id}']
259
+
260
+ self.cur.execute(
261
+ 'create workspace show-ws-1 in group '
262
+ f'"B Fusion Testing {self.id}" with size S-00',
263
+ )
264
+ self.cur.execute(
265
+ 'create workspace show-ws-2 in group '
266
+ f'"B Fusion Testing {self.id}" with size S-00',
267
+ )
268
+ self.cur.execute(
269
+ 'create workspace show-ws-3 in group '
270
+ f'"B Fusion Testing {self.id}" with size S-00',
271
+ )
272
+
273
+ time.sleep(30)
274
+ iterations = 20
275
+ while True:
276
+ wgs = wg.workspaces
277
+ states = [
278
+ x.state for x in wgs
279
+ if x.name in ('show-ws-1', 'show-ws-2', 'show-ws-3')
280
+ ]
281
+ if len(states) == 3 and states.count('ACTIVE') == 3:
282
+ break
283
+ iterations -= 1
284
+ if not iterations:
285
+ raise RuntimeError('timed out waiting for workspaces to start')
286
+ time.sleep(30)
287
+
288
+ # SHOW
289
+ self.cur.execute(f'show workspaces in group "B Fusion Testing {self.id}"')
290
+ desc = self.cur.description
291
+ out = list(self.cur)
292
+ names = [x[0] for x in out]
293
+ assert len(desc) == 4
294
+ assert [x[0] for x in desc] == ['Name', 'ID', 'Size', 'State']
295
+ assert len(out) >= 3
296
+ assert 'show-ws-1' in names
297
+ assert 'show-ws-2' in names
298
+ assert 'show-ws-3' in names
299
+
300
+ # SHOW ID
301
+ self.cur.execute(f'show workspaces in group id {wg.id}')
302
+ desc = self.cur.description
303
+ out = list(self.cur)
304
+ names = [x[0] for x in out]
305
+ assert len(desc) == 4
306
+ assert [x[0] for x in desc] == ['Name', 'ID', 'Size', 'State']
307
+ assert len(out) >= 3
308
+ assert 'show-ws-1' in names
309
+ assert 'show-ws-2' in names
310
+ assert 'show-ws-3' in names
311
+
312
+ # LIKE clause
313
+ self.cur.execute(
314
+ 'show workspaces in group '
315
+ f'"B Fusion Testing {self.id}" like "%2"',
316
+ )
317
+ out = list(self.cur)
318
+ names = [x[0] for x in out]
319
+ assert len(out) >= 1
320
+ assert [x for x in names if x.endswith('2')]
321
+ assert 'show-ws-1' not in names
322
+ assert 'show-ws-2' in names
323
+ assert 'show-ws-3' not in names
324
+
325
+ # Extended attributes
326
+ self.cur.execute(
327
+ 'show workspaces in group '
328
+ f'"B Fusion Testing {self.id}" extended',
329
+ )
330
+ desc = self.cur.description
331
+ out = list(self.cur)
332
+ assert len(desc) == 7
333
+ assert [x[0] for x in desc] == [
334
+ 'Name', 'ID', 'Size', 'State',
335
+ 'Endpoint', 'CreatedAt', 'TerminatedAt',
336
+ ]
337
+
338
+ # ORDER BY
339
+ self.cur.execute(
340
+ 'show workspaces in group '
341
+ f'"B Fusion Testing {self.id}" order by name desc',
342
+ )
343
+ out = list(self.cur)
344
+ desc = self.cur.description
345
+ assert len(desc) == 4
346
+ names = [x[0] for x in out]
347
+ assert names == ['show-ws-3', 'show-ws-2', 'show-ws-1']
348
+
349
+ # LIMIT clause
350
+ self.cur.execute(
351
+ 'show workspaces in group '
352
+ f'"B Fusion Testing {self.id}" order by name desc limit 2',
353
+ )
354
+ out = list(self.cur)
355
+ desc = self.cur.description
356
+ assert len(desc) == 4
357
+ names = [x[0] for x in out]
358
+ assert names == ['show-ws-3', 'show-ws-2']
359
+
360
+ # All options
361
+ self.cur.execute(
362
+ f'show workspaces in group "B Fusion Testing {self.id}" '
363
+ 'like "show-ws%" extended order by name desc limit 2',
364
+ )
365
+ out = list(self.cur)
366
+ desc = self.cur.description
367
+ assert len(desc) == 7
368
+ names = [x[0] for x in out]
369
+ assert names == ['show-ws-3', 'show-ws-2']
370
+
371
+ def test_create_drop_workspace(self):
372
+ mgr = s2.manage_workspaces()
373
+ wg = mgr.workspace_groups[f'A Fusion Testing {self.id}']
374
+
375
+ self.cur.execute(
376
+ f'create workspace foobar-1 in group "A Fusion Testing {self.id}" '
377
+ 'with size S-00 wait on active',
378
+ )
379
+ foobar_1 = [x for x in wg.workspaces if x.name == 'foobar-1']
380
+ assert len(foobar_1) == 1
381
+
382
+ self.cur.execute(
383
+ f'create workspace foobar-2 in group "A Fusion Testing {self.id}" '
384
+ 'with size S-00 wait on active',
385
+ )
386
+ foobar_2 = [x for x in wg.workspaces if x.name == 'foobar-2']
387
+ assert len(foobar_2) == 1
388
+
389
+ # Drop by name
390
+ self.cur.execute(
391
+ f'drop workspace "foobar-1" in group "A Fusion Testing {self.id}" '
392
+ 'wait on terminated',
393
+ )
394
+ foobar_1 = [x for x in wg.workspaces if x.name == 'foobar-1']
395
+ assert len(foobar_1) == 0
396
+
397
+ # Drop by ID
398
+ foobar_2_id = foobar_2[0].id
399
+ self.cur.execute(
400
+ f'drop workspace id {foobar_2_id} in group '
401
+ f'"A Fusion Testing {self.id}" wait on terminated',
402
+ )
403
+ foobar_2 = [x for x in wg.workspaces if x.name == 'foobar-2']
404
+ assert len(foobar_2) == 0
405
+
406
+ # Drop non-existent by ID
407
+ with self.assertRaises(KeyError):
408
+ self.cur.execute(
409
+ f'drop workspace id {foobar_2_id} '
410
+ f'in group "A Fusion Testing {self.id}"',
411
+ )
412
+
413
+ # Drop non-existent by ID with IF EXISTS
414
+ self.cur.execute(
415
+ f'drop workspace IF EXISTS id {foobar_2_id} '
416
+ f'in group "A Fusion Testing {self.id}"',
417
+ )
418
+
419
+ def test_create_drop_workspace_group(self):
420
+ mgr = s2.manage_workspaces()
421
+
422
+ reg = [x for x in mgr.regions if x.name.startswith('US')][0]
423
+ wg_name = f'Create WG Test {id(self)}'
424
+
425
+ try:
426
+ self.cur.execute(
427
+ f'create workspace group "{wg_name}" '
428
+ f'in region "{reg.name}"',
429
+ )
430
+ wg = [x for x in mgr.workspace_groups if x.name == wg_name]
431
+ assert len(wg) == 1
432
+
433
+ # Drop it by name
434
+ self.cur.execute(
435
+ f'drop workspace group "{wg_name}" '
436
+ 'wait on terminated',
437
+ )
438
+ wg = [x for x in mgr.workspace_groups if x.name == wg_name]
439
+ assert len(wg) == 0
440
+
441
+ # Create it again
442
+ self.cur.execute(
443
+ f'create workspace group "{wg_name}" in region "{reg.name}"',
444
+ )
445
+ wg = [x for x in mgr.workspace_groups if x.name == wg_name]
446
+ assert len(wg) == 1
447
+
448
+ # Drop it by ID
449
+ wg_id = wg[0].id
450
+ self.cur.execute(f'drop workspace group id {wg_id} wait on terminated')
451
+ wg = [x for x in mgr.workspace_groups if x.name == wg_name]
452
+ assert len(wg) == 0
453
+
454
+ # Drop non-existent
455
+ with self.assertRaises(KeyError):
456
+ self.cur.execute(f'drop workspace group id {wg_id}')
457
+
458
+ # Drop non-existent with IF EXISTS
459
+ self.cur.execute(f'drop workspace group if exists id {wg_id}')
460
+
461
+ finally:
462
+ try:
463
+ mgr.workspace_groups[wg_name].terminate(force=True)
464
+ except Exception:
465
+ pass
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
  # type: ignore
3
3
  """SingleStoreDB HTTP connection testing."""
4
- from __future__ import annotations
5
-
6
4
  import base64
7
5
  import os
8
6
  import unittest
@@ -32,9 +30,9 @@ class TestHTTP(unittest.TestCase):
32
30
  def setUp(self):
33
31
  self.conn = self._connect()
34
32
  self.cur = self.conn.cursor()
35
- if self.params['protocol'] not in ['http', 'https']:
33
+ if self.params['driver'] not in ['http', 'https']:
36
34
  self.skipTest('Tests must be run using HTTP connection')
37
- self.driver = self.params['protocol'] or 'http'
35
+ self.driver = self.params['driver'] or 'http'
38
36
 
39
37
  def _connect(self):
40
38
  params = sc.build_params(host=config.get_option('host'))
@@ -44,7 +42,7 @@ class TestHTTP(unittest.TestCase):
44
42
  port=params.get('port'),
45
43
  user=params.get('user'),
46
44
  password=params.get('password'),
47
- protocol=params.get('driver'),
45
+ driver=params.get('driver'),
48
46
  ).items() if v is not None
49
47
  }
50
48
  return http.connect(database=type(self).dbname, **self.params)
@@ -65,56 +63,62 @@ class TestHTTP(unittest.TestCase):
65
63
  pass
66
64
 
67
65
  def test_get_exc_type(self):
68
- exc = http.get_exc_type(0)
66
+ exc = http.connection.get_exc_type(0)
69
67
  assert exc is http.InterfaceError, exc
70
68
 
71
- exc = http.get_exc_type(2012)
69
+ exc = http.connection.get_exc_type(2012)
72
70
  assert exc is http.InterfaceError, exc
73
71
 
74
- exc = http.get_exc_type(1230)
72
+ exc = http.connection.get_exc_type(1230)
75
73
  assert exc is http.DataError, exc
76
74
 
77
- exc = http.get_exc_type(1110)
75
+ exc = http.connection.get_exc_type(1110)
78
76
  assert exc is http.ProgrammingError, exc
79
77
 
80
- exc = http.get_exc_type(1452)
78
+ exc = http.connection.get_exc_type(1452)
81
79
  assert exc is http.IntegrityError, exc
82
80
 
83
- exc = http.get_exc_type(9999)
81
+ exc = http.connection.get_exc_type(9999)
84
82
  assert exc is http.OperationalError, exc
85
83
 
86
- exc = http.get_exc_type(222)
84
+ exc = http.connection.get_exc_type(222)
87
85
  assert exc is http.InternalError, exc
88
86
 
89
87
  def test_identity(self):
90
- out = http.identity(1)
88
+ out = http.connection.identity(1)
91
89
  assert out == 1, out
92
90
 
93
- out = http.identity('hi')
91
+ out = http.connection.identity('hi')
94
92
  assert out == 'hi', out
95
93
 
96
94
  def test_b64decode_converter(self):
97
95
  data = base64.b64encode(b'hi there')
98
96
  assert type(data) is bytes, type(data)
99
97
 
100
- out = http.b64decode_converter(http.identity, None)
98
+ out = http.connection.b64decode_converter(http.connection.identity, None)
101
99
  assert out is None, out
102
100
 
103
- out = http.b64decode_converter(http.identity, data)
101
+ out = http.connection.b64decode_converter(http.connection.identity, data)
104
102
  assert out == b'hi there', out
105
103
 
106
- out = http.b64decode_converter(http.identity, str(data, 'utf8'))
104
+ out = http.connection.b64decode_converter(
105
+ http.connection.identity,
106
+ str(data, 'utf8'),
107
+ )
107
108
  assert out == b'hi there', out
108
109
 
109
110
  def test_executemany(self):
110
- self.cur.executemany('select * from data where id < ?', [['d'], ['e']])
111
+ self.cur.executemany(
112
+ 'select * from data where id < %(id)s',
113
+ [dict(id='d'), dict(id='e')],
114
+ )
111
115
 
112
116
  assert self.cur.rownumber == 0, self.cur.rownumber
113
117
 
114
118
  # First set
115
119
  out = self.cur.fetchall()
116
120
 
117
- assert self.cur.rownumber is None, self.cur.rownumber
121
+ assert self.cur.rownumber == 3, self.cur.rownumber
118
122
 
119
123
  desc = self.cur.description
120
124
  rowcount = self.cur.rowcount
@@ -126,7 +130,7 @@ class TestHTTP(unittest.TestCase):
126
130
  ('c', 'cats', 5),
127
131
  ]), out
128
132
 
129
- assert rowcount == 3, rowcount
133
+ assert rowcount == 7, rowcount
130
134
  assert lastrowid is None, lastrowid
131
135
  assert len(desc) == 3, desc
132
136
  assert desc[0][0] == 'id', desc[0][0]
@@ -163,7 +167,7 @@ class TestHTTP(unittest.TestCase):
163
167
  assert desc[2][1] == 8, desc[2][1]
164
168
 
165
169
  out = self.cur.nextset()
166
- assert out is False, out
170
+ assert out is None, out
167
171
 
168
172
  def test_executemany_no_args(self):
169
173
  self.cur.executemany('select * from data where id < "d"')
@@ -192,7 +196,7 @@ class TestHTTP(unittest.TestCase):
192
196
  assert desc[2][1] == 8, desc[2][1]
193
197
 
194
198
  out = self.cur.nextset()
195
- assert out is False, out
199
+ assert out is None, out
196
200
 
197
201
  def test_is_connected(self):
198
202
  assert self.cur.is_connected() is True
@@ -203,18 +207,18 @@ class TestHTTP(unittest.TestCase):
203
207
  self.cur.close()
204
208
  assert self.cur.is_connected() is False
205
209
 
206
- with self.assertRaises(http.InterfaceError):
210
+ with self.assertRaises(http.ProgrammingError):
207
211
  self.cur.execute('select 1')
208
212
 
209
- with self.assertRaises(http.InterfaceError):
213
+ with self.assertRaises(http.ProgrammingError):
210
214
  self.cur.executemany('select 1')
211
215
 
212
- with self.assertRaises(http.InterfaceError):
216
+ with self.assertRaises(http.ProgrammingError):
213
217
  self.cur.callproc('get_animal', ['cats'])
214
218
 
215
- def test_callproc(self):
216
- with self.assertRaises(NotImplementedError):
217
- self.cur.callproc('get_animal', ['cats'])
219
+ # def test_callproc(self):
220
+ # with self.assertRaises(NotImplementedError):
221
+ # self.cur.callproc('get_animal', ['cats'])
218
222
 
219
223
  def test_iter(self):
220
224
  self.cur.execute('select * from data')