@@ -4841,3 +4841,257 @@ DROP TABLE t2_fk;
48414841DROP TABLE t1_fk;
48424842
48434843subtest end
4844+
4845+ subtest drop_col_dep_non_self_fk_stored
4846+
4847+ # The logic in this UDF is equivalent to and originated from
4848+ # columnDropViolatesFKIndexRequirements() in pkg/workload/schemachange/error_screening.go.
4849+ statement ok
4850+ CREATE FUNCTION column_drop_violates_fk_index_requirements(table_name STRING, column_name STRING) RETURNS BOOL LANGUAGE SQL AS $$
4851+ WITH fk AS (
4852+ SELECT
4853+ oid,
4854+ (SELECT r.relname FROM pg_class AS r WHERE r.oid = c.confrelid) AS base_table,
4855+ a.attname AS base_col,
4856+ array_position(c.confkey, a.attnum) AS base_ordinal,
4857+ (SELECT r.relname FROM pg_class AS r WHERE r.oid = c.conrelid) AS referencing_table,
4858+ unnest(
4859+ (SELECT array_agg(attname)
4860+ FROM pg_attribute
4861+ WHERE attrelid = c.conrelid
4862+ AND ARRAY[attnum] <@ c.conkey
4863+ AND array_position(c.confkey, a.attnum) = array_position(c.conkey, attnum))
4864+ ) AS referencing_col
4865+ FROM pg_constraint AS c
4866+ JOIN pg_attribute AS a ON
4867+ c.confrelid = a.attrelid
4868+ AND ARRAY[attnum] <@ c.conkey
4869+ WHERE c.confrelid = table_name::REGCLASS::OID
4870+ ),
4871+ all_index_columns AS (
4872+ SELECT
4873+ i.indexrelid::REGCLASS::STRING AS index_name,
4874+ a.attname AS col_name,
4875+ NOT i.indisunique AS non_unique,
4876+ a.attnum > i.indnkeyatts AS storing
4877+ FROM pg_index i
4878+ JOIN pg_attribute a ON a.attrelid = i.indexrelid AND a.attnum > 0
4879+ WHERE i.indrelid = table_name::REGCLASS::OID
4880+ ),
4881+ valid_indexes AS (
4882+ SELECT *
4883+ FROM all_index_columns
4884+ WHERE index_name NOT IN (
4885+ SELECT DISTINCT index_name
4886+ FROM all_index_columns
4887+ WHERE col_name = column_name
4888+ AND storing = false
4889+ AND index_name NOT LIKE '%_pkey'
4890+ )
4891+ ),
4892+ fk_col_counts AS (
4893+ SELECT oid, count(*) AS num_cols
4894+ FROM fk
4895+ GROUP BY oid
4896+ ),
4897+ index_col_counts AS (
4898+ SELECT index_name, count(*) AS num_cols
4899+ FROM valid_indexes
4900+ WHERE storing = false
4901+ GROUP BY index_name
4902+ ),
4903+ matching_fks AS (
4904+ SELECT fk.oid
4905+ FROM fk
4906+ JOIN valid_indexes
4907+ ON fk.base_col = valid_indexes.col_name
4908+ JOIN fk_col_counts
4909+ ON fk.oid = fk_col_counts.oid
4910+ JOIN index_col_counts
4911+ ON valid_indexes.index_name = index_col_counts.index_name
4912+ WHERE valid_indexes.storing = false
4913+ AND valid_indexes.non_unique = false
4914+ AND fk_col_counts.num_cols = index_col_counts.num_cols
4915+ GROUP BY fk.oid, valid_indexes.index_name, fk_col_counts.num_cols
4916+ HAVING count(*) = fk_col_counts.num_cols
4917+ )
4918+ SELECT EXISTS (
4919+ SELECT *
4920+ FROM fk
4921+ WHERE oid NOT IN (
4922+ SELECT DISTINCT oid FROM matching_fks
4923+ )
4924+ )
4925+ $$;
4926+
4927+ statement ok
4928+ CREATE TABLE parent_dep_store (
4929+ id INT PRIMARY KEY,
4930+ key_col INT UNIQUE,
4931+ stored STRING,
4932+ FAMILY f1 (id, key_col, stored)
4933+ ) WITH (schema_locked = false);
4934+
4935+ statement ok
4936+ CREATE UNIQUE INDEX idx_parent_dep_store ON parent_dep_store (key_col) STORING (stored);
4937+
4938+ statement ok
4939+ CREATE TABLE child_dep_store (
4940+ id INT PRIMARY KEY,
4941+ parent_key INT REFERENCES parent_dep_store (key_col),
4942+ FAMILY f1 (id, parent_key)
4943+ ) WITH (schema_locked = false);
4944+
4945+ query B
4946+ SELECT column_drop_violates_fk_index_requirements('parent_dep_store', 'stored');
4947+ ----
4948+ false
4949+
4950+ statement ok
4951+ ALTER TABLE parent_dep_store DROP COLUMN stored;
4952+
4953+ query TT
4954+ SHOW CREATE TABLE parent_dep_store
4955+ ----
4956+ parent_dep_store CREATE TABLE public.parent_dep_store (
4957+ id INT8 NOT NULL,
4958+ key_col INT8 NULL,
4959+ CONSTRAINT parent_dep_store_pkey PRIMARY KEY (id ASC),
4960+ UNIQUE INDEX parent_dep_store_key_col_key (key_col ASC),
4961+ FAMILY f1 (id, key_col)
4962+ );
4963+
4964+ statement ok
4965+ DROP TABLE child_dep_store;
4966+
4967+ statement ok
4968+ DROP TABLE parent_dep_store;
4969+
4970+ subtest end
4971+
4972+ subtest drop_col_dep_non_self_fk_key
4973+
4974+ statement ok
4975+ CREATE TABLE parent_dep_key (
4976+ id INT PRIMARY KEY,
4977+ key_col INT,
4978+ stored STRING,
4979+ FAMILY f1 (id, key_col, stored)
4980+ ) WITH (schema_locked = false);
4981+
4982+ statement ok
4983+ CREATE UNIQUE INDEX idx_parent_dep_key ON parent_dep_key (key_col) STORING (stored);
4984+
4985+ statement ok
4986+ CREATE TABLE child_dep_key (
4987+ id INT PRIMARY KEY,
4988+ parent_key INT REFERENCES parent_dep_key (key_col),
4989+ FAMILY f1 (id, parent_key)
4990+ ) WITH (schema_locked = false);
4991+
4992+ query B
4993+ SELECT column_drop_violates_fk_index_requirements('parent_dep_key', 'key_col');
4994+ ----
4995+ true
4996+
4997+ statement error pq: "idx_parent_dep_key" is referenced by foreign key from table "child_dep_key"
4998+ ALTER TABLE parent_dep_key DROP COLUMN key_col;
4999+
5000+ query TT
5001+ SHOW CREATE TABLE parent_dep_key
5002+ ----
5003+ parent_dep_key CREATE TABLE public.parent_dep_key (
5004+ id INT8 NOT NULL,
5005+ key_col INT8 NULL,
5006+ stored STRING NULL,
5007+ CONSTRAINT parent_dep_key_pkey PRIMARY KEY (id ASC),
5008+ UNIQUE INDEX idx_parent_dep_key (key_col ASC) STORING (stored),
5009+ FAMILY f1 (id, key_col, stored)
5010+ );
5011+
5012+ statement ok
5013+ DROP TABLE child_dep_key;
5014+
5015+ statement ok
5016+ DROP TABLE parent_dep_key;
5017+
5018+ subtest end
5019+
5020+ subtest drop_col_dep_self_ref_stored
5021+
5022+ statement ok
5023+ CREATE TABLE self_dep_store (
5024+ id INT PRIMARY KEY,
5025+ ref INT,
5026+ stored STRING,
5027+ FAMILY f1 (id, ref, stored)
5028+ ) WITH (schema_locked = false);
5029+
5030+ statement ok
5031+ CREATE UNIQUE INDEX idx_self_dep_store ON self_dep_store (ref) STORING (stored);
5032+
5033+ statement ok
5034+ ALTER TABLE self_dep_store ADD CONSTRAINT fk_self_dep_store FOREIGN KEY (ref) REFERENCES self_dep_store (id);
5035+
5036+ query B
5037+ SELECT column_drop_violates_fk_index_requirements('self_dep_store', 'stored');
5038+ ----
5039+ false
5040+
5041+ statement ok
5042+ ALTER TABLE self_dep_store DROP COLUMN stored;
5043+
5044+ query TT
5045+ SHOW CREATE TABLE self_dep_store
5046+ ----
5047+ self_dep_store CREATE TABLE public.self_dep_store (
5048+ id INT8 NOT NULL,
5049+ ref INT8 NULL,
5050+ CONSTRAINT self_dep_store_pkey PRIMARY KEY (id ASC),
5051+ CONSTRAINT fk_self_dep_store FOREIGN KEY (ref) REFERENCES public.self_dep_store(id),
5052+ FAMILY f1 (id, ref)
5053+ );
5054+
5055+ statement ok
5056+ DROP TABLE self_dep_store;
5057+
5058+ subtest end
5059+
5060+ subtest drop_col_dep_self_ref_key
5061+
5062+ statement ok
5063+ CREATE TABLE self_dep_key (
5064+ id INT PRIMARY KEY,
5065+ ref INT,
5066+ stored STRING,
5067+ FAMILY f1 (id, ref, stored)
5068+ ) WITH (schema_locked = false);
5069+
5070+ statement ok
5071+ CREATE UNIQUE INDEX idx_self_dep_key ON self_dep_key (ref) STORING (stored);
5072+
5073+ statement ok
5074+ ALTER TABLE self_dep_key ADD CONSTRAINT fk_self_dep_key FOREIGN KEY (ref) REFERENCES self_dep_key (id);
5075+
5076+ query B
5077+ SELECT column_drop_violates_fk_index_requirements('self_dep_key', 'ref');
5078+ ----
5079+ false
5080+
5081+ statement ok
5082+ ALTER TABLE self_dep_key DROP COLUMN ref;
5083+
5084+ query TT
5085+ SHOW CREATE TABLE self_dep_key
5086+ ----
5087+ self_dep_key CREATE TABLE public.self_dep_key (
5088+ id INT8 NOT NULL,
5089+ stored STRING NULL,
5090+ CONSTRAINT self_dep_key_pkey PRIMARY KEY (id ASC),
5091+ FAMILY f1 (id, stored)
5092+ );
5093+
5094+ statement ok
5095+ DROP TABLE self_dep_key;
5096+
5097+ subtest end
0 commit comments