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.
Files changed (37) hide show
  1. {quickhouse-0.2.4 → quickhouse-0.3.1}/Cargo.lock +34 -29
  2. {quickhouse-0.2.4 → quickhouse-0.3.1}/Cargo.toml +12 -1
  3. quickhouse-0.3.1/PKG-INFO +227 -0
  4. quickhouse-0.3.1/README.md +196 -0
  5. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/Cargo.toml +4 -0
  6. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/config.rs +130 -4
  7. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/ddl.rs +1 -0
  8. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/decode.rs +36 -10
  9. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/decode_bigquery.rs +16 -4
  10. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/decode_mysql.rs +16 -5
  11. quickhouse-0.3.1/crates/quickhouse-core/src/error.rs +208 -0
  12. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/lib.rs +11 -9
  13. quickhouse-0.3.1/crates/quickhouse-core/src/sink/bigquery.rs +1144 -0
  14. quickhouse-0.3.1/crates/quickhouse-core/src/sink/bigquery_proto.rs +388 -0
  15. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/sink/clickhouse.rs +58 -28
  16. quickhouse-0.3.1/crates/quickhouse-core/src/sink/mod.rs +177 -0
  17. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/source/bigquery.rs +6 -2
  18. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/source/mysql.rs +6 -2
  19. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/source/postgres.rs +10 -5
  20. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/sync.rs +327 -99
  21. quickhouse-0.3.1/crates/quickhouse-core/src/transform.rs +314 -0
  22. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/types.rs +106 -1
  23. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-py/src/lib.rs +95 -17
  24. {quickhouse-0.2.4 → quickhouse-0.3.1}/pyproject.toml +1 -1
  25. {quickhouse-0.2.4 → quickhouse-0.3.1}/python/quickhouse/__init__.py +14 -6
  26. {quickhouse-0.2.4 → quickhouse-0.3.1}/python/quickhouse/_quickhouse.pyi +69 -10
  27. quickhouse-0.2.4/PKG-INFO +0 -289
  28. quickhouse-0.2.4/README.md +0 -258
  29. quickhouse-0.2.4/crates/quickhouse-core/src/error.rs +0 -90
  30. quickhouse-0.2.4/crates/quickhouse-core/src/sink/mod.rs +0 -3
  31. quickhouse-0.2.4/crates/quickhouse-core/src/transform.rs +0 -177
  32. {quickhouse-0.2.4 → quickhouse-0.3.1}/LICENSE +0 -0
  33. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/memory.rs +0 -0
  34. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-core/src/source/mod.rs +0 -0
  35. {quickhouse-0.2.4 → quickhouse-0.3.1}/crates/quickhouse-py/Cargo.toml +0 -0
  36. {quickhouse-0.2.4 → quickhouse-0.3.1}/python/quickhouse/progress.py +0 -0
  37. {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.1",
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.3"
1238
+ version = "0.3.4"
1239
1239
  source = "registry+https://github.com/rust-lang/crates.io-index"
1240
- checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
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.10.1"
1471
+ version = "1.11.0"
1472
1472
  source = "registry+https://github.com/rust-lang/crates.io-index"
1473
- checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
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.186"
1844
+ version = "0.2.189"
1845
1845
  source = "registry+https://github.com/rust-lang/crates.io-index"
1846
- checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
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.2.4"
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.2.4"
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.1",
2980
+ "syn 3.0.3",
2977
2981
  ]
2978
2982
 
2979
2983
  [[package]]
2980
2984
  name = "serde_json"
2981
- version = "1.0.150"
2985
+ version = "1.0.151"
2982
2986
  source = "registry+https://github.com/rust-lang/crates.io-index"
2983
- checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
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.1"
3203
+ version = "3.0.3"
3200
3204
  source = "registry+https://github.com/rust-lang/crates.io-index"
3201
- checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f"
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.1",
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.53"
3304
+ version = "0.3.54"
3301
3305
  source = "registry+https://github.com/rust-lang/crates.io-index"
3302
- checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
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.31"
3324
+ version = "0.2.32"
3321
3325
  source = "registry+https://github.com/rust-lang/crates.io-index"
3322
- checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
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.0"
3389
+ version = "1.53.1"
3386
3390
  source = "registry+https://github.com/rust-lang/crates.io-index"
3387
- checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee"
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.18"
3477
+ version = "0.1.19"
3474
3478
  source = "registry+https://github.com/rust-lang/crates.io-index"
3475
- checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
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.18"
3488
+ version = "0.7.19"
3485
3489
  source = "registry+https://github.com/rust-lang/crates.io-index"
3486
- checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
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.54"
4217
+ version = "0.8.55"
4213
4218
  source = "registry+https://github.com/rust-lang/crates.io-index"
4214
- checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
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.54"
4226
+ version = "0.8.55"
4222
4227
  source = "registry+https://github.com/rust-lang/crates.io-index"
4223
- checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
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.2.4"
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