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 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"]
@@ -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()