|
| 1 | +# name: test/sql/integration/table_in_out/multi_batch_finalize.test |
| 2 | +# description: A streaming table-in-out FINALIZE that emits MANY batches. |
| 3 | +# |
| 4 | +# Every other finalize fixture in every SDK returns exactly ONE batch, and one |
| 5 | +# batch is the easy case: over HTTP a producer is strictly lock-step (one batch |
| 6 | +# per response), so a single-batch flush completes inside its one turn and never |
| 7 | +# needs a continuation token. A flush of TWO OR MORE batches does — and in |
| 8 | +# vgi-rust that path was a hard error instead of a pagination, which nothing |
| 9 | +# noticed for as long as no fixture emitted a second batch. |
| 10 | +# |
| 11 | +# `multi_batch_finish` emits one batch per input row the substream saw: `rows` |
| 12 | +# batches, the first carrying that substream's `total` and the rest carrying 0. |
| 13 | +# Splitting it that way makes the two failure modes tell themselves apart: |
| 14 | +# |
| 15 | +# * SUM(n) wrong -> a batch's CONTENTS were lost, duplicated or reordered. |
| 16 | +# * COUNT(*) wrong -> a BATCH was lost or repeated. This is the one that |
| 17 | +# catches a broken continuation, because a flush truncated after its first |
| 18 | +# batch still SUMS correctly and only the count betrays it. |
| 19 | +# |
| 20 | +# Both invariants hold however DuckDB fans the input across substreams (each |
| 21 | +# substream's partials sum to the whole, and its batch count equals its row |
| 22 | +# count), so nothing here assumes a thread or substream count. |
| 23 | +# group: [vgi_integration_table_in_out] |
| 24 | + |
| 25 | +require-env VGI_TEST_WORKER |
| 26 | + |
| 27 | +require vgi |
| 28 | + |
| 29 | +# HTTP transport ATTACH needs httpfs loaded (else a "VGI HTTP transport requires |
| 30 | +# the httpfs extension" error — containing "HTTP" — makes the harness silently |
| 31 | +# skip the whole file). Harmless on subprocess. |
| 32 | +require httpfs |
| 33 | + |
| 34 | +statement ok |
| 35 | +ATTACH 'example' AS ex (TYPE vgi, LOCATION '${VGI_TEST_WORKER}'); |
| 36 | + |
| 37 | +# --------------------------------------------------------------------------- |
| 38 | +# Single substream, many batches — the case the HTTP continuation must serve. |
| 39 | +# threads=1 keeps it to ONE substream, so one finalize really does flush 200 |
| 40 | +# batches rather than 200 substreams flushing one batch each. |
| 41 | +# --------------------------------------------------------------------------- |
| 42 | + |
| 43 | +statement ok |
| 44 | +SET threads=1; |
| 45 | + |
| 46 | +# 200 input rows -> 200 finalize batches from a single flush. |
| 47 | +query II |
| 48 | +SELECT COUNT(*), SUM(n) FROM ex.multi_batch_finish((SELECT i AS n FROM range(1, 201) t(i))); |
| 49 | +---- |
| 50 | +200 20100 |
| 51 | + |
| 52 | +# Two rows is the smallest input that needs a continuation at all: one batch |
| 53 | +# fits in a single HTTP turn, two do not. |
| 54 | +query II |
| 55 | +SELECT COUNT(*), SUM(n) FROM ex.multi_batch_finish((SELECT i AS n FROM range(1, 3) t(i))); |
| 56 | +---- |
| 57 | +2 3 |
| 58 | + |
| 59 | +# One row still works — the single-batch flush keeps its one-turn drain. |
| 60 | +query II |
| 61 | +SELECT COUNT(*), SUM(n) FROM ex.multi_batch_finish((SELECT i AS n FROM range(1, 2) t(i))); |
| 62 | +---- |
| 63 | +1 1 |
| 64 | + |
| 65 | +# Empty input: no rows accumulated, so no batches to flush. |
| 66 | +query I |
| 67 | +SELECT COUNT(*) FROM ex.multi_batch_finish((SELECT i AS n FROM range(1, 1) t(i))); |
| 68 | +---- |
| 69 | +0 |
| 70 | + |
| 71 | +# --------------------------------------------------------------------------- |
| 72 | +# Parallel — several substreams, each flushing several batches. The invariants |
| 73 | +# are per-substream sums and counts, so they hold at any fan-out. |
| 74 | +# --------------------------------------------------------------------------- |
| 75 | + |
| 76 | +statement ok |
| 77 | +SET threads=8; |
| 78 | + |
| 79 | +statement ok |
| 80 | +CREATE TABLE big AS SELECT i AS n FROM range(1, 5001) t(i); |
| 81 | + |
| 82 | +# sum(1..5000) = 12502500, and one output row per input row. |
| 83 | +query II |
| 84 | +SELECT COUNT(*), SUM(n) FROM ex.multi_batch_finish((SELECT n FROM big)); |
| 85 | +---- |
| 86 | +5000 12502500 |
| 87 | + |
| 88 | +# UNION ALL: each branch is its own substream, so this is several independent |
| 89 | +# multi-batch flushes unioned together. |
| 90 | +query II |
| 91 | +SELECT COUNT(*), SUM(n) FROM ( |
| 92 | + SELECT n FROM ex.multi_batch_finish((SELECT n FROM big)) |
| 93 | + UNION ALL |
| 94 | + SELECT n FROM ex.multi_batch_finish((SELECT n FROM big)) |
| 95 | +); |
| 96 | +---- |
| 97 | +10000 25005000 |
| 98 | + |
| 99 | +# pool false → a fresh worker per substream acquire, so a flush that depended on |
| 100 | +# state left behind on a shared worker would show up here. |
| 101 | +statement ok |
| 102 | +ATTACH 'example' AS ex2 (TYPE vgi, LOCATION '${VGI_TEST_WORKER}', pool false); |
| 103 | + |
| 104 | +query II |
| 105 | +SELECT COUNT(*), SUM(n) FROM ex2.multi_batch_finish((SELECT n FROM big)); |
| 106 | +---- |
| 107 | +5000 12502500 |
0 commit comments