singlestoredb 0.4.0__py3-none-any.whl → 1.0.4__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 (120) hide show
  1. singlestoredb/__init__.py +33 -1
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +5 -1
  4. singlestoredb/config.py +116 -14
  5. singlestoredb/connection.py +483 -516
  6. singlestoredb/converters.py +238 -135
  7. singlestoredb/exceptions.py +30 -2
  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/stage.py +257 -0
  22. singlestoredb/fusion/handlers/utils.py +162 -0
  23. singlestoredb/fusion/handlers/workspace.py +412 -0
  24. singlestoredb/fusion/registry.py +164 -0
  25. singlestoredb/fusion/result.py +399 -0
  26. singlestoredb/http/__init__.py +27 -0
  27. singlestoredb/{http.py → http/connection.py} +555 -154
  28. singlestoredb/management/__init__.py +3 -0
  29. singlestoredb/management/billing_usage.py +148 -0
  30. singlestoredb/management/cluster.py +14 -6
  31. singlestoredb/management/manager.py +100 -38
  32. singlestoredb/management/organization.py +188 -0
  33. singlestoredb/management/region.py +5 -5
  34. singlestoredb/management/utils.py +281 -2
  35. singlestoredb/management/workspace.py +1344 -49
  36. singlestoredb/{clients/pymysqlsv → mysql}/__init__.py +16 -21
  37. singlestoredb/{clients/pymysqlsv → mysql}/_auth.py +39 -8
  38. singlestoredb/{clients/pymysqlsv → mysql}/charset.py +26 -23
  39. singlestoredb/{clients/pymysqlsv/connections.py → mysql/connection.py} +532 -165
  40. singlestoredb/{clients/pymysqlsv → mysql}/constants/CLIENT.py +0 -1
  41. singlestoredb/{clients/pymysqlsv → mysql}/constants/COMMAND.py +0 -1
  42. singlestoredb/{clients/pymysqlsv → mysql}/constants/CR.py +0 -2
  43. singlestoredb/{clients/pymysqlsv → mysql}/constants/ER.py +0 -1
  44. singlestoredb/{clients/pymysqlsv → mysql}/constants/FIELD_TYPE.py +1 -1
  45. singlestoredb/{clients/pymysqlsv → mysql}/constants/FLAG.py +0 -1
  46. singlestoredb/{clients/pymysqlsv → mysql}/constants/SERVER_STATUS.py +0 -1
  47. singlestoredb/mysql/converters.py +271 -0
  48. singlestoredb/{clients/pymysqlsv → mysql}/cursors.py +228 -112
  49. singlestoredb/mysql/err.py +92 -0
  50. singlestoredb/{clients/pymysqlsv → mysql}/optionfile.py +5 -4
  51. singlestoredb/{clients/pymysqlsv → mysql}/protocol.py +49 -20
  52. singlestoredb/mysql/tests/__init__.py +19 -0
  53. singlestoredb/{clients/pymysqlsv → mysql}/tests/base.py +32 -12
  54. singlestoredb/mysql/tests/conftest.py +37 -0
  55. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_DictCursor.py +11 -7
  56. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_SSCursor.py +17 -12
  57. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_basic.py +32 -24
  58. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_connection.py +130 -119
  59. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_converters.py +9 -7
  60. singlestoredb/mysql/tests/test_cursor.py +141 -0
  61. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_err.py +3 -2
  62. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_issues.py +35 -27
  63. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_load_local.py +13 -11
  64. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_nextset.py +7 -3
  65. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_optionfile.py +2 -1
  66. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/__init__.py +1 -1
  67. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  68. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/capabilities.py +19 -17
  69. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/dbapi20.py +31 -22
  70. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +3 -4
  71. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +24 -20
  72. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +4 -4
  73. singlestoredb/{clients/pymysqlsv → mysql}/times.py +3 -4
  74. singlestoredb/pytest.py +283 -0
  75. singlestoredb/tests/empty.sql +0 -0
  76. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  77. singlestoredb/tests/test.sql +210 -0
  78. singlestoredb/tests/test2.sql +1 -0
  79. singlestoredb/tests/test_basics.py +482 -115
  80. singlestoredb/tests/test_config.py +13 -13
  81. singlestoredb/tests/test_connection.py +241 -305
  82. singlestoredb/tests/test_dbapi.py +27 -0
  83. singlestoredb/tests/test_ext_func.py +1193 -0
  84. singlestoredb/tests/test_ext_func_data.py +1101 -0
  85. singlestoredb/tests/test_fusion.py +465 -0
  86. singlestoredb/tests/test_http.py +32 -26
  87. singlestoredb/tests/test_management.py +588 -8
  88. singlestoredb/tests/test_plugin.py +33 -0
  89. singlestoredb/tests/test_results.py +11 -12
  90. singlestoredb/tests/test_udf.py +687 -0
  91. singlestoredb/tests/utils.py +3 -2
  92. singlestoredb/utils/config.py +58 -0
  93. singlestoredb/utils/debug.py +13 -0
  94. singlestoredb/utils/mogrify.py +151 -0
  95. singlestoredb/utils/results.py +4 -1
  96. singlestoredb-1.0.4.dist-info/METADATA +139 -0
  97. singlestoredb-1.0.4.dist-info/RECORD +112 -0
  98. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/WHEEL +1 -1
  99. singlestoredb-1.0.4.dist-info/entry_points.txt +2 -0
  100. singlestoredb/clients/pymysqlsv/converters.py +0 -365
  101. singlestoredb/clients/pymysqlsv/err.py +0 -144
  102. singlestoredb/clients/pymysqlsv/tests/__init__.py +0 -19
  103. singlestoredb/clients/pymysqlsv/tests/test_cursor.py +0 -133
  104. singlestoredb/clients/pymysqlsv/tests/thirdparty/test_MySQLdb/__init__.py +0 -9
  105. singlestoredb/drivers/__init__.py +0 -45
  106. singlestoredb/drivers/base.py +0 -198
  107. singlestoredb/drivers/cymysql.py +0 -38
  108. singlestoredb/drivers/http.py +0 -47
  109. singlestoredb/drivers/mariadb.py +0 -40
  110. singlestoredb/drivers/mysqlconnector.py +0 -49
  111. singlestoredb/drivers/mysqldb.py +0 -60
  112. singlestoredb/drivers/pymysql.py +0 -37
  113. singlestoredb/drivers/pymysqlsv.py +0 -35
  114. singlestoredb/drivers/pyodbc.py +0 -65
  115. singlestoredb-0.4.0.dist-info/METADATA +0 -111
  116. singlestoredb-0.4.0.dist-info/RECORD +0 -86
  117. /singlestoredb/{clients → fusion/handlers}/__init__.py +0 -0
  118. /singlestoredb/{clients/pymysqlsv → mysql}/constants/__init__.py +0 -0
  119. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/LICENSE +0 -0
  120. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.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
