diff --git a/docs/RFCS/20220120_row_level_ttl.md b/docs/RFCS/20220120_row_level_ttl.md index 5d5eb56ec059..7118ff618176 100644 --- a/docs/RFCS/20220120_row_level_ttl.md +++ b/docs/RFCS/20220120_row_level_ttl.md @@ -320,10 +320,16 @@ Rows that have expired their TTL can be optionally removed from all SQL query results. Work for this is required at the optimizer layer. However, this is not planned to be implemented for the first iteration of TTL. -## Foreign Keys to a TTL Table -To avoid additional complexity in the initial implementation, foreign keys to -TTL tables will not be permitted. More thought has to be put on ON -DELETE/ON UPDATE CASCADEs before we can look at allowing this functionality. +## Foreign Keys to/from TTL Tables +To avoid additional complexity in the initial implementation, foreign keys (FK) +to and from TTL tables will not be permitted due to complexities with the +implementation which are complex to handle, for example: +* When having a non-TTL table with a FK dependent on a TTL table with an + `ON UPDATE/DELETE CASCADE`, the non-TTL table need to hide any rows which + are linked to an expired TTL row. +* When having a TTL table with an FK dependent on a non-TTL table, + `ON DELETE RESTRICT` should only block a delete on the non-TTL table + if the row has expired. ## Introspection The TTL definition for the table will appear in `SHOW CREATE TABLE`. The options diff --git a/pkg/sql/catalog/descriptor.go b/pkg/sql/catalog/descriptor.go index 79a9a8d4e580..df2ad4e55d5c 100644 --- a/pkg/sql/catalog/descriptor.go +++ b/pkg/sql/catalog/descriptor.go @@ -638,6 +638,8 @@ type TableDescriptor interface { GetRegionalByRowTableRegionColumnName() (tree.Name, error) // GetRowLevelTTL returns the row-level TTL config for the table. GetRowLevelTTL() *descpb.TableDescriptor_RowLevelTTL + // HasRowLevelTTL returns where there is a row-level TTL config for the table. + HasRowLevelTTL() bool // GetExcludeDataFromBackup returns true if the table's row data is configured // to be excluded during backup. GetExcludeDataFromBackup() bool diff --git a/pkg/sql/catalog/tabledesc/structured.go b/pkg/sql/catalog/tabledesc/structured.go index a893c1d07dd4..b44d3dcdb688 100644 --- a/pkg/sql/catalog/tabledesc/structured.go +++ b/pkg/sql/catalog/tabledesc/structured.go @@ -2440,6 +2440,11 @@ func (desc *wrapper) GetRowLevelTTL() *descpb.TableDescriptor_RowLevelTTL { return desc.RowLevelTTL } +// HasRowLevelTTL implements the TableDescriptor interface. +func (desc *wrapper) HasRowLevelTTL() bool { + return desc.RowLevelTTL != nil +} + // GetExcludeDataFromBackup implements the TableDescriptor interface. func (desc *wrapper) GetExcludeDataFromBackup() bool { return desc.ExcludeDataFromBackup diff --git a/pkg/sql/catalog/tabledesc/validate.go b/pkg/sql/catalog/tabledesc/validate.go index e6e6a91a1966..0ed3d5be0cc2 100644 --- a/pkg/sql/catalog/tabledesc/validate.go +++ b/pkg/sql/catalog/tabledesc/validate.go @@ -176,6 +176,13 @@ func (desc *wrapper) ValidateCrossReferences( } // Check foreign keys. + if desc.HasRowLevelTTL() && (len(desc.OutboundFKs) > 0 || len(desc.InboundFKs) > 0) { + vea.Report(unimplemented.NewWithIssuef( + 76407, + `foreign keys to/from table with TTL "%s" are not permitted`, + desc.Name, + )) + } for i := range desc.OutboundFKs { vea.Report(desc.validateOutboundFK(&desc.OutboundFKs[i], vdg)) } diff --git a/pkg/sql/logictest/testdata/logic_test/row_level_ttl b/pkg/sql/logictest/testdata/logic_test/row_level_ttl index c857ddfc65d9..3e7ff8530257 100644 --- a/pkg/sql/logictest/testdata/logic_test/row_level_ttl +++ b/pkg/sql/logictest/testdata/logic_test/row_level_ttl @@ -97,3 +97,28 @@ CREATE TABLE public.tbl ( CONSTRAINT tbl_pkey PRIMARY KEY (id ASC), FAMILY fam_0_id_text_crdb_internal_expiration (id, text, crdb_internal_expiration) ) WITH (ttl = 'on', ttl_automatic_column = 'on', ttl_expire_after = '00:10:00':::INTERVAL) + +# Test we cannot add FKs to a TTL table. +statement ok +CREATE TABLE ref_table (id INT PRIMARY KEY, ref INT) + +statement error foreign keys to/from table with TTL "ttl_table" are not permitted +CREATE TABLE ttl_table (id INT PRIMARY KEY, ref INT REFERENCES ref_table(id)) WITH (ttl_expire_after = '10 mins') + +statement ok +CREATE TABLE ttl_table (id INT PRIMARY KEY, ref INT) WITH (ttl_expire_after = '10 mins') + +statement error foreign keys to/from table with TTL "ttl_table" are not permitted +CREATE TABLE new_ref_table (id INT PRIMARY KEY, ref INT REFERENCES ttl_table(id)) + +statement error foreign keys to/from table with TTL "ttl_table" are not permitted +ALTER TABLE ref_table ADD CONSTRAINT fk FOREIGN KEY (ref) REFERENCES ttl_table (id) + +statement error foreign keys to/from table with TTL "ttl_table" are not permitted +ALTER TABLE ttl_table ADD CONSTRAINT fk FOREIGN KEY (ref) REFERENCES ttl_table (id) + +statement ok +CREATE TABLE ttl_become_table (id INT PRIMARY KEY, ref INT REFERENCES ref_table (id)) + +statement error foreign keys to/from table with TTL "ttl_become_table" are not permitted +ALTER TABLE ttl_become_table SET (ttl_expire_after = '10 minutes')