From 26ae3f431490bbe017d342354f08c10ee4137ebc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 18 Nov 2025 13:39:03 -0500 Subject: [PATCH] Implement binary inserts with BeginInsert flow Replace the old (and commented-out) code with the new `BeginInsert` / `InsertData` / `SendInsertBlock` pattern in `clickhouse-cpp`. This greatly simplifies the code, as the most of the necessary logic for creating the insert Block object and tracking the state of the insert is now handled by the Client object. Remove the old logic for unwrapping `LowCardinality` columns and fix `column_append` to properly append to those columns. This preserves traffic over the wire, I'm told. Thanks to @slabko for working out how to make that work again. Restore the logic for inserting arrays by creating a column of the proper type, then use `column_append()` to add the items to it before appending it to the `ColumnArray`. Thanks to @slabko again for the incantation to create a column of the proper type for array items. Copy the test from the old FDW and tweak it to match the other tests. --- src/binary.cpp | 187 +++++++++-------------- src/include/binary.hh | 3 +- src/pglink.c | 1 - test/expected/binary_inserts.out | 237 +++++++++++++++++++++++++++++ test/expected/binary_inserts_1.out | 229 ++++++++++++++++++++++++++++ test/expected/result_map.txt | 14 +- test/sql/binary_inserts.sql | 86 +++++++++++ vendor/clickhouse-cpp | 2 +- 8 files changed, 638 insertions(+), 121 deletions(-) create mode 100644 test/expected/binary_inserts.out create mode 100644 test/expected/binary_inserts_1.out create mode 100644 test/sql/binary_inserts.sql diff --git a/src/binary.cpp b/src/binary.cpp index c57d2d11..dbc13424 100644 --- a/src/binary.cpp +++ b/src/binary.cpp @@ -288,98 +288,69 @@ static Oid get_corr_postgres_type(const TypeRef & type) void ch_binary_insert_state_free(void * c) { auto * state = (ch_binary_insert_state *)c; - if (state->columns) + if (state->insert_block) { - /* try to send empty block that sets proper ClickHouse state */ - if (!state->success) + /* Finish the insert to set the proper ClickHouse state */ + Client * client = (Client *)state->conn->client; + try { - try - { - Client * client = (Client *)state->conn->client; - client->Insert(state->table_name, Block()); - } - catch (const std::exception & e) - { - // just ignore, next query will fail - elog(NOTICE, "pg_clickhouse: could not send empty packet"); - } + client->EndInsert(); } - - delete (std::vector *)state->columns; + catch (const std::exception & e) + { + // just ignore, next query will fail + elog(NOTICE, "pg_clickhouse: could not finish INSERT: - %s", e.what()); + } + delete (Block *)state->insert_block; } } void ch_binary_prepare_insert(void * conn, char * query, ch_binary_insert_state * state) { - throw std::runtime_error("clickhouse_fdw: XXX ch_binary_prepare_insert not implemented"); - -// std::vector * vec = nullptr; -// Client * client = (Client *)((ch_binary_connection_t *)conn)->client; - -// try -// { -// client->PrepareInsert( -// std::string(query) + " VALUES", [&state, &vec](const Block & sample_block) { -// if (sample_block.GetColumnCount() == 0) -// return true; - -// vec = new std::vector(); - -// state->len = sample_block.GetColumnCount(); - -// #if PG_VERSION_NUM < 120000 -// state->outdesc = CreateTemplateTupleDesc(state->len, false); -// #else -// state->outdesc = CreateTemplateTupleDesc(state->len); -// #endif - -// for (size_t i = 0; i < state->len; i++) -// { -// bool error = false; -// clickhouse::ColumnRef col = sample_block[i]; - -// auto chtype = col->Type(); -// if (chtype->GetCode() == Type::LowCardinality) -// { -// chtype = col->As()->GetNestedType(); -// } - -// Oid pg_type = get_corr_postgres_type(chtype); - -// vec->push_back(clickhouse::CreateColumnByType(col->Type()->GetName())); -// const char * colname = sample_block.GetColumnName(i).c_str(); - -// /* we can't afford long jumps outside of this function */ -// PG_TRY(); -// { -// TupleDescInitEntry( -// state->outdesc, (AttrNumber)i + 1, colname, pg_type, -1, 0); -// } -// PG_CATCH(); -// { -// error = true; -// } -// PG_END_TRY(); - -// if (error) -// throw std::runtime_error("could not init tuple descriptor"); -// } - -// return true; -// }); -// } -// catch (const std::exception & e) -// { -// client->ResetConnection(); - -// if (vec != nullptr) -// delete vec; - -// elog(ERROR, "clickhouse_fdw: error while insert preparation - %s", e.what()); -// } - -// if (vec != nullptr) -// state->columns = (void *)vec; + // Start the INSERT. + Block * block; + Client * client = (Client *)((ch_binary_connection_t *)conn)->client; + try + { + block = new Block(client->BeginInsert(std::string(query) + " VALUES")); + } + catch (const std::exception & e) + { + elog(ERROR, "pg_clickhouse: could not prepare insert - %s", e.what()); + } + + // Setup the column config (or return if no columns). + state->len = block->GetColumnCount(); + if (state->len == 0) + { + delete block; + return; + } + state->outdesc = CreateTemplateTupleDesc(state->len); + + // Iterate over the list of columns returned by ClickHouse. + AttrNumber i = 0; + for (Block::Iterator bi(*block); bi.IsValid(); bi.Next()) + { + // Determine the Postgres column type. + Oid pg_type = get_corr_postgres_type(bi.Type()); + const char * colname = bi.Name().c_str(); + + PG_TRY(); + { + TupleDescInitEntry(state->outdesc, ++i, colname, pg_type, -1, 0); + } + PG_CATCH(); + { + // Clean up and re-throw. + client->ResetConnection(); + delete block; + PG_RE_THROW(); + } + PG_END_TRY(); + } + + state->insert_block = (ch_insert_block_h *) block; } static void column_append(clickhouse::ColumnRef col, Datum val, Oid valtype, bool isnull) @@ -507,14 +478,7 @@ static void column_append(clickhouse::ColumnRef col, Datum val, Oid valtype, boo col->As()->Append(s); break; case Type::Code::LowCardinality: { - // XXX Figure out proper value to create and pass to - // Append. - throw std::runtime_error( - "clickhouse_fdw: XXX unsupported column type " - + col->Type()->GetName() - ); - // auto item = ItemView{Type::String, std::string_view(s)}; - // col->As()->Append(item); + col->AsStrict>()->Append(s); break; } default: @@ -570,25 +534,19 @@ static void column_append(clickhouse::ColumnRef col, Datum val, Oid valtype, boo break; } case ANYARRAYOID: { - // auto arr = (ch_binary_array_t *)DatumGetPointer(val); - switch (col->Type()->GetCode()) { case Type::Array: { - // XXX Figure out proper value to create and pass to - // Append. - throw std::runtime_error( - "clickhouse_fdw: XXX unsupported column type " - + col->Type()->GetName() + auto arrcol = col->AsStrict(); + auto items = CreateColumnByType( + arrcol->GetType().As()->GetItemType()->GetName() ); - // auto arrcol = col->As(); - - // arrcol->OffsetsIncrease(arr->len); - // for (size_t i = 0; i < arr->len; i++) - // column_append( - // arrcol->Nested(), arr->datums[i], arr->item_type, arr->nulls[i]); + auto arr = (ch_binary_array_t *)DatumGetPointer(val); + for (size_t i = 0; i < arr->len; i++) + column_append(items, arr->datums[i], arr->item_type, arr->nulls[i]); - // break; + arrcol->AppendAsColumn(items); + break; } default: throw std::runtime_error( @@ -610,8 +568,8 @@ void ch_binary_column_append_data(ch_binary_insert_state * state, size_t colidx) { try { - auto columns = *(std::vector *)state->columns; - auto col = columns[colidx]; + auto block = (Block *)state->insert_block; + auto col = (*block)[colidx]; Datum val = state->values[colidx]; Oid valtype = TupleDescAttr(state->outdesc, colidx)->atttypid; @@ -629,16 +587,11 @@ void ch_binary_insert_columns(ch_binary_insert_state * state) { try { - Block block; - auto columns = *(std::vector *)state->columns; - for (int i = 0; i < state->outdesc->natts; ++i) - { - Form_pg_attribute att = TupleDescAttr(state->outdesc, i); - block.AppendColumn(NameStr(att->attname), columns[i]); - } - Client * client = (Client *)state->conn->client; - client->Insert(state->table_name, block); + auto block = (Block *)state->insert_block; + block->RefreshRowCount(); + client->SendInsertBlock(*block); + block->Clear(); } catch (const std::exception & e) { diff --git a/src/include/binary.hh b/src/include/binary.hh index 6cb466b0..6f213832 100644 --- a/src/include/binary.hh +++ b/src/include/binary.hh @@ -6,6 +6,7 @@ extern "C" { #endif typedef struct ch_binary_connection_t ch_binary_connection_t; +typedef struct ch_insert_block_h ch_insert_block_h; typedef struct ch_binary_response_t { void *values; @@ -48,7 +49,7 @@ typedef struct { MemoryContextCallback callback; TupleDesc outdesc; - void *columns; /* std::vector */ + ch_insert_block_h *insert_block; /* clickhouse::Block */ size_t len; void *conversion_states; char *table_name; diff --git a/src/pglink.c b/src/pglink.c index 0bfcdc14..2a0d948b 100644 --- a/src/pglink.c +++ b/src/pglink.c @@ -737,7 +737,6 @@ binary_insert_tuple(void *istate, TupleTableSlot *slot) else { ch_binary_insert_columns(state); - state->success = true; } } diff --git a/test/expected/binary_inserts.out b/test/expected/binary_inserts.out new file mode 100644 index 00000000..19832d94 --- /dev/null +++ b/test/expected/binary_inserts.out @@ -0,0 +1,237 @@ +SET datestyle = 'ISO'; +CREATE SERVER binary_inserts_loopback FOREIGN DATA WRAPPER clickhouse_fdw OPTIONS(dbname 'binary_inserts_test', driver 'binary'); +CREATE USER MAPPING FOR CURRENT_USER SERVER binary_inserts_loopback; +SELECT clickhouse_raw_query('drop database if exists binary_inserts_test'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('create database binary_inserts_test'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.ints ( + c1 Int8, c2 Int16, c3 Int32, c4 Int64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.uints ( + c1 UInt8, c2 UInt16, c3 UInt32, c4 UInt64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.floats ( + c1 Float32, c2 Float64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1) SETTINGS allow_floating_point_partition_key=1; +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.null_ints ( + c1 Int8, c2 Nullable(Int32) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.complex ( + c1 Int32, c2 Date, c3 DateTime, c4 String, c5 FixedString(10), c6 LowCardinality(String), c7 DateTime64(3) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.arrays ( + c1 Int32, c2 Array(Int32) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +IMPORT FOREIGN SCHEMA "binary_inserts_test" FROM SERVER binary_inserts_loopback INTO public; +NOTICE: pg_clickhouse: ClickHouse type was translated to type for column "c1", change it to BOOLEAN if needed +/* ints */ +INSERT INTO ints + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(1, 3) i; +SELECT * FROM ints ORDER BY c1; + c1 | c2 | c3 | c4 +----+----+----+---- + 1 | 2 | 3 | 4 + 2 | 3 | 4 | 5 + 3 | 4 | 5 | 6 +(3 rows) + +INSERT INTO ints (c1, c4, c3, c2) + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(4, 6) i; +SELECT * FROM ints ORDER BY c1; + c1 | c2 | c3 | c4 +----+----+----+---- + 1 | 2 | 3 | 4 + 2 | 3 | 4 | 5 + 3 | 4 | 5 | 6 + 4 | 7 | 6 | 5 + 5 | 8 | 7 | 6 + 6 | 9 | 8 | 7 +(6 rows) + +/* check dropping columns (that will change attnums) */ +ALTER TABLE ints DROP COLUMN c1; +ALTER TABLE ints ADD COLUMN c1 SMALLINT; +INSERT INTO ints (c1, c2, c3, c4) + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(7, 8) i; +SELECT c1, c2, c3, c4 FROM ints ORDER BY c1; + c1 | c2 | c3 | c4 +----+----+----+---- + 1 | 2 | 3 | 4 + 2 | 3 | 4 | 5 + 3 | 4 | 5 | 6 + 4 | 7 | 6 | 5 + 5 | 8 | 7 | 6 + 6 | 9 | 8 | 7 + 7 | 8 | 9 | 10 + 8 | 9 | 10 | 11 +(8 rows) + +/* check other number types */ +INSERT INTO uints + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(1, 3) i; +SELECT * FROM uints ORDER BY c1; + c1 | c2 | c3 | c4 +----+----+----+---- + 1 | 2 | 3 | 4 + 2 | 3 | 4 | 5 + 3 | 4 | 5 | 6 +(3 rows) + +INSERT INTO floats + SELECT i * 1.1, i + 2.1 FROM generate_series(1, 3) i; +SELECT * FROM floats ORDER BY c1; + c1 | c2 +-----+----- + 1.1 | 3.1 + 2.2 | 4.1 + 3.3 | 5.1 +(3 rows) + +/* check nullable */ +INSERT INTO null_ints SELECT i, case WHEN i % 2 = 0 THEN NULL ELSE i END FROM generate_series(1, 10) i; +INSERT INTO null_ints(c1) SELECT i FROM generate_series(11, 13) i; +SELECT * FROM null_ints ORDER BY c1; + c1 | c2 +----+---- + 1 | 1 + 2 | + 3 | 3 + 4 | + 5 | 5 + 6 | + 7 | 7 + 8 | + 9 | 9 + 10 | + 11 | + 12 | + 13 | +(13 rows) + +SELECT * FROM null_ints ORDER BY c1; + c1 | c2 +----+---- + 1 | 1 + 2 | + 3 | 3 + 4 | + 5 | 5 + 6 | + 7 | 7 + 8 | + 9 | 9 + 10 | + 11 | + 12 | + 13 | +(13 rows) + +/* check dates and strings */ +ALTER TABLE complex ALTER COLUMN c7 SET DATA TYPE timestamp(3); +\d+ complex + Foreign table "public.complex" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +--------+--------------------------------+-----------+----------+---------+-------------+----------+--------------+------------- + c1 | integer | | not null | | | plain | | + c2 | date | | not null | | | plain | | + c3 | timestamp without time zone | | not null | | | plain | | + c4 | text | | not null | | | extended | | + c5 | character varying(10) | | not null | | | extended | | + c6 | text | | not null | | | extended | | + c7 | timestamp(3) without time zone | | not null | | | plain | | +Not-null constraints: + "complex_c1_not_null" NOT NULL "c1" + "complex_c2_not_null" NOT NULL "c2" + "complex_c3_not_null" NOT NULL "c3" + "complex_c4_not_null" NOT NULL "c4" + "complex_c5_not_null" NOT NULL "c5" + "complex_c6_not_null" NOT NULL "c6" + "complex_c7_not_null" NOT NULL "c7" +Server: binary_inserts_loopback +FDW options: (database 'binary_inserts_test', table_name 'complex', engine 'MergeTree') + +INSERT INTO complex VALUES + (1, '2020-06-01', '2020-06-02 10:01:02', 't1', 'fix_t1', 'low1', '2020-06-02 10:01:02.123'), + (2, '2020-06-02', '2020-06-03 10:01:02', 5, 'fix_t2', 'low2', '2020-06-03 11:01:02.234'), + (3, '2020-06-03', '2020-06-04 10:01:02', 5, 'fix_t3', 'low3', '2020-06-04 12:01:02'); +SELECT * FROM complex ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+------------+---------------------+----+--------+------+---------------------------- + 1 | 2020-06-01 | 2020-06-02 10:01:02 | t1 | fix_t1 | low1 | 2020-06-02 10:01:02.122999 + 2 | 2020-06-02 | 2020-06-03 10:01:02 | 5 | fix_t2 | low2 | 2020-06-03 11:01:02.234 + 3 | 2020-06-03 | 2020-06-04 10:01:02 | 5 | fix_t3 | low3 | 2020-06-04 12:01:02 +(3 rows) + +/* check arrays */ +INSERT INTO arrays VALUES + (1, ARRAY[1,2]), + (2, ARRAY[3,4,5]), + (3, ARRAY[6,4]); +SELECT * FROM arrays ORDER BY c1; + c1 | c2 +----+--------- + 1 | {1,2} + 2 | {3,4,5} + 3 | {6,4} +(3 rows) + +DROP USER MAPPING FOR CURRENT_USER SERVER binary_inserts_loopback; +SELECT clickhouse_raw_query('DROP DATABASE binary_inserts_test'); + clickhouse_raw_query +---------------------- + +(1 row) + +DROP SERVER binary_inserts_loopback CASCADE; +NOTICE: drop cascades to 6 other objects +DETAIL: drop cascades to foreign table arrays +drop cascades to foreign table complex +drop cascades to foreign table floats +drop cascades to foreign table ints +drop cascades to foreign table null_ints +drop cascades to foreign table uints diff --git a/test/expected/binary_inserts_1.out b/test/expected/binary_inserts_1.out new file mode 100644 index 00000000..bb40eaeb --- /dev/null +++ b/test/expected/binary_inserts_1.out @@ -0,0 +1,229 @@ +SET datestyle = 'ISO'; +CREATE SERVER binary_inserts_loopback FOREIGN DATA WRAPPER clickhouse_fdw OPTIONS(dbname 'binary_inserts_test', driver 'binary'); +CREATE USER MAPPING FOR CURRENT_USER SERVER binary_inserts_loopback; +SELECT clickhouse_raw_query('drop database if exists binary_inserts_test'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('create database binary_inserts_test'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.ints ( + c1 Int8, c2 Int16, c3 Int32, c4 Int64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.uints ( + c1 UInt8, c2 UInt16, c3 UInt32, c4 UInt64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.floats ( + c1 Float32, c2 Float64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1) SETTINGS allow_floating_point_partition_key=1; +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.null_ints ( + c1 Int8, c2 Nullable(Int32) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.complex ( + c1 Int32, c2 Date, c3 DateTime, c4 String, c5 FixedString(10), c6 LowCardinality(String), c7 DateTime64(3) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.arrays ( + c1 Int32, c2 Array(Int32) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + clickhouse_raw_query +---------------------- + +(1 row) + +IMPORT FOREIGN SCHEMA "binary_inserts_test" FROM SERVER binary_inserts_loopback INTO public; +NOTICE: pg_clickhouse: ClickHouse type was translated to type for column "c1", change it to BOOLEAN if needed +/* ints */ +INSERT INTO ints + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(1, 3) i; +SELECT * FROM ints ORDER BY c1; + c1 | c2 | c3 | c4 +----+----+----+---- + 1 | 2 | 3 | 4 + 2 | 3 | 4 | 5 + 3 | 4 | 5 | 6 +(3 rows) + +INSERT INTO ints (c1, c4, c3, c2) + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(4, 6) i; +SELECT * FROM ints ORDER BY c1; + c1 | c2 | c3 | c4 +----+----+----+---- + 1 | 2 | 3 | 4 + 2 | 3 | 4 | 5 + 3 | 4 | 5 | 6 + 4 | 7 | 6 | 5 + 5 | 8 | 7 | 6 + 6 | 9 | 8 | 7 +(6 rows) + +/* check dropping columns (that will change attnums) */ +ALTER TABLE ints DROP COLUMN c1; +ALTER TABLE ints ADD COLUMN c1 SMALLINT; +INSERT INTO ints (c1, c2, c3, c4) + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(7, 8) i; +SELECT c1, c2, c3, c4 FROM ints ORDER BY c1; + c1 | c2 | c3 | c4 +----+----+----+---- + 1 | 2 | 3 | 4 + 2 | 3 | 4 | 5 + 3 | 4 | 5 | 6 + 4 | 7 | 6 | 5 + 5 | 8 | 7 | 6 + 6 | 9 | 8 | 7 + 7 | 8 | 9 | 10 + 8 | 9 | 10 | 11 +(8 rows) + +/* check other number types */ +INSERT INTO uints + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(1, 3) i; +SELECT * FROM uints ORDER BY c1; + c1 | c2 | c3 | c4 +----+----+----+---- + 1 | 2 | 3 | 4 + 2 | 3 | 4 | 5 + 3 | 4 | 5 | 6 +(3 rows) + +INSERT INTO floats + SELECT i * 1.1, i + 2.1 FROM generate_series(1, 3) i; +SELECT * FROM floats ORDER BY c1; + c1 | c2 +-----+----- + 1.1 | 3.1 + 2.2 | 4.1 + 3.3 | 5.1 +(3 rows) + +/* check nullable */ +INSERT INTO null_ints SELECT i, case WHEN i % 2 = 0 THEN NULL ELSE i END FROM generate_series(1, 10) i; +INSERT INTO null_ints(c1) SELECT i FROM generate_series(11, 13) i; +SELECT * FROM null_ints ORDER BY c1; + c1 | c2 +----+---- + 1 | 1 + 2 | + 3 | 3 + 4 | + 5 | 5 + 6 | + 7 | 7 + 8 | + 9 | 9 + 10 | + 11 | + 12 | + 13 | +(13 rows) + +SELECT * FROM null_ints ORDER BY c1; + c1 | c2 +----+---- + 1 | 1 + 2 | + 3 | 3 + 4 | + 5 | 5 + 6 | + 7 | 7 + 8 | + 9 | 9 + 10 | + 11 | + 12 | + 13 | +(13 rows) + +/* check dates and strings */ +ALTER TABLE complex ALTER COLUMN c7 SET DATA TYPE timestamp(3); +\d+ complex + Foreign table "public.complex" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +--------+--------------------------------+-----------+----------+---------+-------------+----------+--------------+------------- + c1 | integer | | not null | | | plain | | + c2 | date | | not null | | | plain | | + c3 | timestamp without time zone | | not null | | | plain | | + c4 | text | | not null | | | extended | | + c5 | character varying(10) | | not null | | | extended | | + c6 | text | | not null | | | extended | | + c7 | timestamp(3) without time zone | | not null | | | plain | | +Server: binary_inserts_loopback +FDW options: (database 'binary_inserts_test', table_name 'complex', engine 'MergeTree') + +INSERT INTO complex VALUES + (1, '2020-06-01', '2020-06-02 10:01:02', 't1', 'fix_t1', 'low1', '2020-06-02 10:01:02.123'), + (2, '2020-06-02', '2020-06-03 10:01:02', 5, 'fix_t2', 'low2', '2020-06-03 11:01:02.234'), + (3, '2020-06-03', '2020-06-04 10:01:02', 5, 'fix_t3', 'low3', '2020-06-04 12:01:02'); +SELECT * FROM complex ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+------------+---------------------+----+--------+------+---------------------------- + 1 | 2020-06-01 | 2020-06-02 10:01:02 | t1 | fix_t1 | low1 | 2020-06-02 10:01:02.122999 + 2 | 2020-06-02 | 2020-06-03 10:01:02 | 5 | fix_t2 | low2 | 2020-06-03 11:01:02.234 + 3 | 2020-06-03 | 2020-06-04 10:01:02 | 5 | fix_t3 | low3 | 2020-06-04 12:01:02 +(3 rows) + +/* check arrays */ +INSERT INTO arrays VALUES + (1, ARRAY[1,2]), + (2, ARRAY[3,4,5]), + (3, ARRAY[6,4]); +SELECT * FROM arrays ORDER BY c1; + c1 | c2 +----+--------- + 1 | {1,2} + 2 | {3,4,5} + 3 | {6,4} +(3 rows) + +DROP USER MAPPING FOR CURRENT_USER SERVER binary_inserts_loopback; +SELECT clickhouse_raw_query('DROP DATABASE binary_inserts_test'); + clickhouse_raw_query +---------------------- + +(1 row) + +DROP SERVER binary_inserts_loopback CASCADE; +NOTICE: drop cascades to 6 other objects +DETAIL: drop cascades to foreign table arrays +drop cascades to foreign table complex +drop cascades to foreign table floats +drop cascades to foreign table ints +drop cascades to foreign table null_ints +drop cascades to foreign table uints diff --git a/test/expected/result_map.txt b/test/expected/result_map.txt index f94916d2..16736c37 100644 --- a/test/expected/result_map.txt +++ b/test/expected/result_map.txt @@ -7,6 +7,18 @@ ClickHouse they cover. * Postgres coverage run using latest ClickHouse release. * ClickHouse coverage run from PostgreSQL 18. +binary_inserts.sql +------------------ + + Postgres | File +----------|---------------------- + 18 | binary_inserts.out + 13-17 | binary_inserts_1.out + + ClickHouse | File +------------|-------------------- + 22-25 | binary_inserts.out + binary_queries.sql ------------------ @@ -33,7 +45,7 @@ deparse_checks.sql 13-17 | deparse_checks_1.out ClickHouse | File -------------|---------------------- +------------|-------------------- 22-25 | deparse_checks.out engines.sql diff --git a/test/sql/binary_inserts.sql b/test/sql/binary_inserts.sql new file mode 100644 index 00000000..eb5d6d02 --- /dev/null +++ b/test/sql/binary_inserts.sql @@ -0,0 +1,86 @@ +SET datestyle = 'ISO'; +CREATE SERVER binary_inserts_loopback FOREIGN DATA WRAPPER clickhouse_fdw OPTIONS(dbname 'binary_inserts_test', driver 'binary'); +CREATE USER MAPPING FOR CURRENT_USER SERVER binary_inserts_loopback; + +SELECT clickhouse_raw_query('drop database if exists binary_inserts_test'); +SELECT clickhouse_raw_query('create database binary_inserts_test'); +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.ints ( + c1 Int8, c2 Int16, c3 Int32, c4 Int64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.uints ( + c1 UInt8, c2 UInt16, c3 UInt32, c4 UInt64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.floats ( + c1 Float32, c2 Float64 +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1) SETTINGS allow_floating_point_partition_key=1; +'); + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.null_ints ( + c1 Int8, c2 Nullable(Int32) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.complex ( + c1 Int32, c2 Date, c3 DateTime, c4 String, c5 FixedString(10), c6 LowCardinality(String), c7 DateTime64(3) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + +SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.arrays ( + c1 Int32, c2 Array(Int32) +) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1); +'); + +IMPORT FOREIGN SCHEMA "binary_inserts_test" FROM SERVER binary_inserts_loopback INTO public; + +/* ints */ +INSERT INTO ints + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(1, 3) i; +SELECT * FROM ints ORDER BY c1; +INSERT INTO ints (c1, c4, c3, c2) + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(4, 6) i; +SELECT * FROM ints ORDER BY c1; + +/* check dropping columns (that will change attnums) */ +ALTER TABLE ints DROP COLUMN c1; +ALTER TABLE ints ADD COLUMN c1 SMALLINT; +INSERT INTO ints (c1, c2, c3, c4) + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(7, 8) i; +SELECT c1, c2, c3, c4 FROM ints ORDER BY c1; + +/* check other number types */ +INSERT INTO uints + SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(1, 3) i; +SELECT * FROM uints ORDER BY c1; +INSERT INTO floats + SELECT i * 1.1, i + 2.1 FROM generate_series(1, 3) i; +SELECT * FROM floats ORDER BY c1; + +/* check nullable */ +INSERT INTO null_ints SELECT i, case WHEN i % 2 = 0 THEN NULL ELSE i END FROM generate_series(1, 10) i; +INSERT INTO null_ints(c1) SELECT i FROM generate_series(11, 13) i; +SELECT * FROM null_ints ORDER BY c1; +SELECT * FROM null_ints ORDER BY c1; + +/* check dates and strings */ +ALTER TABLE complex ALTER COLUMN c7 SET DATA TYPE timestamp(3); +\d+ complex +INSERT INTO complex VALUES + (1, '2020-06-01', '2020-06-02 10:01:02', 't1', 'fix_t1', 'low1', '2020-06-02 10:01:02.123'), + (2, '2020-06-02', '2020-06-03 10:01:02', 5, 'fix_t2', 'low2', '2020-06-03 11:01:02.234'), + (3, '2020-06-03', '2020-06-04 10:01:02', 5, 'fix_t3', 'low3', '2020-06-04 12:01:02'); +SELECT * FROM complex ORDER BY c1; + +/* check arrays */ +INSERT INTO arrays VALUES + (1, ARRAY[1,2]), + (2, ARRAY[3,4,5]), + (3, ARRAY[6,4]); +SELECT * FROM arrays ORDER BY c1; + +DROP USER MAPPING FOR CURRENT_USER SERVER binary_inserts_loopback; +SELECT clickhouse_raw_query('DROP DATABASE binary_inserts_test'); +DROP SERVER binary_inserts_loopback CASCADE; diff --git a/vendor/clickhouse-cpp b/vendor/clickhouse-cpp index 69195246..b8544bbf 160000 --- a/vendor/clickhouse-cpp +++ b/vendor/clickhouse-cpp @@ -1 +1 @@ -Subproject commit 69195246a3b39542c397ef27df9f46ec4a4bf206 +Subproject commit b8544bbf43140a0490a8929e7a726de466aefff3