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.
Files changed (35) hide show
  1. {quickhouse-0.2.4 → quickhouse-0.3.0}/Cargo.lock +10 -9
  2. {quickhouse-0.2.4 → quickhouse-0.3.0}/Cargo.toml +4 -1
  3. {quickhouse-0.2.4 → quickhouse-0.3.0}/PKG-INFO +155 -46
  4. quickhouse-0.3.0/README.md +367 -0
  5. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/Cargo.toml +1 -0
  6. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/config.rs +113 -4
  7. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/ddl.rs +1 -0
  8. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/decode.rs +36 -10
  9. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/decode_bigquery.rs +16 -4
  10. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/decode_mysql.rs +16 -5
  11. quickhouse-0.3.0/crates/quickhouse-core/src/error.rs +208 -0
  12. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/lib.rs +10 -9
  13. quickhouse-0.3.0/crates/quickhouse-core/src/sink/bigquery.rs +928 -0
  14. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/sink/clickhouse.rs +58 -28
  15. quickhouse-0.3.0/crates/quickhouse-core/src/sink/mod.rs +175 -0
  16. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/source/bigquery.rs +6 -2
  17. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/source/mysql.rs +6 -2
  18. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/source/postgres.rs +10 -5
  19. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/sync.rs +327 -99
  20. quickhouse-0.3.0/crates/quickhouse-core/src/transform.rs +314 -0
  21. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/types.rs +106 -1
  22. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-py/src/lib.rs +71 -17
  23. {quickhouse-0.2.4 → quickhouse-0.3.0}/pyproject.toml +1 -1
  24. {quickhouse-0.2.4 → quickhouse-0.3.0}/python/quickhouse/__init__.py +14 -6
  25. {quickhouse-0.2.4 → quickhouse-0.3.0}/python/quickhouse/_quickhouse.pyi +62 -10
  26. quickhouse-0.2.4/README.md +0 -258
  27. quickhouse-0.2.4/crates/quickhouse-core/src/error.rs +0 -90
  28. quickhouse-0.2.4/crates/quickhouse-core/src/sink/mod.rs +0 -3
  29. quickhouse-0.2.4/crates/quickhouse-core/src/transform.rs +0 -177
  30. {quickhouse-0.2.4 → quickhouse-0.3.0}/LICENSE +0 -0
  31. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/memory.rs +0 -0
  32. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-core/src/source/mod.rs +0 -0
  33. {quickhouse-0.2.4 → quickhouse-0.3.0}/crates/quickhouse-py/Cargo.toml +0 -0
  34. {quickhouse-0.2.4 → quickhouse-0.3.0}/python/quickhouse/progress.py +0 -0
  35. {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.1",
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.2.4"
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.2.4"
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.1",
2977
+ "syn 3.0.2",
2977
2978
  ]
2978
2979
 
2979
2980
  [[package]]
2980
2981
  name = "serde_json"
2981
- version = "1.0.150"
2982
+ version = "1.0.151"
2982
2983
  source = "registry+https://github.com/rust-lang/crates.io-index"
2983
- checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
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.1"
3200
+ version = "3.0.2"
3200
3201
  source = "registry+https://github.com/rust-lang/crates.io-index"
3201
- checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f"
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.1",
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.2.4"
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.2.4
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 **Rust engine**
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 ClickHouse's
38
- `FORMAT ArrowStream`, with parallel range-partitioned reads, bounded-memory
39
- streaming, automatic table creation, and both full-refresh and incremental
40
- (watermark) sync modes.
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/odoo")
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="account_move_line",
49
- source_table="account_move_line", key=["id"])
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 compressed inserts.** Arrow IPC ingested by ClickHouse's native
67
- `ArrowStream`, compressed on the fly with zstd (default), gzip, or none — the
68
- body is produced incrementally, never buffered in full.
69
- - **Atomic full refresh.** Loads into a staging table, then `EXCHANGE TABLES` to
70
- swap it into place; a crash mid-run never leaves the destination partial.
71
- - **Idempotent incremental.** Tracks a watermark in an internal ClickHouse state
72
- table, copies only new rows, and dedupes via `ReplacingMergeTree`. Re-running
73
- with no new data is a no-op.
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); deterministic 4xx
76
- errors (bad SQL, auth) fail fast. Messy legacy data — zero-dates, out-of-range
77
- dates — is coerced rather than crashing the run.
78
- - **Automatic DDL.** Generates the ClickHouse `CREATE TABLE` from the source
79
- schema, with per-column type overrides, renames, and include/exclude lists.
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/odoo")
103
- # or: src = qh.MySQL("mysql://user:pw@localhost:3306/odoo")
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="account_move_line",
110
- source_table="account_move_line",
123
+ dest_table="orders",
124
+ source_table="orders",
111
125
  mode="incremental", # or "full"
112
- watermark="write_date", # required for incremental
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=["display_name"],
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 `EXCHANGE TABLES` to swap it into
141
- place atomically. A crash mid-run never leaves the destination empty/partial.
142
- - **`incremental`** reads the last watermark from an internal
143
- `_quickhouse_state` table in ClickHouse, copies only rows past it (snapshotting
144
- the current max up front for consistency), and dedupes via
145
- `ReplacingMergeTree(<watermark>)`. Re-running with no new data is a no-op.
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` | Destination table in the ClickHouse database |
262
+ | `dest_table` | Bare destination table name (within `database` or `dataset_id`) |
185
263
  | `mode` | `"full"` or `"incremental"` |
186
- | `watermark` | Monotonic column (e.g. `write_date`, `id`) — required for incremental; ignored (cleared to `None`) in full mode |
187
- | `key` | Business key ClickHouse `ORDER BY` and dedup key |
188
- | `create_if_missing` | Auto-run generated `CREATE TABLE` when the destination is absent |
189
- | `engine`, `order_by`, `partition_by`, `primary_key` | DDL knobs (sensible defaults per mode) |
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 ClickHouse type (e.g. `{"qty": "Decimal(18, 3)"}`) |
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 stream-count hint
273
- (server-side parallel preparation), but rows are consumed on a single
274
- connection rather than fanned out across concurrent local tasks. Only single
275
- `SELECT` statements are supported for `source_query` (not multi-statement
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