diff --git a/src/backend/columnar/cstore_metadata_tables.c b/src/backend/columnar/cstore_metadata_tables.c index aa62cba00..a7beda70a 100644 --- a/src/backend/columnar/cstore_metadata_tables.c +++ b/src/backend/columnar/cstore_metadata_tables.c @@ -607,13 +607,23 @@ StripesForRelfilenode(RelFileNode relfilenode) /* * GetHighestUsedAddress returns the highest used address for the given * relfilenode across all active and inactive transactions. + * + * This is used by truncate stage of VACUUM, and VACUUM can be called + * for empty tables. So this doesn't throw errors for empty tables and + * returns 0. */ uint64 GetHighestUsedAddress(RelFileNode relfilenode) { uint64 highestUsedAddress = 0; uint64 highestUsedId = 0; - ColumnarMetapage *metapage = ReadMetapage(relfilenode, false); + ColumnarMetapage *metapage = ReadMetapage(relfilenode, true); + + /* empty data file? */ + if (metapage == NULL) + { + return 0; + } GetHighestUsedAddressAndId(metapage->storageId, &highestUsedAddress, &highestUsedId); diff --git a/src/backend/columnar/cstore_tableam.c b/src/backend/columnar/cstore_tableam.c index 2b18eb712..fc0f05a0f 100644 --- a/src/backend/columnar/cstore_tableam.c +++ b/src/backend/columnar/cstore_tableam.c @@ -743,7 +743,8 @@ LogRelationStats(Relation rel, int elevel) appendStringInfo(infoBuf, "total row count: %ld, stripe count: %d, " "average rows per stripe: %ld\n", - tupleCount, stripeCount, tupleCount / stripeCount); + tupleCount, stripeCount, + stripeCount ? tupleCount / stripeCount : 0); appendStringInfo(infoBuf, "block count: %ld" ", containing data for dropped columns: %ld", @@ -817,7 +818,11 @@ TruncateCStore(Relation rel, int elevel) SmgrAddr highestPhysicalAddress = logical_to_smgr(GetHighestUsedAddress(rel->rd_node)); - BlockNumber new_rel_pages = highestPhysicalAddress.blockno + 1; + /* + * Unlock and return if truncation won't reduce data file's size. + */ + BlockNumber new_rel_pages = Min(old_rel_pages, + highestPhysicalAddress.blockno + 1); if (new_rel_pages == old_rel_pages) { UnlockRelation(rel, AccessExclusiveLock); diff --git a/src/test/regress/columnar_am_schedule b/src/test/regress/columnar_am_schedule index b17b796e6..6da119250 100644 --- a/src/test/regress/columnar_am_schedule +++ b/src/test/regress/columnar_am_schedule @@ -8,6 +8,7 @@ test: am_query test: am_analyze test: am_data_types test: am_drop +test: am_empty test: am_insert test: am_copyto test: am_alter diff --git a/src/test/regress/expected/am_empty.out b/src/test/regress/expected/am_empty.out new file mode 100644 index 000000000..abef3d286 --- /dev/null +++ b/src/test/regress/expected/am_empty.out @@ -0,0 +1,97 @@ +-- +-- Test different operations on empty columnar tables. +-- +SET citus.compression to 'none'; +create table t_uncompressed(a int) using columnar; +create table t_compressed(a int) using columnar; +-- set options +SELECT alter_columnar_table_set('t_compressed', compression => 'pglz'); + alter_columnar_table_set +--------------------------------------------------------------------- + +(1 row) + +SELECT alter_columnar_table_set('t_compressed', stripe_row_count => 100); + alter_columnar_table_set +--------------------------------------------------------------------- + +(1 row) + +SELECT alter_columnar_table_set('t_compressed', block_row_count => 100); + alter_columnar_table_set +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM cstore.options WHERE regclass = 't_compressed'::regclass; + regclass | block_row_count | stripe_row_count | compression +--------------------------------------------------------------------- + t_compressed | 100 | 100 | pglz +(1 row) + +-- select +select * from t_uncompressed; + a +--------------------------------------------------------------------- +(0 rows) + +select count(*) from t_uncompressed; + count +--------------------------------------------------------------------- + 0 +(1 row) + +select * from t_compressed; + a +--------------------------------------------------------------------- +(0 rows) + +select count(*) from t_compressed; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- explain +explain (costs off, summary off, timing off) select * from t_uncompressed; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (CStoreScan) on t_uncompressed +(1 row) + +explain (costs off, summary off, timing off) select * from t_compressed; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (CStoreScan) on t_compressed +(1 row) + +-- vacuum +vacuum verbose t_compressed; +INFO: statistics for "t_compressed": +storage id: -1 +total file size: 0, total data size: 0 +total row count: 0, stripe count: 0, average rows per stripe: 0 +block count: 0, containing data for dropped columns: 0, none compressed: 0, pglz compressed: 0 + +vacuum verbose t_uncompressed; +INFO: statistics for "t_uncompressed": +storage id: -1 +total file size: 0, total data size: 0 +total row count: 0, stripe count: 0, average rows per stripe: 0 +block count: 0, containing data for dropped columns: 0, none compressed: 0, pglz compressed: 0 + +-- vacuum full +vacuum full t_compressed; +vacuum full t_uncompressed; +-- analyze +analyze t_uncompressed; +analyze t_compressed; +-- truncate +truncate t_uncompressed; +truncate t_compressed; +-- alter type +alter table t_uncompressed alter column a type text; +alter table t_compressed alter column a type text; +-- drop +drop table t_compressed; +drop table t_uncompressed; diff --git a/src/test/regress/sql/am_empty.sql b/src/test/regress/sql/am_empty.sql new file mode 100644 index 000000000..f20878419 --- /dev/null +++ b/src/test/regress/sql/am_empty.sql @@ -0,0 +1,48 @@ +-- +-- Test different operations on empty columnar tables. +-- + +SET citus.compression to 'none'; +create table t_uncompressed(a int) using columnar; +create table t_compressed(a int) using columnar; + +-- set options +SELECT alter_columnar_table_set('t_compressed', compression => 'pglz'); +SELECT alter_columnar_table_set('t_compressed', stripe_row_count => 100); +SELECT alter_columnar_table_set('t_compressed', block_row_count => 100); + +SELECT * FROM cstore.options WHERE regclass = 't_compressed'::regclass; + +-- select +select * from t_uncompressed; +select count(*) from t_uncompressed; +select * from t_compressed; +select count(*) from t_compressed; + +-- explain +explain (costs off, summary off, timing off) select * from t_uncompressed; +explain (costs off, summary off, timing off) select * from t_compressed; + +-- vacuum +vacuum verbose t_compressed; +vacuum verbose t_uncompressed; + +-- vacuum full +vacuum full t_compressed; +vacuum full t_uncompressed; + +-- analyze +analyze t_uncompressed; +analyze t_compressed; + +-- truncate +truncate t_uncompressed; +truncate t_compressed; + +-- alter type +alter table t_uncompressed alter column a type text; +alter table t_compressed alter column a type text; + +-- drop +drop table t_compressed; +drop table t_uncompressed;