chdb 3.2.0__cp312-cp312-macosx_11_0_arm64.whl → 3.4.0__cp312-cp312-macosx_11_0_arm64.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 chdb might be problematic. Click here for more details.

chdb/__init__.py CHANGED
@@ -19,7 +19,7 @@ _process_result_format_funs = {
19
19
  # UDF script path will be f"{g_udf_path}/{func_name}.py"
20
20
  g_udf_path = ""
21
21
 
22
- chdb_version = ('3', '2', '0')
22
+ chdb_version = ('3', '4', '0')
23
23
  if sys.version_info[:2] >= (3, 7):
24
24
  # get the path of the current file
25
25
  current_path = os.path.dirname(os.path.abspath(__file__))
Binary file
chdb/session/state.py CHANGED
@@ -1,5 +1,3 @@
1
- import tempfile
2
- import shutil
3
1
  import warnings
4
2
 
5
3
  import chdb
@@ -51,11 +49,9 @@ class Session:
51
49
  )
52
50
  g_session.close()
53
51
  g_session_path = None
54
- if path is None or ":memory:" in path:
55
- self._cleanup = True
56
- self._path = tempfile.mkdtemp()
52
+ if path is None:
53
+ self._path = ":memory:"
57
54
  else:
58
- self._cleanup = False
59
55
  self._path = path
60
56
  if chdb.g_udf_path != "":
61
57
  self._udf_path = chdb.g_udf_path
@@ -84,8 +80,6 @@ class Session:
84
80
  self.close()
85
81
 
86
82
  def close(self):
87
- if self._cleanup:
88
- self.cleanup()
89
83
  if self._conn is not None:
90
84
  self._conn.close()
91
85
  self._conn = None
@@ -95,13 +89,7 @@ class Session:
95
89
 
96
90
  def cleanup(self):
97
91
  try:
98
- if self._conn is not None:
99
- self._conn.close()
100
- self._conn = None
101
- shutil.rmtree(self._path)
102
- global g_session, g_session_path
103
- g_session = None
104
- g_session_path = None
92
+ self.close()
105
93
  except: # noqa
106
94
  pass
107
95
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chdb
3
- Version: 3.2.0
3
+ Version: 3.4.0
4
4
  Summary: chDB is an in-process SQL OLAP Engine powered by ClickHouse
5
5
  Home-page: https://github.com/chdb-io/chdb
6
6
  Author: auxten
@@ -54,11 +54,11 @@ Dynamic: requires-python
54
54
 
55
55
 
56
56
  > chDB is an in-process SQL OLAP Engine powered by ClickHouse [^1]
