From 6197a1e2d378f81720b326a7f735cac528eace80 Mon Sep 17 00:00:00 2001 From: Mehmet Yilmaz Date: Thu, 16 Oct 2025 10:58:31 +0000 Subject: [PATCH] Add CREATE VIEW statement for columnar.storage with security barrier Add new views for columnar storage: stripe, chunk_group, and chunk with security barrier Refactor columnar views to use OR REPLACE for consistent binding and add missing comments Enhance columnar test helpers with visibility check function and update related queries for improved storage ID retrieval --- .../sql/citus_columnar--13.2-1--14.0-1.sql | 42 +++++++++++++++++++ src/test/regress/expected/columnar_create.out | 4 +- .../regress/expected/columnar_recursive.out | 17 ++++++-- .../expected/columnar_test_helpers.out | 12 ++++++ src/test/regress/sql/columnar_create.sql | 4 +- src/test/regress/sql/columnar_recursive.sql | 18 ++++++-- .../regress/sql/columnar_test_helpers.sql | 13 ++++++ 7 files changed, 98 insertions(+), 12 deletions(-) diff --git a/src/backend/columnar/sql/citus_columnar--13.2-1--14.0-1.sql b/src/backend/columnar/sql/citus_columnar--13.2-1--14.0-1.sql index 016c78f6b..dc8318898 100644 --- a/src/backend/columnar/sql/citus_columnar--13.2-1--14.0-1.sql +++ b/src/backend/columnar/sql/citus_columnar--13.2-1--14.0-1.sql @@ -1,2 +1,44 @@ -- citus_columnar--13.2-1--14.0-1 -- bump version to 14.0-1 + +CREATE OR REPLACE VIEW columnar.storage WITH (security_barrier) AS + SELECT c.oid::regclass AS relation, + columnar.get_storage_id(c.oid) AS storage_id + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_am am ON c.relam = am.oid + WHERE am.amname = 'columnar' + -- exclude other sessions' temp rels, but keep *my* temp tables + AND (c.relpersistence <> 't' + OR c.relnamespace = pg_catalog.pg_my_temp_schema()) + AND pg_catalog.pg_has_role(c.relowner, 'USAGE'); +COMMENT ON VIEW columnar.storage IS 'Columnar relation ID to storage ID mapping.'; +GRANT SELECT ON columnar.storage TO PUBLIC; + +-- re-emit dependent views with OR REPLACE so they stay bound cleanly +CREATE OR REPLACE VIEW columnar.stripe WITH (security_barrier) AS + SELECT relation, storage.storage_id, stripe_num, file_offset, data_length, + column_count, chunk_row_count, row_count, chunk_group_count, first_row_number + FROM columnar_internal.stripe stripe, columnar.storage storage + WHERE stripe.storage_id = storage.storage_id; +COMMENT ON VIEW columnar.stripe + IS 'Columnar stripe information for tables on which the current user has ownership privileges.'; +GRANT SELECT ON columnar.stripe TO PUBLIC; + +CREATE OR REPLACE VIEW columnar.chunk_group WITH (security_barrier) AS + SELECT relation, storage.storage_id, stripe_num, chunk_group_num, row_count + FROM columnar_internal.chunk_group cg, columnar.storage storage + WHERE cg.storage_id = storage.storage_id; +COMMENT ON VIEW columnar.chunk_group + IS 'Columnar chunk group information for tables on which the current user has ownership privileges.'; +GRANT SELECT ON columnar.chunk_group TO PUBLIC; + +CREATE OR REPLACE VIEW columnar.chunk WITH (security_barrier) AS + SELECT relation, storage.storage_id, stripe_num, attr_num, chunk_group_num, + minimum_value, maximum_value, value_stream_offset, value_stream_length, + exists_stream_offset, exists_stream_length, value_compression_type, + value_compression_level, value_decompressed_length, value_count + FROM columnar_internal.chunk chunk, columnar.storage storage + WHERE chunk.storage_id = storage.storage_id; +COMMENT ON VIEW columnar.chunk + IS 'Columnar chunk information for tables on which the current user has ownership privileges.'; +GRANT SELECT ON columnar.chunk TO PUBLIC; diff --git a/src/test/regress/expected/columnar_create.out b/src/test/regress/expected/columnar_create.out index a134fd063..4b02aa4f6 100644 --- a/src/test/regress/expected/columnar_create.out +++ b/src/test/regress/expected/columnar_create.out @@ -214,8 +214,8 @@ SELECT COUNT(*) FROM columnar_temp WHERE i < 5; 4 (1 row) -SELECT columnar.get_storage_id(oid) AS columnar_temp_storage_id -FROM pg_class WHERE relname='columnar_temp' \gset +SELECT COALESCE(columnar_test_helpers.get_storage_id_if_visible('columnar_temp'::regclass), 0) + AS columnar_temp_storage_id \gset BEGIN; DROP TABLE columnar_temp; -- show that we drop stripes properly diff --git a/src/test/regress/expected/columnar_recursive.out b/src/test/regress/expected/columnar_recursive.out index 7b4b828be..1a54f4f59 100644 --- a/src/test/regress/expected/columnar_recursive.out +++ b/src/test/regress/expected/columnar_recursive.out @@ -11,10 +11,19 @@ $$ LANGUAGE SQL; INSERT INTO t2 SELECT i, f(i) FROM generate_series(1, 5) i; -- there are no subtransactions, so above statement should batch -- INSERTs inside the UDF and create on stripe per table. -SELECT relname, count(*) FROM columnar.stripe a, pg_class b -WHERE columnar.get_storage_id(b.oid)=a.storage_id AND relname IN ('t1', 't2') -GROUP BY relname -ORDER BY relname; +WITH rels(rel) AS ( + VALUES ('t1'::regclass), ('t2'::regclass) +), +sids AS ( + SELECT rel, columnar.get_storage_id(rel) AS sid + FROM rels +) +SELECT c.relname, COUNT(*) AS count +FROM columnar_internal.stripe st +JOIN sids s ON st.storage_id = s.sid +JOIN pg_catalog.pg_class c ON c.oid = s.rel +GROUP BY c.relname +ORDER BY c.relname; relname | count --------------------------------------------------------------------- t1 | 1 diff --git a/src/test/regress/expected/columnar_test_helpers.out b/src/test/regress/expected/columnar_test_helpers.out index f4f179e55..b2b17cc94 100644 --- a/src/test/regress/expected/columnar_test_helpers.out +++ b/src/test/regress/expected/columnar_test_helpers.out @@ -146,3 +146,15 @@ BEGIN RETURN NEXT; END LOOP; END; $$ language plpgsql; +CREATE OR REPLACE FUNCTION get_storage_id_if_visible(rel regclass) +RETURNS bigint +LANGUAGE sql STABLE AS $$ + SELECT CASE + WHEN c.relpersistence = 't' + AND c.relnamespace <> pg_catalog.pg_my_temp_schema() + THEN NULL -- other session’s temp → don’t touch + ELSE columnar.get_storage_id(c.oid) + END + FROM pg_catalog.pg_class c + WHERE c.oid = $1::oid +$$; diff --git a/src/test/regress/sql/columnar_create.sql b/src/test/regress/sql/columnar_create.sql index a0708aeac..93db8a25f 100644 --- a/src/test/regress/sql/columnar_create.sql +++ b/src/test/regress/sql/columnar_create.sql @@ -174,8 +174,8 @@ INSERT INTO columnar_temp SELECT i FROM generate_series(1,5) i; -- test basic select SELECT COUNT(*) FROM columnar_temp WHERE i < 5; -SELECT columnar.get_storage_id(oid) AS columnar_temp_storage_id -FROM pg_class WHERE relname='columnar_temp' \gset +SELECT COALESCE(columnar_test_helpers.get_storage_id_if_visible('columnar_temp'::regclass), 0) + AS columnar_temp_storage_id \gset BEGIN; DROP TABLE columnar_temp; diff --git a/src/test/regress/sql/columnar_recursive.sql b/src/test/regress/sql/columnar_recursive.sql index 08d77afdb..c916ce066 100644 --- a/src/test/regress/sql/columnar_recursive.sql +++ b/src/test/regress/sql/columnar_recursive.sql @@ -15,10 +15,20 @@ INSERT INTO t2 SELECT i, f(i) FROM generate_series(1, 5) i; -- there are no subtransactions, so above statement should batch -- INSERTs inside the UDF and create on stripe per table. -SELECT relname, count(*) FROM columnar.stripe a, pg_class b -WHERE columnar.get_storage_id(b.oid)=a.storage_id AND relname IN ('t1', 't2') -GROUP BY relname -ORDER BY relname; +WITH rels(rel) AS ( + VALUES ('t1'::regclass), ('t2'::regclass) +), +sids AS ( + SELECT rel, columnar.get_storage_id(rel) AS sid + FROM rels +) +SELECT c.relname, COUNT(*) AS count +FROM columnar_internal.stripe st +JOIN sids s ON st.storage_id = s.sid +JOIN pg_catalog.pg_class c ON c.oid = s.rel +GROUP BY c.relname +ORDER BY c.relname; + SELECT * FROM t1 ORDER BY a; SELECT * FROM t2 ORDER BY a; diff --git a/src/test/regress/sql/columnar_test_helpers.sql b/src/test/regress/sql/columnar_test_helpers.sql index 9cff79bbe..51f5123c7 100644 --- a/src/test/regress/sql/columnar_test_helpers.sql +++ b/src/test/regress/sql/columnar_test_helpers.sql @@ -158,3 +158,16 @@ BEGIN RETURN NEXT; END LOOP; END; $$ language plpgsql; + +CREATE OR REPLACE FUNCTION get_storage_id_if_visible(rel regclass) +RETURNS bigint +LANGUAGE sql STABLE AS $$ + SELECT CASE + WHEN c.relpersistence = 't' + AND c.relnamespace <> pg_catalog.pg_my_temp_schema() + THEN NULL -- other session’s temp → don’t touch + ELSE columnar.get_storage_id(c.oid) + END + FROM pg_catalog.pg_class c + WHERE c.oid = $1::oid +$$;