quickhouse 0.2.4__tar.gz → 0.3.1__tar.gz
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.
- {quickhouse-0.2.4 → quickhouse-0.3.1}/Cargo.lock +34 -29
- {quickhouse-0.2.4 → quickhouse-0.3.1}/Cargo.toml +12 -1
- quickhouse-0.3.1/PKG-INFO +227 -0
- quickhouse-0.3.1/README.md +196 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/Cargo.toml +4 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/config.rs +130 -4
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/ddl.rs +1 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/decode.rs +36 -10
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/decode_bigquery.rs +16 -4
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/decode_mysql.rs +16 -5
- quickhouse-0.3.1/crates/quickhouse-core/src/error.rs +208 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/lib.rs +11 -9
- quickhouse-0.3.1/crates/quickhouse-core/src/sink/bigquery.rs +1144 -0
- quickhouse-0.3.1/crates/quickhouse-core/src/sink/bigquery_proto.rs +388 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/sink/clickhouse.rs +58 -28
- quickhouse-0.3.1/crates/quickhouse-core/src/sink/mod.rs +177 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/source/bigquery.rs +6 -2
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/source/mysql.rs +6 -2
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/source/postgres.rs +10 -5
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/sync.rs +327 -99
- quickhouse-0.3.1/crates/quickhouse-core/src/transform.rs +314 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/types.rs +106 -1
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-py/src/lib.rs +95 -17
- {quickhouse-0.2.4 → quickhouse-0.3.1}/pyproject.toml +1 -1
- {quickhouse-0.2.4 → quickhouse-0.3.1}/python/quickhouse/__init__.py +14 -6
- {quickhouse-0.2.4 → quickhouse-0.3.1}/python/quickhouse/_quickhouse.pyi +69 -10
- quickhouse-0.2.4/PKG-INFO +0 -289
- quickhouse-0.2.4/README.md +0 -258
- quickhouse-0.2.4/crates/quickhouse-core/src/error.rs +0 -90
- quickhouse-0.2.4/crates/quickhouse-core/src/sink/mod.rs +0 -3
- quickhouse-0.2.4/crates/quickhouse-core/src/transform.rs +0 -177
- {quickhouse-0.2.4 → quickhouse-0.3.1}/LICENSE +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/memory.rs +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/source/mod.rs +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-py/Cargo.toml +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/python/quickhouse/progress.py +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.1}/python/quickhouse/py.typed +0 -0
|
@@ -331,7 +331,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
|
|
|
331
331
|
dependencies = [
|
|
332
332
|
"proc-macro2",
|
|
333
333
|
"quote",
|
|
334
|
-
"syn 3.0.
|
|
334
|
+
"syn 3.0.3",
|
|
335
335
|
]
|
|
336
336
|
|
|
337
337
|
[[package]]
|
|
@@ -1235,9 +1235,9 @@ dependencies = [
|
|
|
1235
1235
|
|
|
1236
1236
|
[[package]]
|
|
1237
1237
|
name = "glob"
|
|
1238
|
-
version = "0.3.
|
|
1238
|
+
version = "0.3.4"
|
|
1239
1239
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1240
|
-
checksum = "
|
|
1240
|
+
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
|
1241
1241
|
|
|
1242
1242
|
[[package]]
|
|
1243
1243
|
name = "google-cloud-auth"
|
|
@@ -1468,9 +1468,9 @@ dependencies = [
|
|
|
1468
1468
|
|
|
1469
1469
|
[[package]]
|
|
1470
1470
|
name = "hyper"
|
|
1471
|
-
version = "1.
|
|
1471
|
+
version = "1.11.0"
|
|
1472
1472
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1473
|
-
checksum = "
|
|
1473
|
+
checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
|
|
1474
1474
|
dependencies = [
|
|
1475
1475
|
"atomic-waker",
|
|
1476
1476
|
"bytes",
|
|
@@ -1841,9 +1841,9 @@ dependencies = [
|
|
|
1841
1841
|
|
|
1842
1842
|
[[package]]
|
|
1843
1843
|
name = "libc"
|
|
1844
|
-
version = "0.2.
|
|
1844
|
+
version = "0.2.189"
|
|
1845
1845
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1846
|
-
checksum = "
|
|
1846
|
+
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
1847
1847
|
|
|
1848
1848
|
[[package]]
|
|
1849
1849
|
name = "libloading"
|
|
@@ -2506,19 +2506,23 @@ dependencies = [
|
|
|
2506
2506
|
|
|
2507
2507
|
[[package]]
|
|
2508
2508
|
name = "quickhouse-core"
|
|
2509
|
-
version = "0.
|
|
2509
|
+
version = "0.3.1"
|
|
2510
2510
|
dependencies = [
|
|
2511
2511
|
"anyhow",
|
|
2512
2512
|
"arrow",
|
|
2513
2513
|
"arrow-array",
|
|
2514
2514
|
"arrow-schema",
|
|
2515
2515
|
"async-compression",
|
|
2516
|
+
"base64 0.22.1",
|
|
2516
2517
|
"bytes",
|
|
2517
2518
|
"chrono",
|
|
2518
2519
|
"futures",
|
|
2519
2520
|
"google-cloud-bigquery",
|
|
2521
|
+
"google-cloud-gax",
|
|
2520
2522
|
"google-cloud-googleapis",
|
|
2521
2523
|
"mysql_async",
|
|
2524
|
+
"prost",
|
|
2525
|
+
"prost-types",
|
|
2522
2526
|
"reqwest",
|
|
2523
2527
|
"rustls",
|
|
2524
2528
|
"rustls-pemfile",
|
|
@@ -2536,7 +2540,7 @@ dependencies = [
|
|
|
2536
2540
|
|
|
2537
2541
|
[[package]]
|
|
2538
2542
|
name = "quickhouse-py"
|
|
2539
|
-
version = "0.
|
|
2543
|
+
version = "0.3.1"
|
|
2540
2544
|
dependencies = [
|
|
2541
2545
|
"pyo3",
|
|
2542
2546
|
"quickhouse-core",
|
|
@@ -2973,14 +2977,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
|
2973
2977
|
dependencies = [
|
|
2974
2978
|
"proc-macro2",
|
|
2975
2979
|
"quote",
|
|
2976
|
-
"syn 3.0.
|
|
2980
|
+
"syn 3.0.3",
|
|
2977
2981
|
]
|
|
2978
2982
|
|
|
2979
2983
|
[[package]]
|
|
2980
2984
|
name = "serde_json"
|
|
2981
|
-
version = "1.0.
|
|
2985
|
+
version = "1.0.151"
|
|
2982
2986
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2983
|
-
checksum = "
|
|
2987
|
+
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
|
2984
2988
|
dependencies = [
|
|
2985
2989
|
"itoa",
|
|
2986
2990
|
"memchr",
|
|
@@ -3196,9 +3200,9 @@ dependencies = [
|
|
|
3196
3200
|
|
|
3197
3201
|
[[package]]
|
|
3198
3202
|
name = "syn"
|
|
3199
|
-
version = "3.0.
|
|
3203
|
+
version = "3.0.3"
|
|
3200
3204
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3201
|
-
checksum = "
|
|
3205
|
+
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
|
3202
3206
|
dependencies = [
|
|
3203
3207
|
"proc-macro2",
|
|
3204
3208
|
"quote",
|
|
@@ -3283,7 +3287,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
|
|
|
3283
3287
|
dependencies = [
|
|
3284
3288
|
"proc-macro2",
|
|
3285
3289
|
"quote",
|
|
3286
|
-
"syn 3.0.
|
|
3290
|
+
"syn 3.0.3",
|
|
3287
3291
|
]
|
|
3288
3292
|
|
|
3289
3293
|
[[package]]
|
|
@@ -3297,9 +3301,9 @@ dependencies = [
|
|
|
3297
3301
|
|
|
3298
3302
|
[[package]]
|
|
3299
3303
|
name = "time"
|
|
3300
|
-
version = "0.3.
|
|
3304
|
+
version = "0.3.54"
|
|
3301
3305
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3302
|
-
checksum = "
|
|
3306
|
+
checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244"
|
|
3303
3307
|
dependencies = [
|
|
3304
3308
|
"deranged",
|
|
3305
3309
|
"num-conv",
|
|
@@ -3317,9 +3321,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
|
|
3317
3321
|
|
|
3318
3322
|
[[package]]
|
|
3319
3323
|
name = "time-macros"
|
|
3320
|
-
version = "0.2.
|
|
3324
|
+
version = "0.2.32"
|
|
3321
3325
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3322
|
-
checksum = "
|
|
3326
|
+
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
|
|
3323
3327
|
dependencies = [
|
|
3324
3328
|
"num-conv",
|
|
3325
3329
|
"time-core",
|
|
@@ -3382,9 +3386,9 @@ dependencies = [
|
|
|
3382
3386
|
|
|
3383
3387
|
[[package]]
|
|
3384
3388
|
name = "tokio"
|
|
3385
|
-
version = "1.53.
|
|
3389
|
+
version = "1.53.1"
|
|
3386
3390
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3387
|
-
checksum = "
|
|
3391
|
+
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
|
3388
3392
|
dependencies = [
|
|
3389
3393
|
"bytes",
|
|
3390
3394
|
"libc",
|
|
@@ -3470,9 +3474,9 @@ dependencies = [
|
|
|
3470
3474
|
|
|
3471
3475
|
[[package]]
|
|
3472
3476
|
name = "tokio-stream"
|
|
3473
|
-
version = "0.1.
|
|
3477
|
+
version = "0.1.19"
|
|
3474
3478
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3475
|
-
checksum = "
|
|
3479
|
+
checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
|
|
3476
3480
|
dependencies = [
|
|
3477
3481
|
"futures-core",
|
|
3478
3482
|
"pin-project-lite",
|
|
@@ -3481,13 +3485,14 @@ dependencies = [
|
|
|
3481
3485
|
|
|
3482
3486
|
[[package]]
|
|
3483
3487
|
name = "tokio-util"
|
|
3484
|
-
version = "0.7.
|
|
3488
|
+
version = "0.7.19"
|
|
3485
3489
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3486
|
-
checksum = "
|
|
3490
|
+
checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
|
|
3487
3491
|
dependencies = [
|
|
3488
3492
|
"bytes",
|
|
3489
3493
|
"futures-core",
|
|
3490
3494
|
"futures-sink",
|
|
3495
|
+
"libc",
|
|
3491
3496
|
"pin-project-lite",
|
|
3492
3497
|
"tokio",
|
|
3493
3498
|
]
|
|
@@ -4209,18 +4214,18 @@ dependencies = [
|
|
|
4209
4214
|
|
|
4210
4215
|
[[package]]
|
|
4211
4216
|
name = "zerocopy"
|
|
4212
|
-
version = "0.8.
|
|
4217
|
+
version = "0.8.55"
|
|
4213
4218
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4214
|
-
checksum = "
|
|
4219
|
+
checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
|
|
4215
4220
|
dependencies = [
|
|
4216
4221
|
"zerocopy-derive",
|
|
4217
4222
|
]
|
|
4218
4223
|
|
|
4219
4224
|
[[package]]
|
|
4220
4225
|
name = "zerocopy-derive"
|
|
4221
|
-
version = "0.8.
|
|
4226
|
+
version = "0.8.55"
|
|
4222
4227
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4223
|
-
checksum = "
|
|
4228
|
+
checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
|
|
4224
4229
|
dependencies = [
|
|
4225
4230
|
"proc-macro2",
|
|
4226
4231
|
"quote",
|
|
@@ -3,7 +3,7 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/quickhouse-core", "crates/quickhouse-py"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.3.1"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
license = "MIT"
|
|
9
9
|
authors = ["quickhouse contributors"]
|
|
@@ -38,7 +38,18 @@ mysql_async = { version = "0.34", default-features = false, features = ["default
|
|
|
38
38
|
# that entirely, matching the rest of this workspace's pure-rustls stack.
|
|
39
39
|
google-cloud-bigquery = { version = "0.15", default-features = false, features = ["auth", "rustls-tls"] }
|
|
40
40
|
google-cloud-googleapis = { version = "0.16", features = ["bigquery"] }
|
|
41
|
+
# gRPC transport types (Status/Code) for the Storage Write API error handling.
|
|
42
|
+
google-cloud-gax = "0.19"
|
|
41
43
|
time = { version = "0.3", features = ["macros"] }
|
|
44
|
+
# Only used to encode Binary columns as BigQuery's BYTES JSON representation
|
|
45
|
+
# (base64 text) for the insertAll destination path.
|
|
46
|
+
base64 = "0.22"
|
|
47
|
+
# For the opt-in BigQuery Storage Write API path: prost-types provides the
|
|
48
|
+
# runtime protobuf `DescriptorProto`/`FieldDescriptorProto` we build from the
|
|
49
|
+
# Arrow schema; prost is used in tests to decode our hand-encoded rows and
|
|
50
|
+
# verify the wire format. Pinned to the versions the bigquery crate resolves.
|
|
51
|
+
prost = "0.13"
|
|
52
|
+
prost-types = "0.13"
|
|
42
53
|
|
|
43
54
|
# Faster release builds for the shipped wheel.
|
|
44
55
|
[profile.release]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quickhouse
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: Programming Language :: Rust
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Topic :: Database
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
12
|
+
Requires-Dist: tqdm>=4.60 ; extra == 'progress'
|
|
13
|
+
Requires-Dist: pytest>=7 ; extra == 'test'
|
|
14
|
+
Requires-Dist: psycopg[binary]>=3.1 ; extra == 'test'
|
|
15
|
+
Requires-Dist: pymysql>=1.1 ; extra == 'test'
|
|
16
|
+
Requires-Dist: clickhouse-connect>=0.7 ; extra == 'test'
|
|
17
|
+
Requires-Dist: tqdm>=4.60 ; extra == 'test'
|
|
18
|
+
Provides-Extra: progress
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Summary: Fast PostgreSQL/MySQL/BigQuery -> ClickHouse ETL with a Rust engine (parallel, bounded-memory, Arrow-based).
|
|
22
|
+
Keywords: etl,postgresql,mysql,bigquery,clickhouse,arrow,rust,data-engineering
|
|
23
|
+
Author-email: M Mirza Fahmi <mr.sm1l3yz@gmail.com>
|
|
24
|
+
License: MIT
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
27
|
+
Project-URL: Homepage, https://github.com/mmirzafahmi/quickhouse
|
|
28
|
+
Project-URL: Issues, https://github.com/mmirzafahmi/quickhouse/issues
|
|
29
|
+
Project-URL: Repository, https://github.com/mmirzafahmi/quickhouse
|
|
30
|
+
|
|
31
|
+
# quickhouse
|
|
32
|
+
|
|
33
|
+
**Move tables from PostgreSQL, MySQL, or BigQuery into ClickHouse or BigQuery — fast, in one function call.**
|
|
34
|
+
|
|
35
|
+
quickhouse is a small, typed Python API on top of a native Rust engine. You
|
|
36
|
+
hand it a source, a destination, and a table name; it figures out the schema,
|
|
37
|
+
creates the destination table, streams the rows across in parallel, and keeps
|
|
38
|
+
memory flat the whole way. The heavy lifting never touches Python objects —
|
|
39
|
+
each database's native wire protocol flows straight into Apache Arrow and out
|
|
40
|
+
the other side.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import quickhouse
|
|
44
|
+
|
|
45
|
+
src = quickhouse.Postgres("postgresql://user:pw@localhost:5432/shop")
|
|
46
|
+
dst = quickhouse.ClickHouse("http://localhost:8123", database="analytics")
|
|
47
|
+
|
|
48
|
+
result = quickhouse.sync(src, dst, dest_table="orders",
|
|
49
|
+
source_table="orders", key=["id"])
|
|
50
|
+
print(result) # rows_read, rows_written, bytes_written, duration_secs, new_watermark
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Why quickhouse
|
|
54
|
+
|
|
55
|
+
- **It's fast.** Rows are decoded straight off the wire into Arrow, in Rust —
|
|
56
|
+
no per-row Python, no intermediate DataFrame. Tables are split into ranges
|
|
57
|
+
and read in parallel, and decoding overlaps uploading. On a laptop-class box
|
|
58
|
+
a 1M-row, 20-column full refresh runs at **hundreds of thousands of rows per
|
|
59
|
+
second** while peak memory stays flat (under ~180 MB) no matter how much you
|
|
60
|
+
parallelize. Reproduce it with `python benchmarks/bench_transfer.py`.
|
|
61
|
+
|
|
62
|
+
- **It's one function call.** `sync()` replaces the cursor loop, manual
|
|
63
|
+
batching, retry logic, and `CREATE TABLE` you'd otherwise write by hand.
|
|
64
|
+
Defaults handle table creation, type mapping, parallelism, and batching, and
|
|
65
|
+
a typed stub gives you autocomplete on every argument.
|
|
66
|
+
|
|
67
|
+
- **It's safe with real, messy data.** Full refreshes swap in atomically, so a
|
|
68
|
+
crash never leaves a half-written table. Incremental syncs are idempotent —
|
|
69
|
+
safe to re-run or retry. Transient network blips retry automatically. And
|
|
70
|
+
legacy quirks like MySQL zero-dates or out-of-range timestamps are coerced to
|
|
71
|
+
`NULL` with a warning instead of aborting the run.
|
|
72
|
+
|
|
73
|
+
- **There's nothing to stand up.** `pip install quickhouse` and you're done —
|
|
74
|
+
no JVM, no Spark cluster, no separate service. It's an ordinary Python
|
|
75
|
+
dependency that runs wherever your jobs already run: cron, Airflow, Dagster,
|
|
76
|
+
a Lambda, or a plain script.
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install quickhouse
|
|
82
|
+
pip install "quickhouse[progress]" # adds a ready-made tqdm progress bar
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Prebuilt wheels ship for Python 3.9+ on Linux, macOS (Intel + Apple Silicon),
|
|
86
|
+
and Windows (x86_64) — no Rust toolchain needed. Building from source is only
|
|
87
|
+
for development; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
88
|
+
|
|
89
|
+
## Using it
|
|
90
|
+
|
|
91
|
+
A fuller call, with the options you'll reach for most:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import quickhouse as qh
|
|
95
|
+
|
|
96
|
+
src = qh.Postgres("postgresql://user:pw@localhost:5432/shop")
|
|
97
|
+
dst = qh.ClickHouse("http://localhost:8123", database="analytics")
|
|
98
|
+
|
|
99
|
+
qh.sync(
|
|
100
|
+
src, dst,
|
|
101
|
+
dest_table="orders",
|
|
102
|
+
source_table="orders", # or source_query="SELECT ..."
|
|
103
|
+
mode="incremental", # or "full"
|
|
104
|
+
watermark="updated_at", # required for incremental
|
|
105
|
+
key=["id"], # dedup key / ORDER BY
|
|
106
|
+
parallelism=8,
|
|
107
|
+
exclude=["internal_notes"],
|
|
108
|
+
rename={"amount": "amt"},
|
|
109
|
+
on_progress=lambda p: print(f"{p.rows_written:,} rows @ {p.rows_per_sec:,.0f}/s"),
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Sources and destinations
|
|
114
|
+
|
|
115
|
+
Pick a source and a destination by constructing the matching object —
|
|
116
|
+
everything else about `sync()` stays the same:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# sources
|
|
120
|
+
qh.Postgres("postgresql://user:pw@host:5432/db")
|
|
121
|
+
qh.MySQL("mysql://user:pw@host:3306/db", require_tls=True)
|
|
122
|
+
qh.BigQuery("my-gcp-project") # source_table="dataset.table"
|
|
123
|
+
|
|
124
|
+
# destinations
|
|
125
|
+
qh.ClickHouse("http://host:8123", database="analytics")
|
|
126
|
+
qh.BigQuery("my-gcp-project", dataset_id="analytics")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
BigQuery authenticates with a service-account key (`credentials_file=...`) or
|
|
130
|
+
Application Default Credentials. As a **destination** it also takes
|
|
131
|
+
`write_method`: the default `"insert_all"` (simple, proven) or the opt-in
|
|
132
|
+
`"storage_write"` (the gRPC Storage Write API — free and higher-throughput).
|
|
133
|
+
|
|
134
|
+
The DDL knobs (`engine`, `partition_by`, `order_by`, `primary_key`, `key`) are
|
|
135
|
+
interpreted per destination — for ClickHouse they shape the `MergeTree`
|
|
136
|
+
DDL; for BigQuery they map to partitioning and clustering. quickhouse creates
|
|
137
|
+
the table for you (`create_if_missing=True` by default) with a sensible schema
|
|
138
|
+
derived from the source.
|
|
139
|
+
|
|
140
|
+
### Full vs. incremental
|
|
141
|
+
|
|
142
|
+
**Full** reloads the whole table into a staging table, then swaps it into place
|
|
143
|
+
atomically — a crash mid-run never leaves the destination partial. For a
|
|
144
|
+
BigQuery destination that swap runs as a query (a billed scan of the staged
|
|
145
|
+
data), not a free copy job — BigQuery's copy jobs can silently skip rows still
|
|
146
|
+
sitting in a table's streaming buffer, so a real query is what keeps this
|
|
147
|
+
correct rather than just fast.
|
|
148
|
+
|
|
149
|
+
**Incremental** tracks a high-water mark (the `watermark` column) in a small
|
|
150
|
+
state table in the destination and copies only newer rows. Updated rows are
|
|
151
|
+
deduplicated on `key` — via ClickHouse's `ReplacingMergeTree`, or a `MERGE`
|
|
152
|
+
upsert on BigQuery (where `key` is therefore required). Re-running with no new
|
|
153
|
+
data does nothing.
|
|
154
|
+
|
|
155
|
+
For daily syncs that need to catch late-arriving or edited rows, set
|
|
156
|
+
`lookback_seconds` to re-scan a trailing window (e.g. `3 * 86400` for the last
|
|
157
|
+
three days) — the dedup above keeps that overlap from creating duplicates.
|
|
158
|
+
|
|
159
|
+
### Watching progress and diagnosing failures
|
|
160
|
+
|
|
161
|
+
`on_progress` is a plain callback you can point at anything; `qh.progress_bar()`
|
|
162
|
+
wraps [tqdm](https://github.com/tqdm/tqdm) for a ready-made bar. Every `sync()`
|
|
163
|
+
also logs each step to stderr (`RUST_LOG=quickhouse_core=debug` for the actual
|
|
164
|
+
SQL).
|
|
165
|
+
|
|
166
|
+
When something goes wrong, `sync()` raises a `RuntimeError` written to be
|
|
167
|
+
actionable on its own: it names the table involved, and for a bad config or an
|
|
168
|
+
unmappable column it says exactly what's wrong and how to fix it (e.g.
|
|
169
|
+
`exclude=` the column or cast it in a `source_query`). Underlying database
|
|
170
|
+
errors are surfaced verbatim rather than wrapped in something generic.
|
|
171
|
+
|
|
172
|
+
### Full parameter list
|
|
173
|
+
|
|
174
|
+
| Parameter | Meaning |
|
|
175
|
+
| --- | --- |
|
|
176
|
+
| `source_table` / `source_query` | Read a whole table, or a custom `SELECT` (one required) |
|
|
177
|
+
| `dest_table` | Destination table name |
|
|
178
|
+
| `mode` | `"full"` or `"incremental"` |
|
|
179
|
+
| `watermark` | Monotonic column for incremental (e.g. `updated_at`); ignored in full mode |
|
|
180
|
+
| `lookback_seconds` | Re-scan a trailing window of the watermark to catch late/edited rows; `0` disables (default) |
|
|
181
|
+
| `key` | Dedup key (required for BigQuery incremental) |
|
|
182
|
+
| `create_if_missing` | Auto-create the destination table (default `True`) |
|
|
183
|
+
| `engine`, `order_by`, `partition_by`, `primary_key` | DDL knobs, interpreted per destination |
|
|
184
|
+
| `parallelism` | Concurrent read streams |
|
|
185
|
+
| `batch_rows` / `batch_bytes` | Per-batch size knobs (rows, or estimated bytes) |
|
|
186
|
+
| `max_memory_bytes` | Hard ceiling on total in-flight memory; decoding blocks when hit (default 512 MiB, `0` = unbounded) |
|
|
187
|
+
| `type_overrides` | Force a destination column type, e.g. `{"qty": "Decimal(18, 3)"}` |
|
|
188
|
+
| `rename`, `include`, `exclude` | Column renames and allow/deny lists |
|
|
189
|
+
| `on_progress` | Progress callback |
|
|
190
|
+
|
|
191
|
+
## How types are mapped
|
|
192
|
+
|
|
193
|
+
quickhouse maps each source type to a sensible destination type automatically:
|
|
194
|
+
integers to integers, floats to floats, text/JSON/UUID to strings, dates and
|
|
195
|
+
timestamps across as-is, and booleans preserved. A few deliberate choices worth
|
|
196
|
+
knowing:
|
|
197
|
+
|
|
198
|
+
- **Arbitrary-precision decimals** (`numeric`/`DECIMAL`/`NUMERIC`) default to
|
|
199
|
+
`Float64`, since precision can't be recovered from the type alone. Pin an
|
|
200
|
+
exact type with `type_overrides` (e.g. `"Decimal(18, 2)"`).
|
|
201
|
+
- **`TIME`** columns transfer as canonical text into a `String` column
|
|
202
|
+
(ClickHouse has no time-of-day type).
|
|
203
|
+
- **Out-of-range dates** (and MySQL zero-dates like `0000-00-00`) coerce to
|
|
204
|
+
`NULL` with a warning rather than failing the transfer.
|
|
205
|
+
- **Nullable** source columns stay nullable in the destination.
|
|
206
|
+
|
|
207
|
+
Arrays and composite (`RECORD`/`STRUCT`) types aren't supported yet.
|
|
208
|
+
|
|
209
|
+
## Limitations
|
|
210
|
+
|
|
211
|
+
- **mTLS** (client-certificate auth) isn't supported; server TLS is, including
|
|
212
|
+
an extra CA file via `ca_cert_file=...` for providers like AWS RDS.
|
|
213
|
+
- **Array / composite types** aren't mapped yet.
|
|
214
|
+
- **BigQuery as a source** reads through a single connection — `parallelism`
|
|
215
|
+
becomes a server-side hint rather than true client-side fan-out (a limitation
|
|
216
|
+
of the underlying crate's read API).
|
|
217
|
+
- **No CLI yet**, and CDC / custom transforms are future work.
|
|
218
|
+
|
|
219
|
+
## Contributing
|
|
220
|
+
|
|
221
|
+
Bug reports, new source/type mappings, and PRs are welcome — see
|
|
222
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for build steps, tests, and layout.
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
|
227
|
+
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# quickhouse
|
|
2
|
+
|
|
3
|
+
**Move tables from PostgreSQL, MySQL, or BigQuery into ClickHouse or BigQuery — fast, in one function call.**
|
|
4
|
+
|
|
5
|
+
quickhouse is a small, typed Python API on top of a native Rust engine. You
|
|
6
|
+
hand it a source, a destination, and a table name; it figures out the schema,
|
|
7
|
+
creates the destination table, streams the rows across in parallel, and keeps
|
|
8
|
+
memory flat the whole way. The heavy lifting never touches Python objects —
|
|
9
|
+
each database's native wire protocol flows straight into Apache Arrow and out
|
|
10
|
+
the other side.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import quickhouse
|
|
14
|
+
|
|
15
|
+
src = quickhouse.Postgres("postgresql://user:pw@localhost:5432/shop")
|
|
16
|
+
dst = quickhouse.ClickHouse("http://localhost:8123", database="analytics")
|
|
17
|
+
|
|
18
|
+
result = quickhouse.sync(src, dst, dest_table="orders",
|
|
19
|
+
source_table="orders", key=["id"])
|
|
20
|
+
print(result) # rows_read, rows_written, bytes_written, duration_secs, new_watermark
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Why quickhouse
|
|
24
|
+
|
|
25
|
+
- **It's fast.** Rows are decoded straight off the wire into Arrow, in Rust —
|
|
26
|
+
no per-row Python, no intermediate DataFrame. Tables are split into ranges
|
|
27
|
+
and read in parallel, and decoding overlaps uploading. On a laptop-class box
|
|
28
|
+
a 1M-row, 20-column full refresh runs at **hundreds of thousands of rows per
|
|
29
|
+
second** while peak memory stays flat (under ~180 MB) no matter how much you
|
|
30
|
+
parallelize. Reproduce it with `python benchmarks/bench_transfer.py`.
|
|
31
|
+
|
|
32
|
+
- **It's one function call.** `sync()` replaces the cursor loop, manual
|
|
33
|
+
batching, retry logic, and `CREATE TABLE` you'd otherwise write by hand.
|
|
34
|
+
Defaults handle table creation, type mapping, parallelism, and batching, and
|
|
35
|
+
a typed stub gives you autocomplete on every argument.
|
|
36
|
+
|
|
37
|
+
- **It's safe with real, messy data.** Full refreshes swap in atomically, so a
|
|
38
|
+
crash never leaves a half-written table. Incremental syncs are idempotent —
|
|
39
|
+
safe to re-run or retry. Transient network blips retry automatically. And
|
|
40
|
+
legacy quirks like MySQL zero-dates or out-of-range timestamps are coerced to
|
|
41
|
+
`NULL` with a warning instead of aborting the run.
|
|
42
|
+
|
|
43
|
+
- **There's nothing to stand up.** `pip install quickhouse` and you're done —
|
|
44
|
+
no JVM, no Spark cluster, no separate service. It's an ordinary Python
|
|
45
|
+
dependency that runs wherever your jobs already run: cron, Airflow, Dagster,
|
|
46
|
+
a Lambda, or a plain script.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install quickhouse
|
|
52
|
+
pip install "quickhouse[progress]" # adds a ready-made tqdm progress bar
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Prebuilt wheels ship for Python 3.9+ on Linux, macOS (Intel + Apple Silicon),
|
|
56
|
+
and Windows (x86_64) — no Rust toolchain needed. Building from source is only
|
|
57
|
+
for development; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
58
|
+
|
|
59
|
+
## Using it
|
|
60
|
+
|
|
61
|
+
A fuller call, with the options you'll reach for most:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import quickhouse as qh
|
|
65
|
+
|
|
66
|
+
src = qh.Postgres("postgresql://user:pw@localhost:5432/shop")
|
|
67
|
+
dst = qh.ClickHouse("http://localhost:8123", database="analytics")
|
|
68
|
+
|
|
69
|
+
qh.sync(
|
|
70
|
+
src, dst,
|
|
71
|
+
dest_table="orders",
|
|
72
|
+
source_table="orders", # or source_query="SELECT ..."
|
|
73
|
+
mode="incremental", # or "full"
|
|
74
|
+
watermark="updated_at", # required for incremental
|
|
75
|
+
key=["id"], # dedup key / ORDER BY
|
|
76
|
+
parallelism=8,
|
|
77
|
+
exclude=["internal_notes"],
|
|
78
|
+
rename={"amount": "amt"},
|
|
79
|
+
on_progress=lambda p: print(f"{p.rows_written:,} rows @ {p.rows_per_sec:,.0f}/s"),
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Sources and destinations
|
|
84
|
+
|
|
85
|
+
Pick a source and a destination by constructing the matching object —
|
|
86
|
+
everything else about `sync()` stays the same:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# sources
|
|
90
|
+
qh.Postgres("postgresql://user:pw@host:5432/db")
|
|
91
|
+
qh.MySQL("mysql://user:pw@host:3306/db", require_tls=True)
|
|
92
|
+
qh.BigQuery("my-gcp-project") # source_table="dataset.table"
|
|
93
|
+
|
|
94
|
+
# destinations
|
|
95
|
+
qh.ClickHouse("http://host:8123", database="analytics")
|
|
96
|
+
qh.BigQuery("my-gcp-project", dataset_id="analytics")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
BigQuery authenticates with a service-account key (`credentials_file=...`) or
|
|
100
|
+
Application Default Credentials. As a **destination** it also takes
|
|
101
|
+
`write_method`: the default `"insert_all"` (simple, proven) or the opt-in
|
|
102
|
+
`"storage_write"` (the gRPC Storage Write API — free and higher-throughput).
|
|
103
|
+
|
|
104
|
+
The DDL knobs (`engine`, `partition_by`, `order_by`, `primary_key`, `key`) are
|
|
105
|
+
interpreted per destination — for ClickHouse they shape the `MergeTree`
|
|
106
|
+
DDL; for BigQuery they map to partitioning and clustering. quickhouse creates
|
|
107
|
+
the table for you (`create_if_missing=True` by default) with a sensible schema
|
|
108
|
+
derived from the source.
|
|
109
|
+
|
|
110
|
+
### Full vs. incremental
|
|
111
|
+
|
|
112
|
+
**Full** reloads the whole table into a staging table, then swaps it into place
|
|
113
|
+
atomically — a crash mid-run never leaves the destination partial. For a
|
|
114
|
+
BigQuery destination that swap runs as a query (a billed scan of the staged
|
|
115
|
+
data), not a free copy job — BigQuery's copy jobs can silently skip rows still
|
|
116
|
+
sitting in a table's streaming buffer, so a real query is what keeps this
|
|
117
|
+
correct rather than just fast.
|
|
118
|
+
|
|
119
|
+
**Incremental** tracks a high-water mark (the `watermark` column) in a small
|
|
120
|
+
state table in the destination and copies only newer rows. Updated rows are
|
|
121
|
+
deduplicated on `key` — via ClickHouse's `ReplacingMergeTree`, or a `MERGE`
|
|
122
|
+
upsert on BigQuery (where `key` is therefore required). Re-running with no new
|
|
123
|
+
data does nothing.
|
|
124
|
+
|
|
125
|
+
For daily syncs that need to catch late-arriving or edited rows, set
|
|
126
|
+
`lookback_seconds` to re-scan a trailing window (e.g. `3 * 86400` for the last
|
|
127
|
+
three days) — the dedup above keeps that overlap from creating duplicates.
|
|
128
|
+
|
|
129
|
+
### Watching progress and diagnosing failures
|
|
130
|
+
|
|
131
|
+
`on_progress` is a plain callback you can point at anything; `qh.progress_bar()`
|
|
132
|
+
wraps [tqdm](https://github.com/tqdm/tqdm) for a ready-made bar. Every `sync()`
|
|
133
|
+
also logs each step to stderr (`RUST_LOG=quickhouse_core=debug` for the actual
|
|
134
|
+
SQL).
|
|
135
|
+
|
|
136
|
+
When something goes wrong, `sync()` raises a `RuntimeError` written to be
|
|
137
|
+
actionable on its own: it names the table involved, and for a bad config or an
|
|
138
|
+
unmappable column it says exactly what's wrong and how to fix it (e.g.
|
|
139
|
+
`exclude=` the column or cast it in a `source_query`). Underlying database
|
|
140
|
+
errors are surfaced verbatim rather than wrapped in something generic.
|
|
141
|
+
|
|
142
|
+
### Full parameter list
|
|
143
|
+
|
|
144
|
+
| Parameter | Meaning |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `source_table` / `source_query` | Read a whole table, or a custom `SELECT` (one required) |
|
|
147
|
+
| `dest_table` | Destination table name |
|
|
148
|
+
| `mode` | `"full"` or `"incremental"` |
|
|
149
|
+
| `watermark` | Monotonic column for incremental (e.g. `updated_at`); ignored in full mode |
|
|
150
|
+
| `lookback_seconds` | Re-scan a trailing window of the watermark to catch late/edited rows; `0` disables (default) |
|
|
151
|
+
| `key` | Dedup key (required for BigQuery incremental) |
|
|
152
|
+
| `create_if_missing` | Auto-create the destination table (default `True`) |
|
|
153
|
+
| `engine`, `order_by`, `partition_by`, `primary_key` | DDL knobs, interpreted per destination |
|
|
154
|
+
| `parallelism` | Concurrent read streams |
|
|
155
|
+
| `batch_rows` / `batch_bytes` | Per-batch size knobs (rows, or estimated bytes) |
|
|
156
|
+
| `max_memory_bytes` | Hard ceiling on total in-flight memory; decoding blocks when hit (default 512 MiB, `0` = unbounded) |
|
|
157
|
+
| `type_overrides` | Force a destination column type, e.g. `{"qty": "Decimal(18, 3)"}` |
|
|
158
|
+
| `rename`, `include`, `exclude` | Column renames and allow/deny lists |
|
|
159
|
+
| `on_progress` | Progress callback |
|
|
160
|
+
|
|
161
|
+
## How types are mapped
|
|
162
|
+
|
|
163
|
+
quickhouse maps each source type to a sensible destination type automatically:
|
|
164
|
+
integers to integers, floats to floats, text/JSON/UUID to strings, dates and
|
|
165
|
+
timestamps across as-is, and booleans preserved. A few deliberate choices worth
|
|
166
|
+
knowing:
|
|
167
|
+
|
|
168
|
+
- **Arbitrary-precision decimals** (`numeric`/`DECIMAL`/`NUMERIC`) default to
|
|
169
|
+
`Float64`, since precision can't be recovered from the type alone. Pin an
|
|
170
|
+
exact type with `type_overrides` (e.g. `"Decimal(18, 2)"`).
|
|
171
|
+
- **`TIME`** columns transfer as canonical text into a `String` column
|
|
172
|
+
(ClickHouse has no time-of-day type).
|
|
173
|
+
- **Out-of-range dates** (and MySQL zero-dates like `0000-00-00`) coerce to
|
|
174
|
+
`NULL` with a warning rather than failing the transfer.
|
|
175
|
+
- **Nullable** source columns stay nullable in the destination.
|
|
176
|
+
|
|
177
|
+
Arrays and composite (`RECORD`/`STRUCT`) types aren't supported yet.
|
|
178
|
+
|
|
179
|
+
## Limitations
|
|
180
|
+
|
|
181
|
+
- **mTLS** (client-certificate auth) isn't supported; server TLS is, including
|
|
182
|
+
an extra CA file via `ca_cert_file=...` for providers like AWS RDS.
|
|
183
|
+
- **Array / composite types** aren't mapped yet.
|
|
184
|
+
- **BigQuery as a source** reads through a single connection — `parallelism`
|
|
185
|
+
becomes a server-side hint rather than true client-side fan-out (a limitation
|
|
186
|
+
of the underlying crate's read API).
|
|
187
|
+
- **No CLI yet**, and CDC / custom transforms are future work.
|
|
188
|
+
|
|
189
|
+
## Contributing
|
|
190
|
+
|
|
191
|
+
Bug reports, new source/type mappings, and PRs are welcome — see
|
|
192
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for build steps, tests, and layout.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
@@ -31,7 +31,11 @@ rustls-pemfile.workspace = true
|
|
|
31
31
|
mysql_async.workspace = true
|
|
32
32
|
google-cloud-bigquery.workspace = true
|
|
33
33
|
google-cloud-googleapis.workspace = true
|
|
34
|
+
google-cloud-gax.workspace = true
|
|
34
35
|
time.workspace = true
|
|
36
|
+
base64.workspace = true
|
|
37
|
+
prost.workspace = true
|
|
38
|
+
prost-types.workspace = true
|
|
35
39
|
|
|
36
40
|
[dev-dependencies]
|
|
37
41
|
anyhow.workspace = true
|