@@ -30,9 +30,9 @@ class TestHTTP(unittest.TestCase):
30
30
  def setUp(self):
31
31
  self.conn = self._connect()
32
32
  self.cur = self.conn.cursor()
33
- if self.params['protocol'] not in ['http', 'https']:
33
+ if self.params['driver'] not in ['http', 'https']:
34
34
  self.skipTest('Tests must be run using HTTP connection')
35
- self.driver = self.params['protocol'] or 'http'
35
+ self.driver = self.params['driver'] or 'http'
36
36
 
37
37
  def _connect(self):
38
38
  params = sc.build_params(host=config.get_option('host'))
@@ -42,7 +42,7 @@ class TestHTTP(unittest.TestCase):
42
42
  port=params.get('port'),
43
43
  user=params.get('user'),
44
44
  password=params.get('password'),
45
- protocol=params.get('driver'),
45
+ driver=params.get('driver'),
46
46
  ).items() if v is not None
47
47
  }
48
48
  return http.connect(database=type(self).dbname, **self.params)
@@ -63,56 +63,62 @@ class TestHTTP(unittest.TestCase):
63
63
  pass
64
64
 
65
65
  def test_get_exc_type(self):
66
- exc = http.get_exc_type(0)
66
+ exc = http.connection.get_exc_type(0)
67
67
  assert exc is http.InterfaceError, exc
68
68
 
69
- exc = http.get_exc_type(2012)
69
+ exc = http.connection.get_exc_type(2012)
70
70
  assert exc is http.InterfaceError, exc
71
71
 
72
- exc = http.get_exc_type(1230)
72
+ exc = http.connection.get_exc_type(1230)
73
73
  assert exc is http.DataError, exc
74
74
 
75
- exc = http.get_exc_type(1110)
75
+ exc = http.connection.get_exc_type(1110)
76
76
  assert exc is http.ProgrammingError, exc
77
77
 
