chdb 3.2.0__cp310-cp310-macosx_11_0_arm64.whl → 3.4.0__cp310-cp310-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 +1 -1
- chdb/_chdb.cpython-310-darwin.so +0 -0
- chdb/session/state.py +3 -15
- {chdb-3.2.0.dist-info → chdb-3.4.0.dist-info}/METADATA +84 -11
- {chdb-3.2.0.dist-info → chdb-3.4.0.dist-info}/RECORD +8 -10
- {chdb-3.2.0.dist-info → chdb-3.4.0.dist-info}/WHEEL +1 -1
- chdb/state/test_sqlitelike.py +0 -236
- chdb/tests/test_dbapi.py +0 -83
- {chdb-3.2.0.dist-info → chdb-3.4.0.dist-info}/licenses/LICENSE.txt +0 -0
- {chdb-3.2.0.dist-info → chdb-3.4.0.dist-info}/top_level.txt +0 -0
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', '
|
|
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__))
|
chdb/_chdb.cpython-310-darwin.so
CHANGED
|
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
|
|
55
|
-
self.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
1
|
+
chdb/__init__.py,sha256=pNYyLRqm2s5hG1rPhVLECZXvJQ60EGPt_OC88W90j0w,3762
|
|
2
2
|
chdb/__main__.py,sha256=xNNtDY38d973YM5dlxiIazcqqKhXJSpNb7JflyyrXGE,1185
|
|
3
|
-
chdb/_chdb.cpython-310-darwin.so,sha256=
|
|
3
|
+
chdb/_chdb.cpython-310-darwin.so,sha256=FS-ugnIktqOoMSagUfncA3BKb7OPOOetyBJyZqM6Ak4,383541312
|
|
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=
|
|
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.
|
|
27
|
-
chdb-3.
|
|
28
|
-
chdb-3.
|
|
29
|
-
chdb-3.
|
|
30
|
-
chdb-3.
|
|
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=wyHf6UDzyHyUK-aDYscyyyExpYI7SeEZ9xjyEiU4cnw,109
|
|
27
|
+
chdb-3.4.0.dist-info/top_level.txt,sha256=se0Jj0A2-ijfMW51hIjiuNyDJPqy5xJU1G8a_IEdllI,11
|
|
28
|
+
chdb-3.4.0.dist-info/RECORD,,
|
chdb/state/test_sqlitelike.py
DELETED
|
@@ -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()
|
|
File without changes
|
|
File without changes
|