Skip to content

Commit f4603e5

Browse files
committed
catalog: allow offline descriptors for type hydrations
Previously, when a function referenced an implicit table record type, the entire schema would become inaccessible if the referenced table was executing an IMPORT. This would happen because looking up a schema involves hydrating all type references, which include implicit table record types. To address this, this patch allows for offline descriptors during the type hydration logic, since this scenario is possible during an import. Additionally, a test-only assertion is added to confirm this. Fixes: #149988 Release note (bug fix): Fixed a bug where the entire schema would become inaccessible if a table was referenced as an implicit record type by a UDF while the table was undergoing an IMPORT.
1 parent 99d44a0 commit f4603e5

File tree

4 files changed

+123
-11
lines changed

4 files changed

+123
-11
lines changed

pkg/sql/catalog/descs/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ go_library(
6565
"//pkg/sql/sqlerrors",
6666
"//pkg/sql/sqlliveness",
6767
"//pkg/sql/types",
68+
"//pkg/util/buildutil",
6869
"//pkg/util/hlc",
6970
"//pkg/util/intsets",
7071
"//pkg/util/iterutil",

pkg/sql/catalog/descs/descriptor.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,17 @@ func getDescriptorsByID(
215215
if err := tc.finalizeDescriptors(ctx, txn, flags, descs, vls); err != nil {
216216
return err
217217
}
218-
// Hydration is skipped if "SkipHydration" flag is true.
219-
if err := tc.hydrateDescriptors(ctx, txn, flags, descs); err != nil {
220-
return err
221-
}
218+
// Apply any filters on descriptors before hydrating, since if a descriptor
219+
// is offline / dropped, we are doing needless work.
222220
for _, desc := range descs {
223221
if err := filterDescriptor(desc, flags); err != nil {
224222
return err
225223
}
226224
}
225+
// Hydration is skipped if "SkipHydration" flag is true.
226+
if err := tc.hydrateDescriptors(ctx, txn, flags, descs); err != nil {
227+
return err
228+
}
227229
return nil
228230
}
229231

pkg/sql/catalog/descs/hydrate.go

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ import (
1414
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
1515
"github.com/cockroachdb/cockroach/pkg/sql/catalog/nstree"
1616
"github.com/cockroachdb/cockroach/pkg/sql/catalog/schemadesc"
17+
"github.com/cockroachdb/cockroach/pkg/sql/catalog/tabledesc"
1718
"github.com/cockroachdb/cockroach/pkg/sql/catalog/typedesc"
1819
"github.com/cockroachdb/cockroach/pkg/sql/sem/catconstants"
1920
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
2021
"github.com/cockroachdb/cockroach/pkg/sql/sqlerrors"
22+
"github.com/cockroachdb/cockroach/pkg/util/buildutil"
2123
"github.com/cockroachdb/cockroach/pkg/util/intsets"
2224
"github.com/cockroachdb/cockroach/pkg/util/tracing"
2325
"github.com/cockroachdb/errors"
@@ -67,7 +69,7 @@ func (tc *Collection) hydrateDescriptors(
6769

6870
// hydrate mutable hydratable descriptors of the slice in-place.
6971
if !hydratableMutableIndexes.Empty() {
70-
typeFn := makeMutableTypeLookupFunc(tc, txn, descs)
72+
typeFn := makeMutableTypeLookupFunc(tc, txn, flags, descs)
7173
for _, i := range hydratableMutableIndexes.Ordered() {
7274
if err := hydrate(ctx, descs[i], typeFn); err != nil {
7375
return err
@@ -108,7 +110,7 @@ func (tc *Collection) hydrateDescriptors(
108110
}
109111

110112
func makeMutableTypeLookupFunc(
111-
tc *Collection, txn *kv.Txn, descs []catalog.Descriptor,
113+
tc *Collection, txn *kv.Txn, flags getterFlags, descs []catalog.Descriptor,
112114
) typedesc.TypeLookupFunc {
113115
var mc nstree.MutableCatalog
114116
for _, desc := range descs {
@@ -131,7 +133,7 @@ func makeMutableTypeLookupFunc(
131133
if id == catconstants.PublicSchemaID {
132134
return schemadesc.GetPublicSchema(), nil
133135
}
134-
flags := getterFlags{
136+
f := getterFlags{
135137
contextFlags: contextFlags{
136138
isMutable: true,
137139
},
@@ -140,9 +142,35 @@ func makeMutableTypeLookupFunc(
140142
withoutLeased: true,
141143
withoutHydration: skipHydration,
142144
},
145+
descFilters: descFilters{
146+
// For hydration, we will allow offline descriptors for lookups
147+
// of type descriptors. A descriptor can be offline either due to:
148+
// 1) IMPORT in which case we are looking at an implicit record type
149+
// 2) RESTORE which will always pick a new object to restore into, so
150+
// only the restore code paths can enter here, which will include
151+
// offline descriptors explicitly.
152+
withoutOffline: false,
153+
},
154+
}
155+
g := ByIDGetter(makeGetterBase(txn, tc, f))
156+
desc, err := g.Desc(ctx, id)
157+
if err != nil {
158+
return nil, err
143159
}
144-
g := ByIDGetter(makeGetterBase(txn, tc, flags))
145-
return g.Desc(ctx, id)
160+
// Sanity: If offline descriptors were asked to be ignored, then we should
161+
// only observe ones caused by IMPORT.
162+
if flags.descFilters.withoutOffline &&
163+
(desc.GetOfflineReason() != "" && desc.GetOfflineReason() != tabledesc.OfflineReasonImporting) {
164+
if buildutil.CrdbTestBuild {
165+
return nil, errors.AssertionFailedf("unexpected offline descriptor %s(%d): %s",
166+
desc.GetName(),
167+
desc.GetID(),
168+
desc.GetOfflineReason())
169+
}
170+
// For release builds surface an offline descriptor error.
171+
return nil, catalog.FilterOfflineDescriptor(desc)
172+
}
173+
return desc, nil
146174
}
147175
return makeTypeLookupFuncForHydration(mc, mutableLookupFunc)
148176
}
@@ -169,11 +197,34 @@ func makeImmutableTypeLookupFunc(
169197
},
170198
descFilters: descFilters{
171199
withoutDropped: true,
172-
withoutOffline: flags.descFilters.withoutOffline,
200+
// For hydration, we will allow offline descriptors for lookups
201+
// of type descriptors. A descriptor can be offline either due to:
202+
// 1) IMPORT in which case we are looking at an implicit record type
203+
// 2) RESTORE which will always pick a new object to restore into, so
204+
// only the restore code paths can enter here, which will include
205+
// offline descriptors explicitly.
206+
withoutOffline: false,
173207
},
174208
}
175209
g := ByIDGetter(makeGetterBase(txn, tc, f))
176-
return g.Desc(ctx, id)
210+
desc, err := g.Desc(ctx, id)
211+
if err != nil {
212+
return nil, err
213+
}
214+
// Sanity: If offline descriptors were asked to be ignored, then we should
215+
// only observe ones caused by IMPORT.
216+
if flags.descFilters.withoutOffline &&
217+
(desc.GetOfflineReason() != "" && desc.GetOfflineReason() != tabledesc.OfflineReasonImporting) {
218+
if buildutil.CrdbTestBuild {
219+
return nil, errors.AssertionFailedf("unexpected offline descriptor %s(%d): %s",
220+
desc.GetName(),
221+
desc.GetID(),
222+
desc.GetOfflineReason())
223+
}
224+
// For release builds surface an offline descriptor error.
225+
return nil, catalog.FilterOfflineDescriptor(desc)
226+
}
227+
return desc, nil
177228
}
178229
return makeTypeLookupFuncForHydration(mc, immutableLookupFunc)
179230
}

pkg/sql/catalog/resolver/resolver_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,64 @@ CREATE INDEX baz_idx ON baz (s);
764764
require.NoError(t, err)
765765
}
766766

767+
// TestOfflineImplicitTableRecord confirms that offline implicit record references
768+
// do not impact unrelated objects.
769+
func TestOfflineImplicitTableRecord(t *testing.T) {
770+
defer leaktest.AfterTest(t)()
771+
defer log.Scope(t).Close(t)
772+
773+
ctx := context.Background()
774+
srv, sqlDB, _ := serverutils.StartServer(t, base.TestServerArgs{})
775+
defer srv.Stopper().Stop(ctx)
776+
s := srv.ApplicationLayer()
777+
tDB := sqlutils.MakeSQLRunner(sqlDB)
778+
779+
tDB.Exec(t, `
780+
CREATE DATABASE db1;
781+
USE db1;
782+
CREATE TABLE a (k INT PRIMARY KEY);
783+
784+
CREATE TABLE b (
785+
k INT PRIMARY KEY,
786+
a INT,
787+
b INT
788+
);
789+
790+
-- This function will have an implicit record reference to b.
791+
CREATE FUNCTION f(x INT) RETURNS b LANGUAGE SQL AS $$
792+
SELECT k, a, b FROM b WHERE k = x
793+
$$;
794+
`)
795+
// Mark the function as offline.
796+
var tblID descpb.ID
797+
err := sqltestutils.TestingDescsTxn(ctx, s, func(ctx context.Context, txn isql.Txn, col *descs.Collection) error {
798+
dbID, err := col.LookupDatabaseID(ctx, txn.KV(), "db1")
799+
if err != nil {
800+
return err
801+
}
802+
schemaID, err := col.LookupSchemaID(ctx, txn.KV(), dbID, "public")
803+
if err != nil {
804+
return err
805+
}
806+
tblID, err = col.LookupObjectID(ctx, txn.KV(), dbID, schemaID, "b")
807+
if err != nil {
808+
return err
809+
}
810+
mutTbl, err := col.MutableByID(txn.KV()).Table(ctx, tblID)
811+
if err != nil {
812+
return err
813+
}
814+
mutTbl.SetOffline(tabledesc.OfflineReasonImporting)
815+
return col.WriteDesc(ctx, false /*kvTrace*/, mutTbl, txn.KV())
816+
})
817+
require.NoError(t, err)
818+
// Confirm that we can still resolve things under the schema.
819+
tDB.Exec(t, "SELECT * FROM db1.public.a")
820+
// Confirm resolving the actual table hits an error.
821+
tDB.ExpectErr(t, "relation \"b\" is offline: importing", "SELECT * FROM db1.public.b")
822+
tDB.ExpectErr(t, "relation \"b\" is offline: importing", "SELECT * FROM f(0)")
823+
}
824+
767825
func newTableIndexName(db, sc, tbl, idx string) *tree.TableIndexName {
768826
name := &tree.TableIndexName{
769827
Index: tree.UnrestrictedName(idx),

0 commit comments

Comments
 (0)