Skip to content

Commit 8ad2f4c

Browse files
authored
Merge pull request #155560 from yuzefovich/blathers/backport-release-25.4-155541
release-25.4: stmtbundle: include skipped FKs into `schema.sql`
2 parents 1009b08 + 30f3cb9 commit 8ad2f4c

File tree

4 files changed

+107
-38
lines changed

4 files changed

+107
-38
lines changed

pkg/sql/explain_bundle.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ func (b *stmtBundleBuilder) addEnv(ctx context.Context) {
599599
// update this logic to not include virtual tables into schema.sql but still
600600
// create stats files for them.
601601
var tables, sequences, views []tree.TableName
602-
var addFKs []*tree.AlterTable
602+
var addFKs, skipFKs []*tree.AlterTable
603603
err := b.db.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
604604
// Catalog objects can show up multiple times in our lists, so
605605
// deduplicate them.
@@ -778,9 +778,13 @@ func (b *stmtBundleBuilder) addEnv(ctx context.Context) {
778778
include = hasDelete || hasUpdate || hasUpsert
779779
},
780780
)
781-
addFKs = opt.GetAllFKsAmongTables(refTables, func(t cat.Table) (tree.TableName, error) {
782-
return b.plan.catalog.fullyQualifiedNameWithTxn(ctx, t, txn)
783-
})
781+
addFKs, skipFKs = opt.GetAllFKs(
782+
ctx,
783+
b.plan.catalog,
784+
refTables,
785+
func(t cat.Table) (tree.TableName, error) {
786+
return b.plan.catalog.fullyQualifiedNameWithTxn(ctx, t, txn)
787+
})
784788
var err error
785789
tables, err = getNames(len(refTables), func(i int) cat.DataSource {
786790
return refTables[i]
@@ -890,6 +894,10 @@ func (b *stmtBundleBuilder) addEnv(ctx context.Context) {
890894
for _, addFK := range addFKs {
891895
fmt.Fprintf(&buf, "%s;\n", addFK)
892896
}
897+
// Include FK constraints that were skipped in commented out form.
898+
for _, skipFK := range skipFKs {
899+
fmt.Fprintf(&buf, "-- %s;\n", skipFK)
900+
}
893901
}
894902
for i := range views {
895903
blankLine()

pkg/sql/explain_bundle_test.go

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,15 @@ CREATE TABLE users(id UUID DEFAULT gen_random_uuid() PRIMARY KEY, promo_id INT R
536536
r.Exec(t, "CREATE TABLE child2 (pk INT PRIMARY KEY, fk INT REFERENCES parent(pk));")
537537
r.Exec(t, "CREATE TABLE grandchild1 (pk INT PRIMARY KEY, fk INT REFERENCES child1(pk));")
538538
r.Exec(t, "CREATE TABLE grandchild2 (pk INT PRIMARY KEY, fk INT REFERENCES child2(pk));")
539+
getFK := func(table string) string {
540+
if table == "parent" {
541+
return ""
542+
}
543+
return fmt.Sprintf("ALTER TABLE defaultdb.public.%s ADD CONSTRAINT", table)
544+
}
539545
// Only the target tables should be included since we perform a
540546
// read-only stmt.
541-
getContentCheckFn := func(targetTableNames, targetFKs []string) func(name, contents string) error {
547+
getContentCheckFn := func(targetTableNames, addFKs, skipFKs []string) func(name, contents string) error {
542548
return func(name, contents string) error {
543549
if name == "schema.sql" {
544550
for _, targetTableName := range targetTableNames {
@@ -563,14 +569,27 @@ CREATE TABLE users(id UUID DEFAULT gen_random_uuid() PRIMARY KEY, promo_id INT R
563569
"unexpectedly found non-target table 'USE defaultdb;\nCREATE TABLE public.%s' in schema.sql:\n%s", tableName, contents)
564570
}
565571
}
566-
// Now confirm that only relevant FKs are included.
572+
// Sanity-check that all FKs in the output are either in the
573+
// "added" or "skipped" set.
567574
numFoundFKs := strings.Count(contents, "FOREIGN KEY")
568-
if numFoundFKs != len(targetFKs) {
569-
return errors.Newf("found %d FKs, expected %d\n%s", numFoundFKs, len(targetFKs), contents)
575+
if numFoundFKs != len(addFKs)+len(skipFKs) {
576+
return errors.Newf(
577+
"found %d FKs total whereas %d added and %d skipped were passed\n%s",
578+
numFoundFKs, len(addFKs), len(skipFKs), contents,
579+
)
580+
}
581+
// Now check that all expected added and skipped FKs are
582+
// present.
583+
for _, addFK := range addFKs {
584+
if !strings.Contains(contents, addFK) {
585+
return errors.Newf("didn't find added FK: %s\n%s", addFK, contents)
586+
} else if strings.Contains(contents, "-- "+addFK) {
587+
return errors.Newf("added FK shouldn't be commented out: %s\n%s", addFK, contents)
588+
}
570589
}
571-
for _, fk := range targetFKs {
572-
if !strings.Contains(contents, fk) {
573-
return errors.Newf("didn't find target FK: %s\n%s", fk, contents)
590+
for _, skipFK := range skipFKs {
591+
if !strings.Contains(contents, "-- "+skipFK) {
592+
return errors.Newf("didn't find skipped FK in commented out form: %s\n%s", skipFK, contents)
574593
}
575594
}
576595
}
@@ -580,8 +599,12 @@ CREATE TABLE users(id UUID DEFAULT gen_random_uuid() PRIMARY KEY, promo_id INT R
580599
// First read each table separately.
581600
for _, tableName := range tableNames {
582601
targetTableName := tableName
583-
// There should be no FKs included.
584-
contentCheck := getContentCheckFn([]string{targetTableName}, nil /* targetFKs */)
602+
// No FKs should be added, but 1 FK might be skipped.
603+
var skipFKs []string
604+
if skipFK := getFK(tableName); skipFK != "" {
605+
skipFKs = []string{skipFK}
606+
}
607+
contentCheck := getContentCheckFn([]string{targetTableName}, nil /* addFKS */, skipFKs)
585608
rows := r.QueryStr(t, "EXPLAIN ANALYZE (DEBUG) SELECT * FROM "+targetTableName)
586609
checkBundle(
587610
t, fmt.Sprint(rows), targetTableName, contentCheck, false, /* expectErrors */
@@ -590,26 +613,27 @@ CREATE TABLE users(id UUID DEFAULT gen_random_uuid() PRIMARY KEY, promo_id INT R
590613
)
591614
}
592615
// Now read different combinations of tables which will influence
593-
// whether ADD CONSTRAINT ... FOREIGN KEY statements are included.
594-
contentCheck := getContentCheckFn([]string{"parent", "child1"}, []string{"ALTER TABLE defaultdb.public.child1 ADD CONSTRAINT"})
616+
// whether ADD CONSTRAINT ... FOREIGN KEY statements are added or
617+
// skipped.
618+
contentCheck := getContentCheckFn([]string{"parent", "child1"}, []string{getFK("child1")}, nil)
595619
rows := r.QueryStr(t, "EXPLAIN ANALYZE (DEBUG) SELECT * FROM parent, child1")
596620
checkBundle(
597621
t, fmt.Sprint(rows), "parent", contentCheck, false, /* expectErrors */
598622
base, plans, "stats-defaultdb.public.parent.sql stats-defaultdb.public.child1.sql distsql.html vec.txt vec-v.txt",
599623
)
600624

601-
// There should be no FKs since there isn't a direct link between the
602-
// tables.
603-
contentCheck = getContentCheckFn([]string{"parent", "grandchild1"}, nil /* targetFKs */)
625+
// There should be no added FKs since there isn't a direct link between
626+
// the tables.
627+
contentCheck = getContentCheckFn([]string{"parent", "grandchild1"}, nil /* addFKS */, []string{getFK("grandchild1")})
604628
rows = r.QueryStr(t, "EXPLAIN ANALYZE (DEBUG) SELECT * FROM parent, grandchild1")
605629
checkBundle(
606630
t, fmt.Sprint(rows), "parent", contentCheck, false, /* expectErrors */
607631
base, plans, "stats-defaultdb.public.parent.sql stats-defaultdb.public.grandchild1.sql distsql.html vec.txt vec-v.txt",
608632
)
609633

610-
// Note that we omit the FK from grandchild1 since the FK referenced
634+
// Note that we skip the FK from grandchild1 since the FK referenced
611635
// table isn't being read.
612-
contentCheck = getContentCheckFn([]string{"parent", "child2", "grandchild1"}, []string{"ALTER TABLE defaultdb.public.child2 ADD CONSTRAINT"})
636+
contentCheck = getContentCheckFn([]string{"parent", "child2", "grandchild1"}, []string{getFK("child2")}, []string{getFK("grandchild1")})
613637
rows = r.QueryStr(t, "EXPLAIN ANALYZE (DEBUG) SELECT * FROM parent, child2, grandchild1")
614638
checkBundle(
615639
t, fmt.Sprint(rows), "parent", contentCheck, false, /* expectErrors */
@@ -618,10 +642,9 @@ CREATE TABLE users(id UUID DEFAULT gen_random_uuid() PRIMARY KEY, promo_id INT R
618642

619643
contentCheck = getContentCheckFn(
620644
[]string{"parent", "child1", "grandchild1"},
621-
[]string{
622-
"ALTER TABLE defaultdb.public.child1 ADD CONSTRAINT",
623-
"ALTER TABLE defaultdb.public.grandchild1 ADD CONSTRAINT",
624-
})
645+
[]string{getFK("child1"), getFK("grandchild1")},
646+
nil, /* skipFKs */
647+
)
625648
rows = r.QueryStr(t, "EXPLAIN ANALYZE (DEBUG) SELECT * FROM parent, child1, grandchild1")
626649
checkBundle(
627650
t, fmt.Sprint(rows), "parent", contentCheck, false, /* expectErrors */

pkg/sql/opt/exec/execbuilder/relational.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4356,12 +4356,23 @@ func (b *Builder) getEnvData() (exec.ExplainEnvData, error) {
43564356
refTableIncluded.Add(int(table.ID()))
43574357
},
43584358
)
4359-
envOpts.AddFKs = opt.GetAllFKsAmongTables(
4359+
var skipFKs []*tree.AlterTable
4360+
envOpts.AddFKs, skipFKs = opt.GetAllFKs(
4361+
b.ctx,
4362+
b.catalog,
43604363
refTables,
43614364
func(t cat.Table) (tree.TableName, error) {
43624365
return b.catalog.FullyQualifiedName(b.ctx, t)
43634366
},
43644367
)
4368+
// Given that we include all FK-related tables when visiting them, we should
4369+
// not skip any FKs - we'll return an error if we find any in test-only
4370+
// builds.
4371+
if buildutil.CrdbTestBuild {
4372+
if len(skipFKs) > 0 {
4373+
return envOpts, errors.AssertionFailedf("unexpectedly skipped adding FKs in EXPLAIN (OPT): %v", skipFKs)
4374+
}
4375+
}
43654376
var err error
43664377
envOpts.Tables, err = getNames(len(refTables), func(i int) cat.DataSource {
43674378
return refTables[i]

pkg/sql/opt/util.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,26 +104,53 @@ func VisitFKReferenceTables(
104104
}
105105
}
106106

107-
// GetAllFKsAmongTables returns a list of ALTER statements that corresponds to
108-
// all FOREIGN KEY constraints where both the origin and the referenced tables
109-
// are present in the given set of tables. List of the given tables is assumed
110-
// to be unique.
111-
func GetAllFKsAmongTables(
112-
tables []cat.Table, fullyQualifiedName func(cat.Table) (tree.TableName, error),
113-
) []*tree.AlterTable {
107+
// GetAllFKs returns a list of ALTER statements that corresponds to all FOREIGN
108+
// KEY constraints where both the origin and the referenced tables are present
109+
// in the given set of tables as well as a list of ALTER statements for all
110+
// FOREIGN KEY constraints where the origin table is in the given set while the
111+
// referenced one isn't.
112+
//
113+
// List of the given tables is assumed to be unique.
114+
func GetAllFKs(
115+
ctx context.Context,
116+
catalog cat.Catalog,
117+
tables []cat.Table,
118+
fullyQualifiedName func(cat.Table) (tree.TableName, error),
119+
) (addFKs, skipFKs []*tree.AlterTable) {
114120
idToTable := make(map[cat.StableID]cat.Table)
115121
for _, table := range tables {
116122
idToTable[table.ID()] = table
117123
}
118-
var addFKs []*tree.AlterTable
124+
skippedIDToTable := make(map[cat.StableID]cat.Table)
125+
resolveSkippedTable := func(tabID cat.StableID) cat.Table {
126+
if table, ok := skippedIDToTable[tabID]; ok {
127+
return table
128+
}
129+
ds, _, err := catalog.ResolveDataSourceByID(ctx, cat.Flags{}, tabID)
130+
if err != nil {
131+
return nil
132+
}
133+
t, ok := ds.(cat.Table)
134+
if !ok {
135+
return nil
136+
}
137+
skippedIDToTable[tabID] = t
138+
return t
139+
}
119140
for _, origTable := range tables {
120141
for i := 0; i < origTable.OutboundForeignKeyCount(); i++ {
142+
includeInto := &addFKs
121143
fk := origTable.OutboundForeignKey(i)
122144
refTable, ok := idToTable[fk.ReferencedTableID()]
123145
if !ok {
124-
// The referenced table is not in the given list, so we skip
125-
// this FK constraint.
126-
continue
146+
// The referenced table is not in the given list, so we include
147+
// this FK into the skipped list.
148+
includeInto = &skipFKs
149+
refTable = resolveSkippedTable(fk.ReferencedTableID())
150+
if refTable == nil {
151+
// This is a best-effort attempt to get skipped FKs.
152+
continue
153+
}
127154
}
128155
fromCols, toCols := make(tree.NameList, fk.ColumnCount()), make(tree.NameList, fk.ColumnCount())
129156
for j := range fromCols {
@@ -138,7 +165,7 @@ func GetAllFKsAmongTables(
138165
if err != nil {
139166
continue
140167
}
141-
addFKs = append(addFKs, &tree.AlterTable{
168+
*includeInto = append(*includeInto, &tree.AlterTable{
142169
Table: origTableName.ToUnresolvedObjectName(),
143170
Cmds: []tree.AlterTableCmd{
144171
&tree.AlterTableAddConstraint{
@@ -158,7 +185,7 @@ func GetAllFKsAmongTables(
158185
})
159186
}
160187
}
161-
return addFKs
188+
return addFKs, skipFKs
162189
}
163190

164191
// FamiliesForCols returns the set of column families for the set of cols.

0 commit comments

Comments
 (0)