57
- > For more details: [The birth of chDB](https://auxten.com/the-birth-of-chdb/)
57
+ > For more details: [The birth of chDB](https://auxten.com/the-birth-of-chdb/)
58
58
 
59
59
 
60
60
  ## Features
61
-
61
+
62
62
  * In-process SQL OLAP Engine, powered by ClickHouse
63
63
  * No need to install ClickHouse
64
64
  * Minimized data copy from C++ to Python with [python memoryview](https://docs.python.org/3/c-api/memoryview.html)
@@ -149,7 +149,7 @@ res = chdb.query('select version()', 'Pretty'); print(res)
149
149
  # See more data type format in tests/format_output.py
150
150
  res = chdb.query('select * from file("data.parquet", Parquet)', 'JSON'); print(res)
151
151
  res = chdb.query('select * from file("data.csv", CSV)', 'CSV'); print(res)
152
- print(f"SQL read {res.rows_read()} rows, {res.bytes_read()} bytes, elapsed {res.elapsed()} seconds")
152
+ print(f"SQL read {res.rows_read()} rows, {res.bytes_read()} bytes, storage read {res.storage_rows_read()} rows, {res.storage_bytes_read()} bytes, elapsed {res.elapsed()} seconds")
153
153
  ```
154
154
 
155
155
  ### Pandas dataframe output
@@ -174,6 +174,8 @@ ret_tbl = cdf.query(sql="select * from __tbl1__ t1 join __tbl2__ t2 on t1.a = t2
174
174
  print(ret_tbl)
175
175
  # Query on the DataFrame Table
176
176
  print(ret_tbl.query('select b, sum(a) from __table__ group by b'))
177
+ # Pandas DataFrames are automatically registered as temporary tables in ClickHouse
178
+ chdb.query("SELECT * FROM Python(df1) t1 JOIN Python(df2) t2 ON t1.a = t2.c").show()
177
179
  ```
178
180
  </details>
179
181
 
@@ -321,10 +323,19 @@ df = pd.DataFrame(
321
323
  {
322
324
  "a": [1, 2, 3, 4, 5, 6],
323
325
  "b": ["tom", "jerry", "auxten", "tom", "jerry", "auxten"],
326
+ "dict_col": [
327
+ {'id': 1, 'tags': ['urgent', 'important'], 'metadata': {'created': '2024-01-01'}},
328
+ {'id': 2, 'tags': ['normal'], 'metadata': {'created': '2024-02-01'}},
329
+ {'id': 3, 'name': 'tom'},
330
+ {'id': 4, 'value': '100'},
331
+ {'id': 5, 'value': 101},
332
+ {'id': 6, 'value': 102},
333
+ ],
324
334
  }
325
335
  )
326
336
 
327
337
  chdb.query("SELECT b, sum(a) FROM Python(df) GROUP BY b ORDER BY b").show()
338
+ chdb.query("SELECT dict_col.id FROM Python(df) WHERE dict_col.value='100'").show()
328
339
  ```
329
340
 
330
341
  ### Query on Arrow Table
@@ -336,12 +347,19 @@ arrow_table = pa.table(
336
347
  {
337
348
  "a": [1, 2, 3, 4, 5, 6],
338
349
  "b": ["tom", "jerry", "auxten", "tom", "jerry", "auxten"],
350
+ "dict_col": [
351
+ {'id': 1, 'value': 'tom'},
352
+ {'id': 2, 'value': 'jerry'},
353
+ {'id': 3, 'value': 'auxten'},
354
+ {'id': 4, 'value': 'tom'},
355
+ {'id': 5, 'value': 'jerry'},
356
+ {'id': 6, 'value': 'auxten'},
357
+ ],
339
358
  }
340
359
  )
341
360
 
342
- chdb.query(
343
- "SELECT b, sum(a) FROM Python(arrow_table) GROUP BY b ORDER BY b", "debug"
344
- ).show()
361
+ chdb.query("SELECT b, sum(a) FROM Python(arrow_table) GROUP BY b ORDER BY b").show()
362
+ chdb.query("SELECT dict_col.id FROM Python(arrow_table) WHERE dict_col.value='tom'").show()
345
363
  ```
346
364
 
347
365
  ### Query on chdb.PyReader class instance
@@ -365,24 +383,79 @@ class myReader(chdb.PyReader):
365
383
  def read(self, col_names, count):
366
384
  print("Python func read", col_names, count, self.cursor)
367
385
  if self.cursor >= len(self.data["a"]):
386
+ self.cursor = 0
368
387
  return []
369
388
  block = [self.data[col] for col in col_names]
370
389
  self.cursor += len(block[0])
371
390
  return block
372
391
 
392
+ def get_schema(self):
393
+ return [
394
+ ("a", "int"),
395
+ ("b", "str"),
396
+ ("dict_col", "json")
397
+ ]
398
+
373
399
  reader = myReader(
374
400
  {
375
401
  "a": [1, 2, 3, 4, 5, 6],
376
402
  "b": ["tom", "jerry", "auxten", "tom", "jerry", "auxten"],
403
+ "dict_col": [
404
+ {'id': 1, 'tags': ['urgent', 'important'], 'metadata': {'created': '2024-01-01'}},
405
+ {'id': 2, 'tags': ['normal'], 'metadata': {'created': '2024-02-01'}},
406
+ {'id': 3, 'name': 'tom'},
407
+ {'id': 4, 'value': '100'},
408
+ {'id': 5, 'value': 101},
409
+ {'id': 6, 'value': 102}
410
+ ],
377
411
  }
378
412
  )
379
413
 
380
- chdb.query(
381
- "SELECT b, sum(a) FROM Python(reader) GROUP BY b ORDER BY b"
382
- ).show()
414
+ chdb.query("SELECT b, sum(a) FROM Python(reader) GROUP BY b ORDER BY b").show()
415
+ chdb.query("SELECT dict_col.id FROM Python(reader) WHERE dict_col.value='100'").show()
383
416
  ```
384
417
 
385
- see also: [test_query_py.py](tests/test_query_py.py).
418
+ see also: [test_query_py.py](tests/test_query_py.py) and [test_query_json.py](tests/test_query_json.py).
419
+
420
+ ### JSON Type Inference
421
+
422
+ chDB automatically converts Python dictionary objects to ClickHouse JSON types from these sources:
423
+
424
+ 1. **Pandas DataFrame**
425
+ - Columns with `object` dtype are sampled (default 10,000 rows) to detect JSON structures.
426
+ - Control sampling via SQL settings:
427
+ ```sql
428
+ SET pandas_analyze_sample = 10000 -- Default sampling
429
+ SET pandas_analyze_sample = 0 -- Force String type
430
+ SET pandas_analyze_sample = -1 -- Force JSON type
431
+ ```
432
+ - Columns are converted to `String` if sampling finds non-dictionary values.
433
+
434
+ 2. **Arrow Table**
435
+ - `struct` type columns are automatically mapped to JSON columns.
436
+ - Nested structures preserve type information.
437
+
438
+ 3. **chdb.PyReader**
439
+ - Implement custom schema mapping in `get_schema()`:
440
+ ```python
441
+ def get_schema(self):
442
+ return [
443
+ ("c1", "JSON"), # Explicit JSON mapping
444
+ ("c2", "String")
445
+ ]
446
+ ```
447
+ - Column types declared as "JSON" will bypass auto-detection.
448
+
449
+ When converting Python dictionary objects to JSON columns:
450
+
451
+ 1. **Nested Structures**
452
+ - Recursively process nested dictionaries, lists, tuples and NumPy arrays.
453
+
454
+ 2. **Primitive Types**
455
+ - Automatic type recognition for basic types such as integers, floats, strings, and booleans, and more.
456
+
457
+ 3. **Complex Objects**
458
+ - Non-primitive types will be converted to strings.
386
459
 
387
460
  ### Limitations
388
461
 
@@ -1,6 +1,6 @@
1
- chdb/__init__.py,sha256=yuWj0i3_5-uBRZCyZMBKIiBR1MmjEyAjcuxKTm076jI,3762
1
+ chdb/__init__.py,sha256=pNYyLRqm2s5hG1rPhVLECZXvJQ60EGPt_OC88W90j0w,3762
2
2
  chdb/__main__.py,sha256=xNNtDY38d973YM5dlxiIazcqqKhXJSpNb7JflyyrXGE,1185
3
- chdb/_chdb.cpython-312-darwin.so,sha256=dW8SCyHWtJgofdkqdxyb2x1rSgzMY7gU0Ph2CA2o1U4,356206720
3
+ chdb/_chdb.cpython-312-darwin.so,sha256=mb3p1w-m0THwqNf_PLIz-RMV-i-D0FXu8WPkeJaRVrc,383558832
4
4
  chdb/rwabc.py,sha256=tbiwCrXirfrfx46wCJxS64yvFe6pVWIPGdSuvrAL5Ys,2102
5
5
  chdb/dataframe/__init__.py,sha256=1_mrZZiJwqBTnH_P8_FCbbYXIWWY5sxnaFpe3-tDLF4,680
6
6
  chdb/dataframe/query.py,sha256=ggvE8A5vtabFg9gSTp99S7LCrnIEwbWtb-PtJVT8Ct0,12759
@@ -13,18 +13,16 @@ chdb/dbapi/times.py,sha256=_qXgDaYwsHntvpIKSKXp1rrYIgtq6Z9pLyLnO2XNoL0,360
13
13
  chdb/dbapi/constants/FIELD_TYPE.py,sha256=ytFzgAnGmb9hvdsBlnK68qdZv_a6jYFIXT6VSAb60z8,370
14
14
  chdb/dbapi/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  chdb/session/__init__.py,sha256=fCUROZ5L1-92o2lcASiWJpFu-80-kDoSrNfouLEmLg8,50
16
- chdb/session/state.py,sha256=UtObxVuyNgeqFkTXVHtmOknR90Pe1dEzbOpKFDBYOkg,4845
16
+ chdb/session/state.py,sha256=m7K9zZtoMQTlh-pfmSyJV38pAe6eHNTPtOvlHYrImhA,4436
17
17
  chdb/state/__init__.py,sha256=RVUIWDqDi7gte4Os7Mz1wPXFyFpdHT_p1klJC7QtluI,55
18
18
  chdb/state/sqlitelike.py,sha256=v0xh9jWirHzhDVq26C2213LxfaDbRulSAhSHaTiZ24c,12283
19
- chdb/state/test_sqlitelike.py,sha256=8Y7Up2Glkl6PpSgkMF_Rgz_3oGgx-6aLcyxaw2bzQR8,7586
20
- chdb/tests/test_dbapi.py,sha256=Z5KloM0ujhVFaLOES6V6slLIRWbTyg0lPyggl3m2ZqY,2765
21
19
  chdb/udf/__init__.py,sha256=qSMaPEre7w1pYz8uJ-iZtuu8wYOUNRcI_8UNuaOymGE,80
22
20
  chdb/udf/udf.py,sha256=z0A1RmyZrx55bykpvvS-LpVt1lMrQOexjvU5zxCdCSA,3935
23
21
  chdb/utils/__init__.py,sha256=tXRcwBRGW2YQNBZWV4Mitw5QlCu_qlSRCjllw15XHbs,171
24
22
  chdb/utils/trace.py,sha256=W-pvDoKlnzq6H_7FiWjr5_teN40UNE4E5--zbUrjOIc,2511
25
23
  chdb/utils/types.py,sha256=MGLFIjoDvu7Uc2Wy8EDY60jjue66HmMPxbhrujjrZxQ,7530
26
- chdb-3.2.0.dist-info/licenses/LICENSE.txt,sha256=isYVtNCO5910aj6e9bJJ6kQceivkLqsMlFSNYwzGGKI,11366
27
- chdb-3.2.0.dist-info/METADATA,sha256=n8no2boSiPOzbj8PtIT3nIQUtYGXQ1TEgq4QSOFzI4U,21585
28
- chdb-3.2.0.dist-info/WHEEL,sha256=IR6S_K8Km0Ji5Zl8vjPSo_uH9-UA1bb9Jsoca0AUJ14,109
29
- chdb-3.2.0.dist-info/top_level.txt,sha256=se0Jj0A2-ijfMW51hIjiuNyDJPqy5xJU1G8a_IEdllI,11
30
- chdb-3.2.0.dist-info/RECORD,,
24
+ chdb-3.4.0.dist-info/licenses/LICENSE.txt,sha256=isYVtNCO5910aj6e9bJJ6kQceivkLqsMlFSNYwzGGKI,11366
25
+ chdb-3.4.0.dist-info/METADATA,sha256=pwAsyrEzxFDBifcZoNLzRQHB7bCXd2B8y-31HX3APPE,24690
26
+ chdb-3.4.0.dist-info/WHEEL,sha256=CltXN3lQvXbHxKDtiDwW0RNzF8s2WyBuPbOAX_ZeQlA,109
27
+ chdb-3.4.0.dist-info/top_level.txt,sha256=se0Jj0A2-ijfMW51hIjiuNyDJPqy5xJU1G8a_IEdllI,11
28
+ chdb-3.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp312-cp312-macosx_11_0_arm64
5
5
 
@@ -1,236 +0,0 @@
1
- import unittest
2
- from datetime import datetime, date
3
- from chdb.state.sqlitelike import connect
4
-
5
-
6
- class TestCursor(unittest.TestCase):
7
- def setUp(self):
8
- self.conn = connect(":memory:")
9
- self.cursor = self.conn.cursor()
10
-
11
- def tearDown(self):
12
- self.cursor.close()
13
- self.conn.close()
14
-
15
- def test_basic_types(self):
16
- # Test basic types including NULL values
17
- self.cursor.execute(
18
- """
19
- SELECT
20
- 42 as int_val,
21
- 3.14 as float_val,
22
- 'hello' as str_val,
23
- true as bool_val,
24
- NULL as null_val
25
- """
26
- )
27
- row = self.cursor.fetchone()
28
- self.assertEqual(row, (42, 3.14, "hello", True, None))
29
-
30
- def test_date_time_types(self):
31
- # Test date and datetime types with various formats
32
- self.cursor.execute(
33
- """
34
- SELECT
35
- toDateTime('2024-03-20 15:30:00') as datetime_str,
36
- toDateTime64('2024-03-20 15:30:00.123456', 6) as datetime64_str,
37
- toDate('2024-03-20') as date_str,
38
- toDateTime(1710945000) as datetime_ts, -- 2024-03-20 15:30:00
39
- toDate(19437) as date_ts -- 2024-03-20 (days since 1970-01-01)
40
- """
41
- )
42
- row = self.cursor.fetchone()
43
- self.assertEqual(len(row), 5)
44
-
45
- # Test datetime string format
46
- self.assertIsInstance(row[0], datetime)
47
- self.assertEqual(row[0].strftime("%Y-%m-%d %H:%M:%S"), "2024-03-20 15:30:00")
48
-
49
- # Test datetime64 with microseconds
50
- self.assertIsInstance(row[1], datetime)
51
- self.assertEqual(
52
- row[1].strftime("%Y-%m-%d %H:%M:%S.%f"), "2024-03-20 15:30:00.123456"
53
- )
54
-
55
- # Test date string format
56
- self.assertIsInstance(row[2], date)
57
- self.assertEqual(row[2].isoformat(), "2024-03-20")
58
-
59
- # Test datetime from timestamp
60
- self.assertIsInstance(row[3], datetime)
61
- self.assertEqual(row[3].strftime("%Y-%m-%d %H:%M:%S"), "2024-03-20 15:30:00")
62
-
63
- # Test date from timestamp
64
- self.assertIsInstance(row[4], date)
65
- self.assertEqual(row[4].isoformat(), "2024-03-20")
66
-
67
- def test_array_types(self):
68
- # Test array types (should be converted to string)
69
- self.cursor.execute(
70
- """
71
- SELECT
72
- [1, 2, 3] as int_array,
73
- ['a', 'b', 'c'] as str_array
74
- """
75
- )
76
- row = self.cursor.fetchone()
77
- self.assertIsInstance(row[0], str)
78
- self.assertIsInstance(row[1], str)
79
-
80
- def test_complex_types(self):
81
- # Test more complex ClickHouse types
82
- self.cursor.execute(
83
- """
84
- SELECT
85
- toDecimal64(123.45, 2) as decimal_val,
86
- toFixedString('test', 10) as fixed_str_val,
87
- tuple(1, 'a') as tuple_val,
88
- map('key', 'value') as map_val
89
- """
90
- )
91
- row = self.cursor.fetchone()
92
- # All complex types should be converted to strings
93
- for val in row:
94
- self.assertIsInstance(val, (str, type(None)))
95
-
96
- def test_fetch_methods(self):
97
- # Test different fetch methods
98
- self.cursor.execute(
99
- """
100
- SELECT number
101
- FROM system.numbers
102
- LIMIT 5
103
- """
104
- )
105
-
106
- # Test fetchone
107
- self.assertEqual(self.cursor.fetchone(), (0,))
108
-
109
- # Test fetchmany
110
- self.assertEqual(self.cursor.fetchmany(2), ((1,), (2,)))
111
-
112
- # Test fetchall
113
- self.assertEqual(self.cursor.fetchall(), ((3,), (4,)))
114
-
115
- # Test fetchone after end
116
- self.assertIsNone(self.cursor.fetchone())
117
-
118
- def test_empty_result(self):
119
- # Test empty result handling
120
- self.cursor.execute("SELECT 1 WHERE 1=0")
121
- self.assertIsNone(self.cursor.fetchone())
122
- self.assertEqual(self.cursor.fetchall(), ())
123
-
124
- def test_iterator(self):
125
- # Test cursor as iterator
126
- self.cursor.execute(
127
- """
128
- SELECT number
129
- FROM system.numbers
130
- LIMIT 3
131
- """
132
- )
133
- rows = [row for row in self.cursor]
134
- self.assertEqual(rows, [(0,), (1,), (2,)])
135
-
136
- def test_error_handling(self):
137
- # Test invalid SQL
138
- with self.assertRaises(Exception):
139
- self.cursor.execute("SELECT invalid_column")
140
-
141
- def test_large_result(self):
142
- # Test handling of larger result sets
143
- self.cursor.execute(
144
- """
145
- SELECT
146
- number,
147
- toString(number) as str_val,
148
- toDateTime('2024-03-20 15:30:00') + interval number second as time_val
149
- FROM system.numbers
150
- LIMIT 1000
151
- """
152
- )
153
- rows = self.cursor.fetchall()
154
- self.assertEqual(len(rows), 1000)
155
- self.assertEqual(rows[0], (0, "0", datetime(2024, 3, 20, 15, 30, 0)))
156
- self.assertEqual(rows[-1], (999, "999", datetime(2024, 3, 20, 15, 46, 39)))
157
-
158
- def test_column_names(self):
159
- # Test that column names are stored and accessible
160
- self.cursor.execute(
161
- """
162
- SELECT
163
- 42 as int_col,
164
- 'hello' as str_col,
165
- now() as time_col
166
- """
167
- )
168
-
169
- # Test the column names method
170
- self.assertEqual(self.cursor.column_names(), ["int_col", "str_col", "time_col"])
171
-
172
- # Test the column types method
173
- self.assertEqual(len(self.cursor.column_types()), 3)
174
-
175
- # Test the description property (DB-API 2.0)
176
- description = self.cursor.description
177
- self.assertEqual(len(description), 3)
178
- self.assertEqual(description[0][0], "int_col") # First column name
179
- self.assertEqual(description[1][0], "str_col") # Second column name
180
-
181
- # Test fetchone
182
- row = self.cursor.fetchone()
183
- self.assertEqual(len(row), 3)
184
-
185
- # Test that all data was properly converted
186
- self.assertIsInstance(row[0], int)
187
- self.assertIsInstance(row[1], str)
188
- self.assertIsInstance(
189
- row[2], (str, datetime)
190
- ) # May be str or datetime depending on conversion
191
-
192
- def test_uuid_type(self):
193
- # Test UUID type handling
194
- self.cursor.execute(
195
- "SELECT '6bbd51ac-b0cc-43a2-8cb2-eab06ff7de7b'::UUID as uuid_val"
196
- )
197
- row = self.cursor.fetchone()
198
- self.assertEqual(row[0], "6bbd51ac-b0cc-43a2-8cb2-eab06ff7de7b")
199
-
200
- # Test UUID in a more complex query
201
- self.cursor.execute(
202
- """
203
- SELECT
204
- '6bbd51ac-b0cc-43a2-8cb2-eab06ff7de7b'::UUID as uuid_val1,
205
- NULL::UUID as uuid_val2
206
- """
207
- )
208
- row = self.cursor.fetchone()
209
- self.assertEqual(row[0], "6bbd51ac-b0cc-43a2-8cb2-eab06ff7de7b")
210
- self.assertIsNone(row[1])
211
-
212
- def test_null_handling(self):
213
- # Test NULL handling
214
- self.cursor.execute("SELECT NULL")
215
- row = self.cursor.fetchone()
216
- self.assertEqual(row, (None,))
217
-
218
- # Test NULL with different types
219
- self.cursor.execute(
220
- """
221
- SELECT
222
- NULL::Int32 as null_int,
223
- NULL::String as null_str,
224
- NULL::UUID as null_uuid,
225
- NULL::DateTime as null_datetime,
226
- NULL::Date as null_date
227
- """
228
- )
229
- row = self.cursor.fetchone()
230
- self.assertEqual(len(row), 5)
231
- for val in row:
232
- self.assertIsNone(val)
233
-
234
-
235
- if __name__ == "__main__":
236
- unittest.main()
chdb/tests/test_dbapi.py DELETED
@@ -1,83 +0,0 @@
1
- import unittest
2
- from chdb.dbapi import connect
3
-
4
-
5
- class TestDBAPI(unittest.TestCase):
6
- def setUp(self):
7
- self.conn = connect()
8
- self.cur = self.conn.cursor()
9
-
10
- def tearDown(self):
11
- self.cur.close()
12
- self.conn.close()
13
-
14
- def test_select_version(self):
15
- """Test simple version query"""
16
- self.cur.execute("select version()") # ClickHouse version
17
- row = self.cur.fetchone()
18
- self.assertIsNotNone(row)
19
- self.assertEqual(len(row), 1)
20
- self.assertIsInstance(row[0], str)
21
-
22
- def test_description(self):
23
- """Test cursor description"""
24
- # Test with multiple columns of different types
25
- self.cur.execute(
26
- """
27
- SELECT
28
- 1 as int_col,
29
- 'test' as str_col,
30
- now() as time_col,
31
- NULL as null_col,
32
- '6bbd51ac-b0cc-43a2-8cb2-eab06ff7de7b'::UUID as uuid_col
33
- """
34
- )
35
-
36
- # Check description format
37
- self.assertIsNotNone(self.cur.description)
38
- self.assertEqual(len(self.cur.description), 5)
39
-
40
- # Check each column's metadata
41
- int_col = self.cur.description[0]
42
- str_col = self.cur.description[1]
43
- time_col = self.cur.description[2]
44
- null_col = self.cur.description[3]
45
- uuid_col = self.cur.description[4]
46
-
47
- # Check column names
48
- self.assertEqual(int_col[0], "int_col")
49
- self.assertEqual(str_col[0], "str_col")
50
- self.assertEqual(time_col[0], "time_col")
51
- self.assertEqual(null_col[0], "null_col")
52
- self.assertEqual(uuid_col[0], "uuid_col")
53
-
54
- # Check that type info is present
55
- self.assertTrue(int_col[1].startswith("Int") or int_col[1].startswith("UInt"))
56
- self.assertTrue(str_col[1] in ("String", "FixedString"))
57
- self.assertTrue(time_col[1].startswith("DateTime"))
58
- self.assertTrue(uuid_col[1] == "UUID")
59
-
60
- # Check that other fields are None as per DB-API 2.0
61
- for col in self.cur.description:
62
- self.assertEqual(len(col), 7) # DB-API 2.0 specifies 7 fields
63
- self.assertTrue(
64
- all(x is None for x in col[2:])
65
- ) # All fields after name and type should be None
66
-
67
- def test_rowcount(self):
68
- """Test rowcount attribute"""
69
- # Test with empty result
70
- self.cur.execute("SELECT 1 WHERE 1=0")
71
- self.assertEqual(self.cur.rowcount, 0)
72
-
73
- # Test with single row
74
- self.cur.execute("SELECT 1")
75
- self.assertEqual(self.cur.rowcount, 1)
76
-
77
- # Test with multiple rows
78
- self.cur.execute("SELECT number FROM system.numbers LIMIT 10")
79
- self.assertEqual(self.cur.rowcount, 10)
80
-
81
-
82
- if __name__ == "__main__":
83
- unittest.main()