From 52297804ae08508a106dce125aa0b2b9276f8b09 Mon Sep 17 00:00:00 2001 From: Hadi Moshayedi Date: Tue, 2 Feb 2021 20:27:51 -0800 Subject: [PATCH] Fix zero column tables --- src/backend/columnar/cstore_reader.c | 37 ++++++++++ src/test/regress/expected/am_alter.out | 94 ++++++++++++++++++++++++++ src/test/regress/sql/am_alter.sql | 55 +++++++++++++++ 3 files changed, 186 insertions(+) diff --git a/src/backend/columnar/cstore_reader.c b/src/backend/columnar/cstore_reader.c index 4010c1419..8caff6017 100644 --- a/src/backend/columnar/cstore_reader.c +++ b/src/backend/columnar/cstore_reader.c @@ -21,6 +21,7 @@ #include "access/nbtree.h" #include "catalog/pg_am.h" #include "commands/defrem.h" +#include "distributed/listutils.h" #include "nodes/makefuncs.h" #if PG_VERSION_NUM >= 120000 #include "nodes/nodeFuncs.h" @@ -46,6 +47,13 @@ struct TableReadState TupleDesc tupleDescriptor; Relation relation; + /* + * Following are used for tables with zero columns, or when no + * columns are projected. + */ + uint64 totalRowCount; + uint64 readRowCount; + /* * List of Var pointers for columns in the query. We use this both for * getting vector of projected columns, and also when we want to build @@ -112,6 +120,13 @@ ColumnarBeginRead(Relation relation, TupleDesc tupleDescriptor, List *projectedColumnList, List *whereClauseList) { List *stripeList = StripesForRelfilenode(relation->rd_node); + StripeMetadata *stripeMetadata = NULL; + + uint64 totalRowCount = 0; + foreach_ptr(stripeMetadata, stripeList) + { + totalRowCount += stripeMetadata->rowCount; + } /* * We allocate all stripe specific data in the stripeReadContext, and reset @@ -135,6 +150,8 @@ ColumnarBeginRead(Relation relation, TupleDesc tupleDescriptor, readState->stripeReadContext = stripeReadContext; readState->chunkData = NULL; readState->deserializedChunkIndex = -1; + readState->readRowCount = 0; + readState->totalRowCount = totalRowCount; return readState; } @@ -151,6 +168,26 @@ ColumnarReadNextRow(TableReadState *readState, Datum *columnValues, bool *column StripeMetadata *stripeMetadata = readState->currentStripeMetadata; MemoryContext oldContext = NULL; + /* + * We rely on first column's metadata in rest of this function. So for zero + * column tables we just return "true" for totalRowCount times. We do the + * same when no columns are projected. + */ + if (readState->projectedColumnList == NIL) + { + if (readState->totalRowCount == readState->readRowCount) + { + return false; + } + else + { + int columnCount = readState->tupleDescriptor->natts; + memset(columnNulls, 1, sizeof(bool) * columnCount); + readState->readRowCount++; + return true; + } + } + /* * If no stripes are loaded, load the next non-empty stripe. Note that when * loading stripes, we skip over chunks whose contents can be filtered with diff --git a/src/test/regress/expected/am_alter.out b/src/test/regress/expected/am_alter.out index a5e82ad3e..9db5580ef 100644 --- a/src/test/regress/expected/am_alter.out +++ b/src/test/regress/expected/am_alter.out @@ -240,5 +240,99 @@ SELECT * FROM test_gen_ex; 3 | 4 (3 rows) +-- check removing all columns while having some data to simulate +-- table with non-zero rows but zero-columns. +-- https://github.com/citusdata/citus/issues/4626 +BEGIN; +create table local(y int); +insert into local values (1), (2); +alter table local drop column y; +CREATE TABLE zero_col_columnar (like local) USING COLUMNAR; +ALTER TABLE local RENAME TO local_xxxxx; +INSERT INTO zero_col_columnar SELECT * FROM local_xxxxx; +COMMIT; +SELECT * FROM zero_col_columnar; +-- +(2 rows) + +SELECT count(*) FROM zero_col_columnar; + count +--------------------------------------------------------------------- + 2 +(1 row) + +EXPLAIN (costs off, summary off) SELECT * FROM zero_col_columnar; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (ColumnarScan) on zero_col_columnar +(1 row) + +INSERT INTO zero_col_columnar DEFAULT VALUES; +INSERT INTO zero_col_columnar DEFAULT VALUES; +INSERT INTO zero_col_columnar DEFAULT VALUES; +SELECT * FROM zero_col_columnar; +-- +(5 rows) + +SELECT count(*) FROM zero_col_columnar; + count +--------------------------------------------------------------------- + 5 +(1 row) + +EXPLAIN (costs off, summary off) SELECT * FROM zero_col_columnar; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (ColumnarScan) on zero_col_columnar +(1 row) + +VACUUM VERBOSE zero_col_columnar; +INFO: statistics for "zero_col_columnar": +storage id: xxxxx +total file size: 16384, total data size: 0 +compression rate: 1.00x +total row count: 5, stripe count: 4, average rows per stripe: 1 +chunk count: 0, containing data for dropped columns: 0 + +ANALYZE zero_col_columnar; +VACUUM FULL zero_col_columnar; +SELECT * FROM zero_col_columnar; +-- +(5 rows) + +TRUNCATE zero_col_columnar; +SELECT * FROM zero_col_columnar; +-- +(0 rows) + +DROP TABLE zero_col_columnar; +CREATE TABLE zero_col_columnar(a int) USING columnar; +INSERT INTO zero_col_columnar SELECT i FROM generate_series(1, 5) i; +alter table zero_col_columnar drop column a; +SELECT * FROM zero_col_columnar; +-- +(5 rows) + +INSERT INTO zero_col_columnar DEFAULT VALUES; +INSERT INTO zero_col_columnar DEFAULT VALUES; +INSERT INTO zero_col_columnar DEFAULT VALUES; +SELECT * FROM zero_col_columnar; +-- +(8 rows) + +VACUUM VERBOSE zero_col_columnar; +INFO: statistics for "zero_col_columnar": +storage id: xxxxx +total file size: 49152, total data size: 60 +compression rate: 0.40x +total row count: 8, stripe count: 4, average rows per stripe: 2 +chunk count: 4, containing data for dropped columns: 4, zstd compressed: 4 + +ANALYZE zero_col_columnar; +VACUUM FULL zero_col_columnar; +SELECT * FROM zero_col_columnar; +-- +(8 rows) + SET client_min_messages TO WARNING; DROP SCHEMA columnar_alter CASCADE; diff --git a/src/test/regress/sql/am_alter.sql b/src/test/regress/sql/am_alter.sql index c3efad28a..2ec12a6e4 100644 --- a/src/test/regress/sql/am_alter.sql +++ b/src/test/regress/sql/am_alter.sql @@ -123,5 +123,60 @@ INSERT INTO test_gen_ex VALUES (1), (2), (3); ALTER TABLE test_gen_ex ADD COLUMN y int generated always as (x+1) stored; SELECT * FROM test_gen_ex; + +-- check removing all columns while having some data to simulate +-- table with non-zero rows but zero-columns. +-- https://github.com/citusdata/citus/issues/4626 +BEGIN; +create table local(y int); +insert into local values (1), (2); +alter table local drop column y; + +CREATE TABLE zero_col_columnar (like local) USING COLUMNAR; +ALTER TABLE local RENAME TO local_xxxxx; +INSERT INTO zero_col_columnar SELECT * FROM local_xxxxx; +COMMIT; + +SELECT * FROM zero_col_columnar; +SELECT count(*) FROM zero_col_columnar; +EXPLAIN (costs off, summary off) SELECT * FROM zero_col_columnar; + +INSERT INTO zero_col_columnar DEFAULT VALUES; +INSERT INTO zero_col_columnar DEFAULT VALUES; +INSERT INTO zero_col_columnar DEFAULT VALUES; +SELECT * FROM zero_col_columnar; +SELECT count(*) FROM zero_col_columnar; +EXPLAIN (costs off, summary off) SELECT * FROM zero_col_columnar; + +VACUUM VERBOSE zero_col_columnar; +ANALYZE zero_col_columnar; +VACUUM FULL zero_col_columnar; + +SELECT * FROM zero_col_columnar; + +TRUNCATE zero_col_columnar; + +SELECT * FROM zero_col_columnar; + +DROP TABLE zero_col_columnar; + +CREATE TABLE zero_col_columnar(a int) USING columnar; +INSERT INTO zero_col_columnar SELECT i FROM generate_series(1, 5) i; +alter table zero_col_columnar drop column a; + +SELECT * FROM zero_col_columnar; + +INSERT INTO zero_col_columnar DEFAULT VALUES; +INSERT INTO zero_col_columnar DEFAULT VALUES; +INSERT INTO zero_col_columnar DEFAULT VALUES; + +SELECT * FROM zero_col_columnar; + +VACUUM VERBOSE zero_col_columnar; +ANALYZE zero_col_columnar; +VACUUM FULL zero_col_columnar; + +SELECT * FROM zero_col_columnar; + SET client_min_messages TO WARNING; DROP SCHEMA columnar_alter CASCADE;