chdb 3.6.0__cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.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 +134 -0
- chdb/__main__.py +38 -0
- chdb/_chdb.abi3.so +0 -0
- chdb/dataframe/__init__.py +19 -0
- chdb/dataframe/query.py +356 -0
- chdb/dbapi/__init__.py +79 -0
- chdb/dbapi/connections.py +100 -0
- chdb/dbapi/constants/FIELD_TYPE.py +31 -0
- chdb/dbapi/constants/__init__.py +0 -0
- chdb/dbapi/converters.py +293 -0
- chdb/dbapi/cursors.py +351 -0
- chdb/dbapi/err.py +61 -0
- chdb/dbapi/times.py +20 -0
- chdb/libpybind11nonlimitedapi_chdb_3.10.so +0 -0
- chdb/libpybind11nonlimitedapi_chdb_3.11.so +0 -0
- chdb/libpybind11nonlimitedapi_chdb_3.12.so +0 -0
- chdb/libpybind11nonlimitedapi_chdb_3.13.so +0 -0
- chdb/libpybind11nonlimitedapi_chdb_3.8.so +0 -0
- chdb/libpybind11nonlimitedapi_chdb_3.9.so +0 -0
- chdb/libpybind11nonlimitedapi_stubs.so +0 -0
- chdb/rwabc.py +65 -0
- chdb/session/__init__.py +3 -0
- chdb/session/state.py +124 -0
- chdb/state/__init__.py +3 -0
- chdb/state/sqlitelike.py +505 -0
- chdb/udf/__init__.py +3 -0
- chdb/udf/udf.py +106 -0
- chdb/utils/__init__.py +9 -0
- chdb/utils/trace.py +74 -0
- chdb/utils/types.py +234 -0
- chdb-3.6.0.dist-info/LICENSE.txt +203 -0
- chdb-3.6.0.dist-info/METADATA +554 -0
- chdb-3.6.0.dist-info/RECORD +36 -0
- chdb-3.6.0.dist-info/WHEEL +6 -0
- chdb-3.6.0.dist-info/top_level.txt +2 -0
- chdb.libs/libpybind11nonlimitedapi_stubs-18c482a6.so +0 -0
chdb/__init__.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChdbError(Exception):
|
|
7
|
+
"""Base class for exceptions in this module."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_arrow_format = set({"dataframe", "arrowtable"})
|
|
11
|
+
_process_result_format_funs = {
|
|
12
|
+
"dataframe": lambda x: to_df(x),
|
|
13
|
+
"arrowtable": lambda x: to_arrowTable(x),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# If any UDF is defined, the path of the UDF will be set to this variable
|
|
17
|
+
# and the path will be deleted when the process exits
|
|
18
|
+
# UDF config path will be f"{g_udf_path}/udf_config.xml"
|
|
19
|
+
# UDF script path will be f"{g_udf_path}/{func_name}.py"
|
|
20
|
+
g_udf_path = ""
|
|
21
|
+
|
|
22
|
+
chdb_version = ('3', '6', '0')
|
|
23
|
+
if sys.version_info[:2] >= (3, 7):
|
|
24
|
+
# get the path of the current file
|
|
25
|
+
current_path = os.path.dirname(os.path.abspath(__file__))
|
|
26
|
+
# change the current working directory to the path of the current file
|
|
27
|
+
# and import _chdb then change the working directory back
|
|
28
|
+
cwd = os.getcwd()
|
|
29
|
+
os.chdir(current_path)
|
|
30
|
+
from . import _chdb # noqa
|
|
31
|
+
|
|
32
|
+
os.chdir(cwd)
|
|
33
|
+
conn = _chdb.connect()
|
|
34
|
+
engine_version = str(conn.query("SELECT version();", "CSV").bytes())[3:-4]
|
|
35
|
+
conn.close()
|
|
36
|
+
else:
|
|
37
|
+
raise NotImplementedError("Python 3.6 or lower version is not supported")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
# Change here if project is renamed and does not equal the package name
|
|
41
|
+
dist_name = __name__
|
|
42
|
+
__version__ = ".".join(map(str, chdb_version))
|
|
43
|
+
except: # noqa
|
|
44
|
+
__version__ = "unknown"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# return pyarrow table
|
|
48
|
+
def to_arrowTable(res):
|
|
49
|
+
"""convert res to arrow table"""
|
|
50
|
+
# try import pyarrow and pandas, if failed, raise ImportError with suggestion
|
|
51
|
+
try:
|
|
52
|
+
import pyarrow as pa # noqa
|
|
53
|
+
import pandas as pd # noqa
|
|
54
|
+
except ImportError as e:
|
|
55
|
+
print(f"ImportError: {e}")
|
|
56
|
+
print('Please install pyarrow and pandas via "pip install pyarrow pandas"')
|
|
57
|
+
raise ImportError("Failed to import pyarrow or pandas") from None
|
|
58
|
+
if len(res) == 0:
|
|
59
|
+
return pa.Table.from_batches([], schema=pa.schema([]))
|
|
60
|
+
return pa.RecordBatchFileReader(res.bytes()).read_all()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# return pandas dataframe
|
|
64
|
+
def to_df(r):
|
|
65
|
+
"""convert arrow table to Dataframe"""
|
|
66
|
+
t = to_arrowTable(r)
|
|
67
|
+
return t.to_pandas(use_threads=True)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# global connection lock, for multi-threading use of legacy chdb.query()
|
|
71
|
+
g_conn_lock = threading.Lock()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# wrap _chdb functions
|
|
75
|
+
def query(sql, output_format="CSV", path="", udf_path=""):
|
|
76
|
+
global g_udf_path
|
|
77
|
+
if udf_path != "":
|
|
78
|
+
g_udf_path = udf_path
|
|
79
|
+
conn_str = ""
|
|
80
|
+
if path == "":
|
|
81
|
+
conn_str = ":memory:"
|
|
82
|
+
else:
|
|
83
|
+
conn_str = f"{path}"
|
|
84
|
+
if g_udf_path != "":
|
|
85
|
+
if "?" in conn_str:
|
|
86
|
+
conn_str = f"{conn_str}&udf_path={g_udf_path}"
|
|
87
|
+
else:
|
|
88
|
+
conn_str = f"{conn_str}?udf_path={g_udf_path}"
|
|
89
|
+
if output_format == "Debug":
|
|
90
|
+
output_format = "CSV"
|
|
91
|
+
if "?" in conn_str:
|
|
92
|
+
conn_str = f"{conn_str}&verbose&log-level=test"
|
|
93
|
+
else:
|
|
94
|
+
conn_str = f"{conn_str}?verbose&log-level=test"
|
|
95
|
+
|
|
96
|
+
lower_output_format = output_format.lower()
|
|
97
|
+
result_func = _process_result_format_funs.get(lower_output_format, lambda x: x)
|
|
98
|
+
if lower_output_format in _arrow_format:
|
|
99
|
+
output_format = "Arrow"
|
|
100
|
+
|
|
101
|
+
with g_conn_lock:
|
|
102
|
+
conn = _chdb.connect(conn_str)
|
|
103
|
+
res = conn.query(sql, output_format)
|
|
104
|
+
if res.has_error():
|
|
105
|
+
conn.close()
|
|
106
|
+
raise ChdbError(res.error_message())
|
|
107
|
+
conn.close()
|
|
108
|
+
return result_func(res)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# alias for query
|
|
112
|
+
sql = query
|
|
113
|
+
|
|
114
|
+
PyReader = _chdb.PyReader
|
|
115
|
+
|
|
116
|
+
from . import dbapi, session, udf, utils # noqa: E402
|
|
117
|
+
from .state import connect # noqa: E402
|
|
118
|
+
|
|
119
|
+
__all__ = [
|
|
120
|
+
"_chdb",
|
|
121
|
+
"PyReader",
|
|
122
|
+
"ChdbError",
|
|
123
|
+
"query",
|
|
124
|
+
"sql",
|
|
125
|
+
"chdb_version",
|
|
126
|
+
"engine_version",
|
|
127
|
+
"to_df",
|
|
128
|
+
"to_arrowTable",
|
|
129
|
+
"dbapi",
|
|
130
|
+
"session",
|
|
131
|
+
"udf",
|
|
132
|
+
"utils",
|
|
133
|
+
"connect",
|
|
134
|
+
]
|
chdb/__main__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from .__init__ import query
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
prog = 'python -m chdb'
|
|
7
|
+
custom_usage = "%(prog)s [-h] \"SELECT 1\" [format]"
|
|
8
|
+
description = ('''A simple command line interface for chdb
|
|
9
|
+
to run SQL and output in specified format''')
|
|
10
|
+
parser = argparse.ArgumentParser(prog=prog,
|
|
11
|
+
usage=custom_usage,
|
|
12
|
+
description=description)
|
|
13
|
+
parser.add_argument('sql', nargs=1,
|
|
14
|
+
type=str,
|
|
15
|
+
help='sql, e.g: select 1112222222,555')
|
|
16
|
+
parser.add_argument('format', nargs='?',
|
|
17
|
+
type=str,
|
|
18
|
+
help='''sql result output format,
|
|
19
|
+
e.g: CSV, Dataframe, JSON etc,
|
|
20
|
+
more format checkout on
|
|
21
|
+
https://clickhouse.com/docs/en/interfaces/formats''',
|
|
22
|
+
default="CSV")
|
|
23
|
+
options = parser.parse_args()
|
|
24
|
+
sql = options.sql[0]
|
|
25
|
+
output_format = options.format
|
|
26
|
+
res = query(sql, output_format)
|
|
27
|
+
try:
|
|
28
|
+
if output_format.lower() in ("dataframe", "arrowtable"):
|
|
29
|
+
temp = res
|
|
30
|
+
else:
|
|
31
|
+
temp = res.data()
|
|
32
|
+
print(temp, end="")
|
|
33
|
+
except UnicodeDecodeError:
|
|
34
|
+
print(repr(res.bytes()))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == '__main__':
|
|
38
|
+
main()
|
chdb/_chdb.abi3.so
ADDED
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# try import pyarrow and pandas, if failed, raise ImportError with suggestion
|
|
2
|
+
try:
|
|
3
|
+
import pyarrow as pa # noqa
|
|
4
|
+
import pandas as pd # noqa
|
|
5
|
+
except ImportError as e:
|
|
6
|
+
print(f'ImportError: {e}')
|
|
7
|
+
print('Please install pyarrow and pandas via "pip install pyarrow pandas"')
|
|
8
|
+
raise ImportError('Failed to import pyarrow or pandas') from None
|
|
9
|
+
|
|
10
|
+
# check if pandas version >= 2.0.0
|
|
11
|
+
if pd.__version__[0] < '2':
|
|
12
|
+
print('Please upgrade pandas to version 2.0.0 or higher to have better performance')
|
|
13
|
+
|
|
14
|
+
from .query import Table, pandas_read_parquet # noqa: C0413
|
|
15
|
+
|
|
16
|
+
query = Table.queryStatic
|
|
17
|
+
sql = Table.queryStatic
|
|
18
|
+
|
|
19
|
+
__all__ = ["Table", "query", "sql", "pandas_read_parquet"]
|
chdb/dataframe/query.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
import re
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import pyarrow as pa
|
|
7
|
+
from chdb import query as chdb_query
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Table:
|
|
11
|
+
"""
|
|
12
|
+
Table is a wrapper of multiple formats of data buffer, including parquet file path,
|
|
13
|
+
parquet bytes, and pandas dataframe.
|
|
14
|
+
if use_memfd is True, will try using memfd_create to create a temp file in memory, which is
|
|
15
|
+
only available on Linux. If failed, will fallback to use tempfile.mkstemp to create a temp file
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
parquet_path: str = None,
|
|
21
|
+
temp_parquet_path: str = None,
|
|
22
|
+
parquet_memoryview: memoryview = None,
|
|
23
|
+
dataframe: pd.DataFrame = None,
|
|
24
|
+
arrow_table: pa.Table = None,
|
|
25
|
+
use_memfd: bool = False,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Initialize a Table object with one of parquet file path, parquet bytes, pandas dataframe or
|
|
29
|
+
parquet table.
|
|
30
|
+
"""
|
|
31
|
+
self._parquet_path = parquet_path
|
|
32
|
+
self._temp_parquet_path = temp_parquet_path
|
|
33
|
+
self._parquet_memoryview = parquet_memoryview
|
|
34
|
+
self._dataframe = dataframe
|
|
35
|
+
self._arrow_table = arrow_table
|
|
36
|
+
self.use_memfd = use_memfd
|
|
37
|
+
self._rows_read = 0
|
|
38
|
+
self._bytes_read = 0
|
|
39
|
+
self._elapsed = 0
|
|
40
|
+
|
|
41
|
+
def __del__(self):
|
|
42
|
+
if self._temp_parquet_path is not None:
|
|
43
|
+
try:
|
|
44
|
+
os.remove(self._temp_parquet_path)
|
|
45
|
+
except OSError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def rows_read(self):
|
|
49
|
+
return self._rows_read
|
|
50
|
+
|
|
51
|
+
def bytes_read(self):
|
|
52
|
+
return self._bytes_read
|
|
53
|
+
|
|
54
|
+
def elapsed(self):
|
|
55
|
+
return self._elapsed
|
|
56
|
+
|
|
57
|
+
def to_pandas(self) -> pd.DataFrame:
|
|
58
|
+
if self._dataframe is None:
|
|
59
|
+
if self._arrow_table is not None:
|
|
60
|
+
return self._arrow_table.to_pandas()
|
|
61
|
+
elif self._parquet_memoryview is not None:
|
|
62
|
+
# wrap bytes to ReadBuffer
|
|
63
|
+
pq_reader = BytesIO(self._parquet_memoryview.tobytes())
|
|
64
|
+
return pandas_read_parquet(pq_reader)
|
|
65
|
+
elif self._parquet_path is not None:
|
|
66
|
+
return pandas_read_parquet(self._parquet_path)
|
|
67
|
+
elif self._temp_parquet_path is not None:
|
|
68
|
+
return pandas_read_parquet(self._temp_parquet_path)
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError("No data buffer in Table object")
|
|
71
|
+
return self._dataframe
|
|
72
|
+
|
|
73
|
+
def flush_to_disk(self):
|
|
74
|
+
"""
|
|
75
|
+
Flush the data in memory to disk.
|
|
76
|
+
"""
|
|
77
|
+
if self._parquet_path is not None or self._temp_parquet_path is not None:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
if self._dataframe is not None:
|
|
81
|
+
self._df_to_disk(self._dataframe)
|
|
82
|
+
self._dataframe = None
|
|
83
|
+
elif self._arrow_table is not None:
|
|
84
|
+
self._arrow_table_to_disk(self._arrow_table)
|
|
85
|
+
self._arrow_table = None
|
|
86
|
+
elif self._parquet_memoryview is not None:
|
|
87
|
+
self._memoryview_to_disk(self._parquet_memoryview)
|
|
88
|
+
self._parquet_memoryview = None
|
|
89
|
+
else:
|
|
90
|
+
raise ValueError("No data in Table object")
|
|
91
|
+
|
|
92
|
+
def _df_to_disk(self, df):
|
|
93
|
+
with tempfile.NamedTemporaryFile(suffix=".parquet", delete=False) as tmp:
|
|
94
|
+
df.to_parquet(tmp)
|
|
95
|
+
self._temp_parquet_path = tmp.name
|
|
96
|
+
|
|
97
|
+
def _arrow_table_to_disk(self, arrow_table):
|
|
98
|
+
with tempfile.NamedTemporaryFile(suffix=".parquet", delete=False) as tmp:
|
|
99
|
+
pa.parquet.write_table(arrow_table, tmp.name)
|
|
100
|
+
self._temp_parquet_path = tmp.name
|
|
101
|
+
|
|
102
|
+
def _memoryview_to_disk(self, memoryview):
|
|
103
|
+
# copy memoryview to temp file
|
|
104
|
+
with tempfile.NamedTemporaryFile(suffix=".parquet", delete=False) as tmp:
|
|
105
|
+
tmp.write(memoryview.tobytes())
|
|
106
|
+
self._temp_parquet_path = tmp.name
|
|
107
|
+
|
|
108
|
+
def __repr__(self):
|
|
109
|
+
return repr(self.to_pandas())
|
|
110
|
+
|
|
111
|
+
def __str__(self):
|
|
112
|
+
return str(self.to_pandas())
|
|
113
|
+
|
|
114
|
+
def query(self, sql: str, **kwargs) -> "Table":
|
|
115
|
+
"""
|
|
116
|
+
Query on current Table object, return a new Table object.
|
|
117
|
+
The `FROM` table name in SQL should always be `__table__`. eg:
|
|
118
|
+
`SELECT * FROM __table__ WHERE ...`
|
|
119
|
+
"""
|
|
120
|
+
self._validate_sql(sql)
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
self._parquet_path is not None
|
|
124
|
+
): # if we have parquet file path, run chdb query on it directly is faster
|
|
125
|
+
return self._query_on_path(self._parquet_path, sql, **kwargs)
|
|
126
|
+
elif self._temp_parquet_path is not None:
|
|
127
|
+
return self._query_on_path(self._temp_parquet_path, sql, **kwargs)
|
|
128
|
+
elif self._parquet_memoryview is not None:
|
|
129
|
+
return self.queryParquetBuffer(sql, **kwargs)
|
|
130
|
+
elif self._dataframe is not None:
|
|
131
|
+
return self.queryDF(sql, **kwargs)
|
|
132
|
+
elif self._arrow_table is not None:
|
|
133
|
+
return self.queryArrowTable(sql, **kwargs)
|
|
134
|
+
else:
|
|
135
|
+
raise ValueError("Table object is not initialized correctly")
|
|
136
|
+
|
|
137
|
+
# alias sql = query
|
|
138
|
+
sql = query
|
|
139
|
+
|
|
140
|
+
def show(self):
|
|
141
|
+
print(self.to_pandas())
|
|
142
|
+
|
|
143
|
+
def _query_on_path(self, path, sql, **kwargs):
|
|
144
|
+
new_sql = sql.replace("__table__", f'file("{path}", Parquet)')
|
|
145
|
+
res = chdb_query(new_sql, "Parquet", **kwargs)
|
|
146
|
+
tbl = Table(parquet_memoryview=res.get_memview())
|
|
147
|
+
tbl._rows_read = res.rows_read()
|
|
148
|
+
tbl._bytes_read = res.bytes_read()
|
|
149
|
+
tbl._elapsed = res.elapsed()
|
|
150
|
+
return tbl
|
|
151
|
+
|
|
152
|
+
def _validate_sql(self, sql):
|
|
153
|
+
if "__table__" not in sql:
|
|
154
|
+
raise ValueError("SQL should always contain `FROM __table__`")
|
|
155
|
+
|
|
156
|
+
def queryParquetBuffer(self, sql: str, **kwargs) -> "Table":
|
|
157
|
+
if "__table__" not in sql:
|
|
158
|
+
raise ValueError("SQL should always contain `FROM __table__`")
|
|
159
|
+
if self._parquet_memoryview is None:
|
|
160
|
+
raise ValueError("Parquet buffer is None")
|
|
161
|
+
|
|
162
|
+
temp_path = None
|
|
163
|
+
parquet_fd = -1
|
|
164
|
+
if self.use_memfd:
|
|
165
|
+
parquet_fd = memfd_create("parquet_buffer")
|
|
166
|
+
# if memfd_create failed, use tempfile to create a file descriptor for the memoryview
|
|
167
|
+
if parquet_fd == -1:
|
|
168
|
+
parquet_fd, temp_path = tempfile.mkstemp()
|
|
169
|
+
ffd = os.fdopen(parquet_fd, "wb")
|
|
170
|
+
ffd.write(self._parquet_memoryview.tobytes())
|
|
171
|
+
ffd.flush()
|
|
172
|
+
ret = self._run_on_temp(parquet_fd, temp_path, sql=sql, fmt="Parquet", **kwargs)
|
|
173
|
+
ffd.close()
|
|
174
|
+
if temp_path is not None:
|
|
175
|
+
os.remove(temp_path)
|
|
176
|
+
return ret
|
|
177
|
+
|
|
178
|
+
def queryArrowTable(self, sql: str, **kwargs) -> "Table":
|
|
179
|
+
if "__table__" not in sql:
|
|
180
|
+
raise ValueError("SQL should always contain `FROM __table__`")
|
|
181
|
+
if self._arrow_table is None:
|
|
182
|
+
raise ValueError("Arrow table is None")
|
|
183
|
+
|
|
184
|
+
temp_path = None
|
|
185
|
+
arrow_fd = -1
|
|
186
|
+
if self.use_memfd:
|
|
187
|
+
arrow_fd = memfd_create("arrow_buffer")
|
|
188
|
+
if arrow_fd == -1:
|
|
189
|
+
arrow_fd, temp_path = tempfile.mkstemp()
|
|
190
|
+
ffd = os.fdopen(arrow_fd, "wb")
|
|
191
|
+
with pa.RecordBatchFileWriter(ffd, self._arrow_table.schema) as writer:
|
|
192
|
+
writer.write_table(self._arrow_table)
|
|
193
|
+
ffd.flush()
|
|
194
|
+
ret = self._run_on_temp(arrow_fd, temp_path, sql=sql, fmt="Arrow", **kwargs)
|
|
195
|
+
ffd.close()
|
|
196
|
+
if temp_path is not None:
|
|
197
|
+
os.remove(temp_path)
|
|
198
|
+
return ret
|
|
199
|
+
|
|
200
|
+
def queryDF(self, sql: str, **kwargs) -> "Table":
|
|
201
|
+
if "__table__" not in sql:
|
|
202
|
+
raise ValueError("SQL should always contain `FROM __table__`")
|
|
203
|
+
if self._dataframe is None:
|
|
204
|
+
raise ValueError("Dataframe is None")
|
|
205
|
+
|
|
206
|
+
temp_path = None
|
|
207
|
+
parquet_fd = -1
|
|
208
|
+
if self.use_memfd:
|
|
209
|
+
parquet_fd = memfd_create()
|
|
210
|
+
if parquet_fd == -1:
|
|
211
|
+
parquet_fd, temp_path = tempfile.mkstemp()
|
|
212
|
+
ffd = os.fdopen(parquet_fd, "wb")
|
|
213
|
+
self._dataframe.to_parquet(ffd, engine="pyarrow", compression=None)
|
|
214
|
+
ffd.flush()
|
|
215
|
+
ret = self._run_on_temp(parquet_fd, temp_path, sql=sql, fmt="Parquet", **kwargs)
|
|
216
|
+
ffd.close()
|
|
217
|
+
if temp_path is not None:
|
|
218
|
+
os.remove(temp_path)
|
|
219
|
+
return ret
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def queryStatic(sql: str, **kwargs) -> "Table":
|
|
223
|
+
"""
|
|
224
|
+
Query on multiple Tables, use Table variables as the table name in SQL
|
|
225
|
+
eg.
|
|
226
|
+
table1 = Table(...)
|
|
227
|
+
table2 = Table(...)
|
|
228
|
+
query("SELECT * FROM __table1__ JOIN __table2__ ON ...", table1=table1, table2=table2)
|
|
229
|
+
"""
|
|
230
|
+
ansiTablePattern = re.compile(r"__([a-zA-Z][a-zA-Z0-9_]*)__")
|
|
231
|
+
temp_paths = []
|
|
232
|
+
ffds = []
|
|
233
|
+
|
|
234
|
+
def replace_table_name(match):
|
|
235
|
+
tableName = match.group(1)
|
|
236
|
+
if tableName not in kwargs:
|
|
237
|
+
raise ValueError(f"Table {tableName} should be passed as a parameter")
|
|
238
|
+
|
|
239
|
+
tbl = kwargs[tableName]
|
|
240
|
+
# if tbl is DataFrame, convert it to Table
|
|
241
|
+
if isinstance(tbl, pd.DataFrame):
|
|
242
|
+
tbl = Table(dataframe=tbl)
|
|
243
|
+
elif not isinstance(tbl, Table):
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"Table {tableName} should be an instance of Table or DataFrame")
|
|
246
|
+
|
|
247
|
+
if tbl._parquet_path is not None:
|
|
248
|
+
return f'file("{tbl._parquet_path}", Parquet)'
|
|
249
|
+
|
|
250
|
+
if tbl._temp_parquet_path is not None:
|
|
251
|
+
return f'file("{tbl._temp_parquet_path}", Parquet)'
|
|
252
|
+
|
|
253
|
+
temp_path = None
|
|
254
|
+
data_fd = -1
|
|
255
|
+
|
|
256
|
+
if tbl.use_memfd:
|
|
257
|
+
data_fd = memfd_create()
|
|
258
|
+
|
|
259
|
+
if data_fd == -1:
|
|
260
|
+
data_fd, temp_path = tempfile.mkstemp()
|
|
261
|
+
temp_paths.append(temp_path)
|
|
262
|
+
|
|
263
|
+
ffd = os.fdopen(data_fd, "wb")
|
|
264
|
+
ffds.append(ffd)
|
|
265
|
+
|
|
266
|
+
if tbl._parquet_memoryview is not None:
|
|
267
|
+
ffd.write(tbl._parquet_memoryview.tobytes())
|
|
268
|
+
ffd.flush()
|
|
269
|
+
os.lseek(data_fd, 0, os.SEEK_SET)
|
|
270
|
+
return f'file("/dev/fd/{data_fd}", Parquet)'
|
|
271
|
+
|
|
272
|
+
if tbl._dataframe is not None:
|
|
273
|
+
ffd.write(tbl._dataframe.to_parquet(engine="pyarrow", compression=None))
|
|
274
|
+
ffd.flush()
|
|
275
|
+
os.lseek(data_fd, 0, os.SEEK_SET)
|
|
276
|
+
return f'file("/dev/fd/{data_fd}", Parquet)'
|
|
277
|
+
|
|
278
|
+
if tbl._arrow_table is not None:
|
|
279
|
+
with pa.RecordBatchFileWriter(ffd, tbl._arrow_table.schema) as writer:
|
|
280
|
+
writer.write_table(tbl._arrow_table)
|
|
281
|
+
ffd.flush()
|
|
282
|
+
os.lseek(data_fd, 0, os.SEEK_SET)
|
|
283
|
+
return f'file("/dev/fd/{data_fd}", Arrow)'
|
|
284
|
+
|
|
285
|
+
raise ValueError(f"Table {tableName} is not initialized correctly")
|
|
286
|
+
|
|
287
|
+
sql = ansiTablePattern.sub(replace_table_name, sql)
|
|
288
|
+
res = chdb_query(sql, "Parquet")
|
|
289
|
+
|
|
290
|
+
for fd in ffds:
|
|
291
|
+
fd.close()
|
|
292
|
+
|
|
293
|
+
for tmp_path in temp_paths:
|
|
294
|
+
os.remove(tmp_path)
|
|
295
|
+
|
|
296
|
+
tbl = Table(parquet_memoryview=res.get_memview())
|
|
297
|
+
tbl._rows_read = res.rows_read()
|
|
298
|
+
tbl._bytes_read = res.bytes_read()
|
|
299
|
+
tbl._elapsed = res.elapsed()
|
|
300
|
+
return tbl
|
|
301
|
+
|
|
302
|
+
def _run_on_temp(
|
|
303
|
+
self,
|
|
304
|
+
fd: int,
|
|
305
|
+
temp_path: str = None,
|
|
306
|
+
sql: str = None,
|
|
307
|
+
fmt: str = "Parquet",
|
|
308
|
+
**kwargs,
|
|
309
|
+
) -> "Table":
|
|
310
|
+
# replace "__table__" with file("temp_path", Parquet) or file("/dev/fd/{parquet_fd}", Parquet)
|
|
311
|
+
if temp_path is not None:
|
|
312
|
+
new_sql = sql.replace("__table__", f'file("{temp_path}", {fmt})')
|
|
313
|
+
else:
|
|
314
|
+
os.lseek(fd, 0, os.SEEK_SET)
|
|
315
|
+
new_sql = sql.replace("__table__", f'file("/dev/fd/{fd}", {fmt})')
|
|
316
|
+
res = chdb_query(new_sql, "Parquet", **kwargs)
|
|
317
|
+
tbl = Table(parquet_memoryview=res.get_memview())
|
|
318
|
+
tbl._rows_read = res.rows_read()
|
|
319
|
+
tbl._bytes_read = res.bytes_read()
|
|
320
|
+
tbl._elapsed = res.elapsed()
|
|
321
|
+
return tbl
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def pandas_read_parquet(path) -> pd.DataFrame:
|
|
325
|
+
return pd.read_parquet(path)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def memfd_create(name: str = None) -> int:
|
|
329
|
+
"""
|
|
330
|
+
Try to use memfd_create(2) to create a file descriptor with memory.
|
|
331
|
+
Only available on Linux 3.17 or newer with glibc 2.27 or newer.
|
|
332
|
+
"""
|
|
333
|
+
if hasattr(os, "memfd_create"):
|
|
334
|
+
try:
|
|
335
|
+
fd = os.memfd_create(name, flags=os.MFD_CLOEXEC)
|
|
336
|
+
return fd
|
|
337
|
+
except: # noqa
|
|
338
|
+
return -1
|
|
339
|
+
return -1
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
if __name__ == "__main__":
|
|
343
|
+
import argparse
|
|
344
|
+
|
|
345
|
+
parser = argparse.ArgumentParser(description="Run SQL on parquet file")
|
|
346
|
+
parser.add_argument("parquet_path", type=str, help="path to parquet file")
|
|
347
|
+
parser.add_argument("sql", type=str, help="SQL to run")
|
|
348
|
+
parser.add_argument(
|
|
349
|
+
"--use-memfd",
|
|
350
|
+
action="store_true",
|
|
351
|
+
help="use memfd_create to create file descriptor",
|
|
352
|
+
)
|
|
353
|
+
args = parser.parse_args()
|
|
354
|
+
|
|
355
|
+
table = Table(parquet_path=args.parquet_path, use_memfd=args.use_memfd)
|
|
356
|
+
print(table.query(args.sql))
|
chdb/dbapi/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from .constants import FIELD_TYPE
|
|
2
|
+
from . import connections as _orig_conn
|
|
3
|
+
from .. import chdb_version
|
|
4
|
+
|
|
5
|
+
if len(chdb_version) > 3 and chdb_version[3] is not None:
|
|
6
|
+
VERSION_STRING = "%s.%s.%s_%s" % chdb_version
|
|
7
|
+
else:
|
|
8
|
+
VERSION_STRING = "%s.%s.%s" % chdb_version[:3]
|
|
9
|
+
|
|
10
|
+
threadsafety = 1
|
|
11
|
+
apilevel = "2.0"
|
|
12
|
+
paramstyle = "format"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DBAPISet(frozenset):
|
|
16
|
+
|
|
17
|
+
def __ne__(self, other):
|
|
18
|
+
if isinstance(other, set):
|
|
19
|
+
return frozenset.__ne__(self, other)
|
|
20
|
+
else:
|
|
21
|
+
return other not in self
|
|
22
|
+
|
|
23
|
+
def __eq__(self, other):
|
|
24
|
+
if isinstance(other, frozenset):
|
|
25
|
+
return frozenset.__eq__(self, other)
|
|
26
|
+
else:
|
|
27
|
+
return other in self
|
|
28
|
+
|
|
29
|
+
def __hash__(self):
|
|
30
|
+
return frozenset.__hash__(self)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# TODO it's in pep249 find out meaning and usage of this
|
|
34
|
+
# https://www.python.org/dev/peps/pep-0249/#string
|
|
35
|
+
STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING,
|
|
36
|
+
FIELD_TYPE.VAR_STRING])
|
|
37
|
+
BINARY = DBAPISet([FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB,
|
|
38
|
+
FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.TINY_BLOB])
|
|
39
|
+
NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT,
|
|
40
|
+
FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG,
|
|
41
|
+
FIELD_TYPE.TINY, FIELD_TYPE.YEAR])
|
|
42
|
+
DATE = DBAPISet([FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE])
|
|
43
|
+
TIME = DBAPISet([FIELD_TYPE.TIME])
|
|
44
|
+
TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME])
|
|
45
|
+
DATETIME = TIMESTAMP
|
|
46
|
+
ROWID = DBAPISet()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def Binary(x):
|
|
50
|
+
"""Return x as a binary type."""
|
|
51
|
+
return bytes(x)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def Connect(*args, **kwargs):
|
|
55
|
+
"""
|
|
56
|
+
Connect to the database; see connections.Connection.__init__() for
|
|
57
|
+
more information.
|
|
58
|
+
"""
|
|
59
|
+
from .connections import Connection
|
|
60
|
+
return Connection(*args, **kwargs)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if _orig_conn.Connection.__init__.__doc__ is not None:
|
|
64
|
+
Connect.__doc__ = _orig_conn.Connection.__init__.__doc__
|
|
65
|
+
del _orig_conn
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_client_info(): # for MySQLdb compatibility
|
|
69
|
+
version = chdb_version
|
|
70
|
+
if len(chdb_version) > 3 and chdb_version[3] is None:
|
|
71
|
+
version = chdb_version[:3]
|
|
72
|
+
return '.'.join(map(str, version))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
connect = Connection = Connect
|
|
76
|
+
|
|
77
|
+
NULL = "NULL"
|
|
78
|
+
|
|
79
|
+
__version__ = get_client_info()
|