78
- exc = http.get_exc_type(1452)
78
+ exc = http.connection.get_exc_type(1452)
79
79
  assert exc is http.IntegrityError, exc
80
80
 
81
- exc = http.get_exc_type(9999)
81
+ exc = http.connection.get_exc_type(9999)
82
82
  assert exc is http.OperationalError, exc
83
83
 
84
- exc = http.get_exc_type(222)
84
+ exc = http.connection.get_exc_type(222)
85
85
  assert exc is http.InternalError, exc
86
86
 
87
87
  def test_identity(self):
88
- out = http.identity(1)
88
+ out = http.connection.identity(1)
89
89
  assert out == 1, out
90
90
 
91
- out = http.identity('hi')
91
+ out = http.connection.identity('hi')
92
92
  assert out == 'hi', out
93
93
 
94
94
  def test_b64decode_converter(self):
95
95
  data = base64.b64encode(b'hi there')
96
96
  assert type(data) is bytes, type(data)
97
97
 
98
- out = http.b64decode_converter(http.identity, None)
98
+ out = http.connection.b64decode_converter(http.connection.identity, None)
99
99
  assert out is None, out
100
100
 
101
- out = http.b64decode_converter(http.identity, data)
101
+ out = http.connection.b64decode_converter(http.connection.identity, data)
102
102
  assert out == b'hi there', out
103
103
 
104
- out = http.b64decode_converter(http.identity, str(data, 'utf8'))
104
+ out = http.connection.b64decode_converter(
105
+ http.connection.identity,
106
+ str(data, 'utf8'),
107
+ )
105
108
  assert out == b'hi there', out
106
109
 
107
110
  def test_executemany(self):
108
- 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
+ )
109
115
 
110
116
  assert self.cur.rownumber == 0, self.cur.rownumber
111
117
 
112
118
  # First set
113
119
  out = self.cur.fetchall()
114
120
 
115
- assert self.cur.rownumber is None, self.cur.rownumber
121
+ assert self.cur.rownumber == 3, self.cur.rownumber
116
122
 
117
123
  desc = self.cur.description
118
124
  rowcount = self.cur.rowcount
@@ -124,7 +130,7 @@ class TestHTTP(unittest.TestCase):
124
130
  ('c', 'cats', 5),
125
131
  ]), out
126
132
 
127
- assert rowcount == 3, rowcount
133
+ assert rowcount == 7, rowcount
128
134
  assert lastrowid is None, lastrowid
129
135
  assert len(desc) == 3, desc
130
136
  assert desc[0][0] == 'id', desc[0][0]
@@ -161,7 +167,7 @@ class TestHTTP(unittest.TestCase):
161
167
  assert desc[2][1] == 8, desc[2][1]
162
168
 
163
169
  out = self.cur.nextset()
164
- assert out is False, out
170
+ assert out is None, out
165
171
 
166
172
  def test_executemany_no_args(self):
167
173
  self.cur.executemany('select * from data where id < "d"')
@@ -190,7 +196,7 @@ class TestHTTP(unittest.TestCase):
190
196
  assert desc[2][1] == 8, desc[2][1]
191
197
 
192
198
  out = self.cur.nextset()
193
- assert out is False, out
199
+ assert out is None, out
194
200
 
195
201
  def test_is_connected(self):
196
202
  assert self.cur.is_connected() is True
@@ -201,18 +207,18 @@ class TestHTTP(unittest.TestCase):
201
207
  self.cur.close()
202
208
  assert self.cur.is_connected() is False
203
209
 
204
- with self.assertRaises(http.InterfaceError):
210
+ with self.assertRaises(http.ProgrammingError):
205
211
  self.cur.execute('select 1')
206
212
 
207
- with self.assertRaises(http.InterfaceError):
213
+ with self.assertRaises(http.ProgrammingError):
208
214
  self.cur.executemany('select 1')
209
215
 
210
- with self.assertRaises(http.InterfaceError):
216
+ with self.assertRaises(http.ProgrammingError):
211
217
  self.cur.callproc('get_animal', ['cats'])
212
218
 
213
- def test_callproc(self):
214
- with self.assertRaises(NotImplementedError):
215
- self.cur.callproc('get_animal', ['cats'])
219
+ # def test_callproc(self):
220
+ # with self.assertRaises(NotImplementedError):
221
+ # self.cur.callproc('get_animal', ['cats'])
216
222
 
217
223
  def test_iter(self):
218
224
  self.cur.execute('select * from data')