quickhouse 0.2.4__tar.gz → 0.3.0__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.0}/Cargo.lock +10 -9
- {quickhouse-0.2.4 → quickhouse-0.3.0}/Cargo.toml +4 -1
- {quickhouse-0.2.4 → quickhouse-0.3.0}/PKG-INFO +155 -46
- quickhouse-0.3.0/README.md +367 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/Cargo.toml +1 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/config.rs +113 -4
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/ddl.rs +1 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/decode.rs +36 -10
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/decode_bigquery.rs +16 -4
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/decode_mysql.rs +16 -5
- quickhouse-0.3.0/crates/quickhouse-core/src/error.rs +208 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/lib.rs +10 -9
- quickhouse-0.3.0/crates/quickhouse-core/src/sink/bigquery.rs +928 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/sink/clickhouse.rs +58 -28
- quickhouse-0.3.0/crates/quickhouse-core/src/sink/mod.rs +175 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/source/bigquery.rs +6 -2
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/source/mysql.rs +6 -2
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/source/postgres.rs +10 -5
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/sync.rs +327 -99
- quickhouse-0.3.0/crates/quickhouse-core/src/transform.rs +314 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/types.rs +106 -1
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-py/src/lib.rs +71 -17
- {quickhouse-0.2.4 → quickhouse-0.3.0}/pyproject.toml +1 -1
- {quickhouse-0.2.4 → quickhouse-0.3.0}/python/quickhouse/__init__.py +14 -6
- {quickhouse-0.2.4 → quickhouse-0.3.0}/python/quickhouse/_quickhouse.pyi +62 -10
- 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.0}/LICENSE +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/memory.rs +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/source/mod.rs +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-py/Cargo.toml +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/python/quickhouse/progress.py +0 -0
- {quickhouse-0.2.4 → quickhouse-0.3.0}/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.2",
|
|
335
335
|
]
|
|
336
336
|
|
|
337
337
|
[[package]]
|
|
@@ -2506,13 +2506,14 @@ dependencies = [
|
|
|
2506
2506
|
|
|
2507
2507
|
[[package]]
|
|
2508
2508
|
name = "quickhouse-core"
|
|
2509
|
-
version = "0.
|
|
2509
|
+
version = "0.3.0"
|
|
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",
|
|
@@ -2536,7 +2537,7 @@ dependencies = [
|
|
|
2536
2537
|
|
|
2537
2538
|
[[package]]
|
|
2538
2539
|
name = "quickhouse-py"
|
|
2539
|
-
version = "0.
|
|
2540
|
+
version = "0.3.0"
|
|
2540
2541
|
dependencies = [
|
|
2541
2542
|
"pyo3",
|
|
2542
2543
|
"quickhouse-core",
|
|
@@ -2973,14 +2974,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
|
2973
2974
|
dependencies = [
|
|
2974
2975
|
"proc-macro2",
|
|
2975
2976
|
"quote",
|
|
2976
|
-
"syn 3.0.
|
|
2977
|
+
"syn 3.0.2",
|
|
2977
2978
|
]
|
|
2978
2979
|
|
|
2979
2980
|
[[package]]
|
|
2980
2981
|
name = "serde_json"
|
|
2981
|
-
version = "1.0.
|
|
2982
|
+
version = "1.0.151"
|
|
2982
2983
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2983
|
-
checksum = "
|
|
2984
|
+
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
|
2984
2985
|
dependencies = [
|
|
2985
2986
|
"itoa",
|
|
2986
2987
|
"memchr",
|
|
@@ -3196,9 +3197,9 @@ dependencies = [
|
|
|
3196
3197
|
|
|
3197
3198
|
[[package]]
|
|
3198
3199
|
name = "syn"
|
|
3199
|
-
version = "3.0.
|
|
3200
|
+
version = "3.0.2"
|
|
3200
3201
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3201
|
-
checksum = "
|
|
3202
|
+
checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3"
|
|
3202
3203
|
dependencies = [
|
|
3203
3204
|
"proc-macro2",
|
|
3204
3205
|
"quote",
|
|
@@ -3283,7 +3284,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
|
|
|
3283
3284
|
dependencies = [
|
|
3284
3285
|
"proc-macro2",
|
|
3285
3286
|
"quote",
|
|
3286
|
-
"syn 3.0.
|
|
3287
|
+
"syn 3.0.2",
|
|
3287
3288
|
]
|
|
3288
3289
|
|
|
3289
3290
|
[[package]]
|
|
@@ -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.0"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
license = "MIT"
|
|
9
9
|
authors = ["quickhouse contributors"]
|
|
@@ -39,6 +39,9 @@ mysql_async = { version = "0.34", default-features = false, features = ["default
|
|
|
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
41
|
time = { version = "0.3", features = ["macros"] }
|
|
42
|
+
# Only used to encode Binary columns as BigQuery's BYTES JSON representation
|
|
43
|
+
# (base64 text) for the insertAll destination path.
|
|
44
|
+
base64 = "0.22"
|
|
42
45
|
|
|
43
46
|
# Faster release builds for the shipped wheel.
|
|
44
47
|
[profile.release]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickhouse
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -30,23 +30,24 @@ Project-URL: Repository, https://github.com/mmirzafahmi/quickhouse
|
|
|
30
30
|
|
|
31
31
|
# quickhouse
|
|
32
32
|
|
|
33
|
-
Fast **PostgreSQL / MySQL / BigQuery → ClickHouse** ETL: a native
|
|
34
|
-
driven from a small, typed **Python** API.
|
|
33
|
+
Fast **PostgreSQL / MySQL / BigQuery → ClickHouse or BigQuery** ETL: a native
|
|
34
|
+
**Rust engine** driven from a small, typed **Python** API.
|
|
35
35
|
|
|
36
36
|
The hot path never materializes Python objects — each source's native wire
|
|
37
|
-
protocol flows straight into Apache Arrow and out to
|
|
38
|
-
`FORMAT ArrowStream`,
|
|
39
|
-
streaming
|
|
40
|
-
|
|
37
|
+
protocol flows straight into Apache Arrow and out to the destination's own
|
|
38
|
+
native ingestion path (ClickHouse's `FORMAT ArrowStream`, or BigQuery's
|
|
39
|
+
`insertAll` streaming insert), with parallel range-partitioned reads,
|
|
40
|
+
bounded-memory streaming, automatic table creation, and both full-refresh and
|
|
41
|
+
incremental (watermark) sync modes.
|
|
41
42
|
|
|
42
43
|
```python
|
|
43
44
|
import quickhouse
|
|
44
45
|
|
|
45
|
-
src = quickhouse.Postgres("postgresql://user:pw@localhost:5432/
|
|
46
|
+
src = quickhouse.Postgres("postgresql://user:pw@localhost:5432/shop")
|
|
46
47
|
dst = quickhouse.ClickHouse("http://localhost:8123", database="analytics")
|
|
47
48
|
|
|
48
|
-
result = quickhouse.sync(src, dst, dest_table="
|
|
49
|
-
source_table="
|
|
49
|
+
result = quickhouse.sync(src, dst, dest_table="orders",
|
|
50
|
+
source_table="orders", key=["id"])
|
|
50
51
|
print(result) # rows_read, rows_written, bytes_written, duration_secs, new_watermark
|
|
51
52
|
```
|
|
52
53
|
|
|
@@ -54,6 +55,10 @@ print(result) # rows_read, rows_written, bytes_written, duration_secs, new_wat
|
|
|
54
55
|
|
|
55
56
|
- **Three sources, one API.** PostgreSQL, MySQL, and Google BigQuery — swap the
|
|
56
57
|
source object, and everything else about `sync()` is identical.
|
|
58
|
+
- **Two destinations, one API.** ClickHouse or BigQuery — swap the destination
|
|
59
|
+
object (`quickhouse.ClickHouse(...)` or `quickhouse.BigQuery(..., dataset_id=...)`);
|
|
60
|
+
DDL, inserts, the atomic swap, and incremental watermark state all adapt
|
|
61
|
+
automatically to whichever you pick.
|
|
57
62
|
- **Fast by construction.** Source rows are decoded straight into Arrow in Rust
|
|
58
63
|
(no per-row Python), tables are split into key ranges read in parallel, and
|
|
59
64
|
decoding overlaps uploading — each finished batch's insert is spawned as a
|
|
@@ -63,20 +68,29 @@ print(result) # rows_read, rows_written, bytes_written, duration_secs, new_wat
|
|
|
63
68
|
against each batch's real Arrow allocation. When it's reached, decoding blocks
|
|
64
69
|
(backpressure) — so peak RSS stays flat regardless of `parallelism`, row
|
|
65
70
|
width, or partition skew.
|
|
66
|
-
- **Streaming
|
|
67
|
-
`ArrowStream
|
|
68
|
-
body is produced incrementally, never buffered in full.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- **
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
- **Streaming inserts.** ClickHouse: Arrow IPC ingested by its native
|
|
72
|
+
`ArrowStream` format, compressed on the fly with zstd (default), gzip, or
|
|
73
|
+
none — the body is produced incrementally, never buffered in full. BigQuery:
|
|
74
|
+
rows streamed via `tabledata.insertAll`, chunked to stay under its
|
|
75
|
+
per-request limits.
|
|
76
|
+
- **Atomic full refresh.** Loads into a staging table, then swaps it into place
|
|
77
|
+
atomically — ClickHouse's `EXCHANGE TABLES`, or a BigQuery `WRITE_TRUNCATE`
|
|
78
|
+
copy job (no query cost, unlike `CREATE OR REPLACE TABLE ... AS SELECT`).
|
|
79
|
+
A crash mid-run never leaves the destination partial.
|
|
80
|
+
- **Idempotent incremental.** Tracks a watermark in an internal state table in
|
|
81
|
+
the destination and copies only new rows. Updated rows (same key, newer
|
|
82
|
+
watermark) dedupe via ClickHouse's `ReplacingMergeTree`, or — since BigQuery
|
|
83
|
+
has no engine-level equivalent — a `MERGE` upsert keyed on `key` (required
|
|
84
|
+
for BigQuery incremental mode). Re-running with no new data is a no-op.
|
|
74
85
|
- **Resilient.** Transient insert failures (dropped connections, timeouts, HTTP
|
|
75
|
-
5xx/429) retry with exponential backoff (up to 4 attempts)
|
|
76
|
-
errors (bad SQL, auth) fail fast. Messy
|
|
77
|
-
dates — is coerced rather than
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
5xx/429) retry with exponential backoff (up to 4 attempts) for either
|
|
87
|
+
destination; deterministic 4xx errors (bad SQL, auth) fail fast. Messy
|
|
88
|
+
legacy data — zero-dates, out-of-range dates — is coerced rather than
|
|
89
|
+
crashing the run.
|
|
90
|
+
- **Automatic DDL.** Generates each destination's own schema from the source's
|
|
91
|
+
— a SQL `CREATE TABLE` for ClickHouse, a structured `Table`/schema object for
|
|
92
|
+
BigQuery (partitioning + clustering from `partition_by`/`order_by`/`key`) —
|
|
93
|
+
with per-column type overrides, renames, and include/exclude lists.
|
|
80
94
|
- **GIL-free.** The whole transfer runs inside `Python::allow_threads`; the GIL
|
|
81
95
|
is only re-acquired to fire your `on_progress` callback.
|
|
82
96
|
|
|
@@ -99,22 +113,22 @@ Rust toolchain and maturin — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
99
113
|
```python
|
|
100
114
|
import quickhouse as qh
|
|
101
115
|
|
|
102
|
-
src = qh.Postgres("postgresql://user:pw@localhost:5432/
|
|
103
|
-
# or: src = qh.MySQL("mysql://user:pw@localhost:3306/
|
|
116
|
+
src = qh.Postgres("postgresql://user:pw@localhost:5432/shop")
|
|
117
|
+
# or: src = qh.MySQL("mysql://user:pw@localhost:3306/shop")
|
|
104
118
|
# or: src = qh.BigQuery("my-gcp-project") # source_table="dataset.table"
|
|
105
119
|
dst = qh.ClickHouse("http://localhost:8123", database="analytics")
|
|
106
120
|
|
|
107
121
|
result = qh.sync(
|
|
108
122
|
src, dst,
|
|
109
|
-
dest_table="
|
|
110
|
-
source_table="
|
|
123
|
+
dest_table="orders",
|
|
124
|
+
source_table="orders",
|
|
111
125
|
mode="incremental", # or "full"
|
|
112
|
-
watermark="
|
|
126
|
+
watermark="updated_at", # required for incremental
|
|
113
127
|
key=["id"], # ORDER BY / dedup key
|
|
114
128
|
create_if_missing=True, # auto-generate the ClickHouse table
|
|
115
129
|
parallelism=8,
|
|
116
130
|
batch_rows=100_000,
|
|
117
|
-
exclude=["
|
|
131
|
+
exclude=["internal_notes"],
|
|
118
132
|
rename={"amount": "amt"},
|
|
119
133
|
type_overrides={"amt": "Decimal(18, 2)"},
|
|
120
134
|
on_progress=lambda p: print(f"{p.rows_written:,} rows @ {p.rows_per_sec:,.0f}/s"),
|
|
@@ -135,14 +149,59 @@ qh.BigQuery("my-gcp-project", credentials_file=None) # None → Application De
|
|
|
135
149
|
Read a whole table with `source_table=...`, or a custom `SELECT` with
|
|
136
150
|
`source_query=...` (exactly one is required).
|
|
137
151
|
|
|
152
|
+
### Choosing a destination
|
|
153
|
+
|
|
154
|
+
`sync()`'s second argument accepts either:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
qh.ClickHouse("http://host:8123", database="analytics", user="default", password="", compression="zstd")
|
|
158
|
+
qh.BigQuery("my-gcp-project", dataset_id="analytics", credentials_file=None)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`dest_table` is a bare table name within `database` (ClickHouse) or
|
|
162
|
+
`dataset_id` (BigQuery). `engine`/`order_by`/`partition_by`/`primary_key`/`key`
|
|
163
|
+
are interpreted per destination:
|
|
164
|
+
|
|
165
|
+
| Config field | ClickHouse | BigQuery |
|
|
166
|
+
| --- | --- | --- |
|
|
167
|
+
| `engine` | `MergeTree`/`ReplacingMergeTree`-family (default per mode) | ignored — no engine concept |
|
|
168
|
+
| `partition_by` | a `PARTITION BY` SQL expression, e.g. `"toYYYYMM(date)"` | a bare `DATE`/`DATETIME`/`TIMESTAMP` column name (not an expression) |
|
|
169
|
+
| `order_by` / `key` | `ORDER BY` columns (falls back to `key`) | combined into `Clustering` columns, at most 4 total |
|
|
170
|
+
| `key` (incremental mode) | optional — dedup key for `ReplacingMergeTree` | **required** — the `MERGE` match key (see "Sync modes" below) |
|
|
171
|
+
|
|
138
172
|
### Sync modes
|
|
139
173
|
|
|
140
|
-
- **`full`** — loads into a staging table, then
|
|
141
|
-
|
|
142
|
-
-
|
|
143
|
-
|
|
144
|
-
the
|
|
145
|
-
|
|
174
|
+
- **`full`** — loads into a staging table, then swaps it into place atomically:
|
|
175
|
+
ClickHouse's `EXCHANGE TABLES`, or a BigQuery `WRITE_TRUNCATE` copy job. A
|
|
176
|
+
crash mid-run never leaves the destination empty/partial.
|
|
177
|
+
- **`incremental`** — reads the last watermark from an internal state table in
|
|
178
|
+
the destination (`_quickhouse_state`), copies only rows past it (snapshotting
|
|
179
|
+
the current max up front for consistency). Dedup on an *updated* row (same
|
|
180
|
+
key, newer watermark) differs by destination:
|
|
181
|
+
- **ClickHouse** — `ReplacingMergeTree(<watermark>)` dedupes lazily at
|
|
182
|
+
background merge time; reads need `FINAL` to guarantee only the latest
|
|
183
|
+
row immediately.
|
|
184
|
+
- **BigQuery** — has no engine-level dedup, so writes go into a staging
|
|
185
|
+
table first, then a `MERGE` statement matched on `key` upserts them into
|
|
186
|
+
the destination (`key` is therefore **required** here, unlike everywhere
|
|
187
|
+
else it's optional). This bills for bytes scanned in both tables, unlike
|
|
188
|
+
the free `insertAll` path used for full-refresh — but is naturally
|
|
189
|
+
idempotent, so a crashed/retried incremental run re-applies the same
|
|
190
|
+
key-matched rows rather than duplicating them.
|
|
191
|
+
|
|
192
|
+
Re-running with no new data is a no-op either way.
|
|
193
|
+
|
|
194
|
+
**Lookback.** Pass `lookback_seconds=N` to widen the tracked watermark's
|
|
195
|
+
lower bound by `N` seconds before filtering, so a run re-includes a
|
|
196
|
+
trailing window of already-synced rows — catches late-arriving/edited rows
|
|
197
|
+
that don't monotonically bump the watermark (e.g. `lookback_seconds=3 * 86400`
|
|
198
|
+
on a daily sync to safely reprocess the last 3 days). Relies on the
|
|
199
|
+
upsert/dedup above to replace the overlap rather than duplicate it, so it
|
|
200
|
+
requires `key` or `order_by` to be set, plus a `watermark` column that
|
|
201
|
+
resolves to a date or timestamp type. For a BigQuery source, a sub-day
|
|
202
|
+
`lookback_seconds` against a `DATE`-typed watermark rounds *up* to a whole
|
|
203
|
+
day (no sub-day granularity there). Default `0` disables lookback
|
|
204
|
+
(byte-identical to the plain watermark filter).
|
|
146
205
|
|
|
147
206
|
### Progress reporting
|
|
148
207
|
|
|
@@ -176,23 +235,43 @@ RUST_LOG=quickhouse_core=debug python my_script.py # + actual SQL/DDL text
|
|
|
176
235
|
RUST_LOG=debug python my_script.py # everything, incl. deps
|
|
177
236
|
```
|
|
178
237
|
|
|
238
|
+
### Errors
|
|
239
|
+
|
|
240
|
+
Every failure raises a plain Python `RuntimeError` whose message is built to be
|
|
241
|
+
actionable on its own, without needing to scroll back through stderr logs:
|
|
242
|
+
|
|
243
|
+
- **Every error names the table it happened to** — `sync()` is often called in
|
|
244
|
+
a loop over many tables (see the quickstart), so the message is always
|
|
245
|
+
prefixed with `"<source> -> <dest_table>: ..."`.
|
|
246
|
+
- **Bad config or a data-shape problem says exactly what's wrong**, e.g. an
|
|
247
|
+
incremental `watermark` column that doesn't exist in the source names the
|
|
248
|
+
columns that do; a source column type with no ClickHouse mapping (an array,
|
|
249
|
+
a geometry type, BigQuery `RECORD`) names the real engine, the real type, and
|
|
250
|
+
suggests a fix — `exclude=[...]` the column, or cast it in a `source_query`.
|
|
251
|
+
- **A message starting with "internal error"** means the failure couldn't have
|
|
252
|
+
been caused by your data or config — please file an issue with the message.
|
|
253
|
+
- Everything else (connection failures, a rejected `INSERT`, etc.) surfaces the
|
|
254
|
+
underlying database's own error text — e.g. Postgres's SQLSTATE code and
|
|
255
|
+
message, or ClickHouse's exception code — rather than a generic wrapper.
|
|
256
|
+
|
|
179
257
|
### `sync()` parameters
|
|
180
258
|
|
|
181
259
|
| Parameter | Meaning |
|
|
182
260
|
| --- | --- |
|
|
183
261
|
| `source_table` / `source_query` | Read a whole table, or a custom `SELECT` (one is required) |
|
|
184
|
-
| `dest_table` |
|
|
262
|
+
| `dest_table` | Bare destination table name (within `database` or `dataset_id`) |
|
|
185
263
|
| `mode` | `"full"` or `"incremental"` |
|
|
186
|
-
| `watermark` | Monotonic column (e.g. `
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
264
|
+
| `watermark` | Monotonic column (e.g. `updated_at`, `id`) — required for incremental; ignored (cleared to `None`) in full mode |
|
|
265
|
+
| `lookback_seconds` | Widen the watermark's lower bound by this many seconds to re-sync a trailing overlap window (see "Sync modes"); `0` disables (default). Requires `key`/`order_by` and a date/timestamp `watermark` |
|
|
266
|
+
| `key` | Business/dedup key → ClickHouse `ORDER BY` fallback + `ReplacingMergeTree` dedup; BigQuery clustering + the `MERGE` match key (**required** for BigQuery incremental mode — see "Choosing a destination") |
|
|
267
|
+
| `create_if_missing` | Auto-create the destination table when absent |
|
|
268
|
+
| `engine`, `order_by`, `partition_by`, `primary_key` | DDL knobs, interpreted per destination — see "Choosing a destination" |
|
|
190
269
|
| `parallelism` | Number of concurrent partition streams |
|
|
191
270
|
| `batch_rows` | Max rows per Arrow batch / insert — a per-batch granularity knob, **not** the memory ceiling |
|
|
192
271
|
| `batch_bytes` | Also cap each batch at this many estimated source bytes (default 4 MiB); `0` disables |
|
|
193
272
|
| `max_memory_bytes` | **The memory ceiling.** Hard cap on total in-flight Arrow memory across all partitions and uploads; decoding blocks when reached. Default 512 MiB; `0` = unbounded |
|
|
194
273
|
| `partition_column` | Integer column to range-split on (defaults to first `key`) |
|
|
195
|
-
| `type_overrides` | Per-column
|
|
274
|
+
| `type_overrides` | Per-column destination type (e.g. ClickHouse `{"qty": "Decimal(18, 3)"}`, BigQuery `{"qty": "NUMERIC"}`) |
|
|
196
275
|
| `rename` | Source → destination column renames |
|
|
197
276
|
| `include` / `exclude` | Column allow/deny lists |
|
|
198
277
|
| `on_progress` | Callback receiving a `Progress` (`rows_written`, `rows_per_sec`, …) |
|
|
@@ -259,6 +338,27 @@ even for `source_query`.
|
|
|
259
338
|
`RECORD`/`STRUCT` and repeated (`ARRAY`) fields aren't supported in v1 — same
|
|
260
339
|
scalar-only scope as the Postgres/MySQL sources.
|
|
261
340
|
|
|
341
|
+
### BigQuery (as a destination)
|
|
342
|
+
|
|
343
|
+
Any source's Arrow output maps to BigQuery's own column types — not just what
|
|
344
|
+
BigQuery-as-a-source itself produces, since any of the three sources can now
|
|
345
|
+
feed a BigQuery destination:
|
|
346
|
+
|
|
347
|
+
| Arrow | BigQuery |
|
|
348
|
+
| --- | --- |
|
|
349
|
+
| `Int8..64` / `UInt8..64` | `INTEGER` (signed 64-bit — a `UInt64` value above `i64::MAX` would overflow; not handled in v1) |
|
|
350
|
+
| `Float32/64` | `FLOAT` |
|
|
351
|
+
| `Boolean` | `BOOLEAN` |
|
|
352
|
+
| `Utf8` | `STRING` |
|
|
353
|
+
| `Binary` | `BYTES` (base64-encoded in the `insertAll` JSON payload) |
|
|
354
|
+
| `Date32` | `DATE` |
|
|
355
|
+
| `Timestamp(µs)` (no tz) | `DATETIME` |
|
|
356
|
+
| `Timestamp(µs, tz)` | `TIMESTAMP` |
|
|
357
|
+
|
|
358
|
+
BigQuery's `DATE`/`DATETIME`/`TIMESTAMP` range (0001-01-01 to 9999-12-31) is far
|
|
359
|
+
wider than Arrow's, so — unlike the ClickHouse destination — no out-of-range
|
|
360
|
+
coercion is needed here.
|
|
361
|
+
|
|
262
362
|
## Limitations / roadmap (v1)
|
|
263
363
|
|
|
264
364
|
- TLS uses rustls, trusting the public CA roots plus, optionally, an extra CA
|
|
@@ -269,11 +369,20 @@ scalar-only scope as the Postgres/MySQL sources.
|
|
|
269
369
|
auth isn't supported yet.
|
|
270
370
|
- Array and composite (`RECORD`/`STRUCT`) types aren't supported; extend
|
|
271
371
|
`types.rs` + the relevant `decode*.rs`.
|
|
272
|
-
- **BigQuery parallelism**: `parallelism` is passed as a
|
|
273
|
-
(server-side parallel preparation), but rows are consumed
|
|
274
|
-
connection rather than fanned out across concurrent local tasks.
|
|
275
|
-
`SELECT` statements are supported for `source_query` (not
|
|
276
|
-
scripts).
|
|
372
|
+
- **BigQuery parallelism (as a source)**: `parallelism` is passed as a
|
|
373
|
+
stream-count hint (server-side parallel preparation), but rows are consumed
|
|
374
|
+
on a single connection rather than fanned out across concurrent local tasks.
|
|
375
|
+
Only single `SELECT` statements are supported for `source_query` (not
|
|
376
|
+
multi-statement scripts).
|
|
377
|
+
- **BigQuery write path (as a destination)**: v1 uses the `tabledata.insertAll`
|
|
378
|
+
streaming-insert API — official and fully supported, but not BigQuery's
|
|
379
|
+
newest/highest-throughput option. The **Storage Write API** (free, higher
|
|
380
|
+
throughput, exactly-once capable) is a natural future enhancement; it needs a
|
|
381
|
+
from-scratch dynamic protobuf encoder (no crate support for runtime-built
|
|
382
|
+
schemas), which is why it isn't v1. Load jobs were considered too, but this
|
|
383
|
+
project's BigQuery crate only supports them via a Cloud Storage staging file
|
|
384
|
+
— a hard GCS dependency and bucket requirement this destination doesn't
|
|
385
|
+
otherwise need.
|
|
277
386
|
- No CLI yet — a config-driven CLI over the same engine is planned.
|
|
278
387
|
- Logical-replication CDC and arbitrary transform callbacks are future work.
|
|
279
388
|
|