-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
Copy pathdescriptor.go
325 lines (301 loc) · 11.4 KB
/
descriptor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
// Copyright 2021 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package descs
import (
"context"
"strings"
"github.com/cockroachdb/cockroach/pkg/keys"
"github.com/cockroachdb/cockroach/pkg/kv"
"github.com/cockroachdb/cockroach/pkg/sql/catalog"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catconstants"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/lease"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/schemadesc"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/errors"
)
// GetMutableDescriptorByID returns a mutable implementation of the descriptor
// with the requested id. An error is returned if no descriptor exists.
// Deprecated in favor of GetMutableDescriptorByIDWithFlags.
func (tc *Collection) GetMutableDescriptorByID(
ctx context.Context, id descpb.ID, txn *kv.Txn,
) (catalog.MutableDescriptor, error) {
return tc.GetMutableDescriptorByIDWithFlags(ctx, txn, id, tree.CommonLookupFlags{
IncludeOffline: true,
IncludeDropped: true,
})
}
// GetMutableDescriptorByIDWithFlags returns a mutable implementation of the
// descriptor with the requested id. An error is returned if no descriptor exists.
// TODO (lucy): This is meant to replace GetMutableDescriptorByID. Once it does,
// rename this function.
func (tc *Collection) GetMutableDescriptorByIDWithFlags(
ctx context.Context, txn *kv.Txn, id descpb.ID, flags tree.CommonLookupFlags,
) (catalog.MutableDescriptor, error) {
log.VEventf(ctx, 2, "planner getting mutable descriptor for id %d", id)
flags.RequireMutable = true
desc, err := tc.getDescriptorByID(ctx, txn, id, flags)
if err != nil {
return nil, err
}
return desc.(catalog.MutableDescriptor), nil
}
// GetImmutableDescriptorByID returns an immmutable implementation of the
// descriptor with the requested id. An error is returned if no descriptor exists.
// Deprecated in favor of GetMutableDescriptorByIDWithFlags.
func (tc *Collection) GetImmutableDescriptorByID(
ctx context.Context, txn *kv.Txn, id descpb.ID, flags tree.CommonLookupFlags,
) (catalog.Descriptor, error) {
log.VEventf(ctx, 2, "planner getting immutable descriptor for id %d", id)
flags.RequireMutable = false
return tc.getDescriptorByID(ctx, txn, id, flags)
}
func (tc *Collection) getDescriptorByID(
ctx context.Context, txn *kv.Txn, id descpb.ID, flags tree.CommonLookupFlags,
) (catalog.Descriptor, error) {
return tc.getDescriptorByIDMaybeSetTxnDeadline(
ctx, txn, id, flags, false /* setTxnDeadline */)
}
// getDescriptorByIDMaybeSetTxnDeadline returns a descriptor according to the
// provided lookup flags. Note that flags.Required is ignored, and an error is
// always returned if no descriptor with the ID exists.
func (tc *Collection) getDescriptorByIDMaybeSetTxnDeadline(
ctx context.Context, txn *kv.Txn, id descpb.ID, flags tree.CommonLookupFlags, setTxnDeadline bool,
) (catalog.Descriptor, error) {
getDescriptorByID := func() (catalog.Descriptor, error) {
vd, err := tc.virtual.getByID(ctx, id, flags.RequireMutable)
if vd != nil || err != nil {
return vd, err
}
if found, sd := tc.synthetic.getByID(id); found && !flags.AvoidSynthetic {
if flags.RequireMutable {
return nil, newMutableSyntheticDescriptorAssertionError(sd.GetID())
}
return sd, nil
}
{
ud := tc.uncommitted.getByID(id)
if ud != nil {
log.VEventf(ctx, 2, "found uncommitted descriptor %d", id)
if flags.RequireMutable {
ud, err = tc.uncommitted.checkOut(id)
if err != nil {
return nil, err
}
}
return ud, nil
}
}
if !flags.AvoidLeased && !flags.RequireMutable && !lease.TestingTableLeasesAreDisabled() {
// If we have already read all of the descriptors, use it as a negative
// cache to short-circuit a lookup we know will be doomed to fail.
//
// TODO(ajwerner): More generally leverage this set of kv descriptors on
// the resolution path.
if tc.kv.idDefinitelyDoesNotExist(id) {
return nil, catalog.ErrDescriptorNotFound
}
desc, shouldReadFromStore, err := tc.leased.getByID(ctx, tc.deadlineHolder(txn), id, setTxnDeadline)
if err != nil {
return nil, err
}
if !shouldReadFromStore {
return desc, nil
}
}
return tc.withReadFromStore(flags.RequireMutable, func() (catalog.MutableDescriptor, error) {
return tc.kv.getByID(ctx, txn, id)
})
}
desc, err := getDescriptorByID()
if err != nil {
return nil, err
}
if dropped, err := filterDescriptorState(desc, true /* required */, flags); err != nil || dropped {
// This is a special case for tables in the adding state: Roughly speaking,
// we always need to resolve tables in the adding state by ID when they were
// newly created in the transaction for DDL statements and for some
// information queries (but not for ordinary name resolution for queries/
// DML), but we also need to make these tables public in the schema change
// job in a separate transaction.
// TODO (lucy): We need something like an IncludeAdding flag so that callers
// can specify this behavior, instead of having the collection infer the
// desired behavior based on the flags (and likely producing unintended
// behavior). See the similar comment on etDescriptorByName, which covers
// the ordinary name resolution path as well as DDL statements.
if desc.Adding() && (desc.IsUncommittedVersion() || flags.AvoidLeased || flags.RequireMutable) {
return desc, nil
}
return nil, err
}
return desc, nil
}
func (tc *Collection) getByName(
ctx context.Context,
txn *kv.Txn,
db catalog.DatabaseDescriptor,
sc catalog.SchemaDescriptor,
name string,
avoidLeased, mutable, avoidSynthetic bool,
) (found bool, desc catalog.Descriptor, err error) {
var parentID, parentSchemaID descpb.ID
if db != nil {
if sc == nil {
// Schema descriptors are handled in a special way, see getSchemaByName
// function declaration for details.
return getSchemaByName(ctx, tc, txn, db, name, avoidLeased, mutable, avoidSynthetic)
}
parentID, parentSchemaID = db.GetID(), sc.GetID()
}
if found, sd := tc.synthetic.getByName(parentID, parentSchemaID, name); found && !avoidSynthetic {
if mutable {
return false, nil, newMutableSyntheticDescriptorAssertionError(sd.GetID())
}
return true, sd, nil
}
{
refuseFurtherLookup, ud := tc.uncommitted.getByName(parentID, parentSchemaID, name)
if ud != nil {
log.VEventf(ctx, 2, "found uncommitted descriptor %d", ud.GetID())
if mutable {
ud, err = tc.uncommitted.checkOut(ud.GetID())
if err != nil {
return false, nil, err
}
}
return true, ud, nil
}
if refuseFurtherLookup {
return false, nil, nil
}
}
if !avoidLeased && !mutable && !lease.TestingTableLeasesAreDisabled() {
var shouldReadFromStore bool
desc, shouldReadFromStore, err = tc.leased.getByName(ctx, tc.deadlineHolder(txn), parentID, parentSchemaID, name)
if err != nil {
return false, nil, err
}
if !shouldReadFromStore {
return desc != nil, desc, nil
}
}
desc, err = tc.withReadFromStore(mutable, func() (desc catalog.MutableDescriptor, err error) {
uncommittedDB, _ := tc.uncommitted.getByID(parentID).(catalog.DatabaseDescriptor)
return tc.kv.getByName(ctx, txn, uncommittedDB, parentID, parentSchemaID, name)
})
return desc != nil, desc, err
}
// withReadFromStore updates the state of the Collection, especially its
// uncommitted descriptors layer, after reading a descriptor from the storage
// layer. The logic is the same regardless of whether the descriptor was read
// by name or by ID.
func (tc *Collection) withReadFromStore(
requireMutable bool, readFn func() (catalog.MutableDescriptor, error),
) (desc catalog.Descriptor, _ error) {
mut, err := readFn()
if mut == nil || err != nil {
return nil, err
}
desc, err = tc.uncommitted.add(mut)
if err != nil {
return nil, err
}
if requireMutable {
desc, err = tc.uncommitted.checkOut(desc.GetID())
if err != nil {
return nil, err
}
}
tc.kv.releaseAllDescriptors()
return desc, nil
}
func (tc *Collection) deadlineHolder(txn *kv.Txn) deadlineHolder {
if tc.maxTimestampBoundDeadlineHolder.maxTimestampBound.IsEmpty() {
return txn
}
return &tc.maxTimestampBoundDeadlineHolder
}
// Getting a schema by name uses a special resolution path which can avoid
// a namespace lookup because the mapping of database to schema is stored on
// the database itself. This is an important optimization in the case when
// the schema does not exist.
//
// TODO(ajwerner): Understand and rationalize the namespace lookup given the
// schema lookup by ID path only returns descriptors owned by this session.
func getSchemaByName(
ctx context.Context,
tc *Collection,
txn *kv.Txn,
db catalog.DatabaseDescriptor,
name string,
avoidLeased, mutable, avoidSynthetic bool,
) (bool, catalog.Descriptor, error) {
if !db.HasPublicSchemaWithDescriptor() && name == tree.PublicSchema {
return true, schemadesc.GetPublicSchema(), nil
}
if sc := tc.virtual.getSchemaByName(name); sc != nil {
return true, sc, nil
}
if isTemporarySchema(name) {
if isDone, sc := tc.temporary.getSchemaByName(ctx, db.GetID(), name); sc != nil || isDone {
return sc != nil, sc, nil
}
scID, err := tc.kv.lookupName(ctx, txn, nil /* maybeDB */, db.GetID(), keys.RootNamespaceID, name)
if err != nil || scID == descpb.InvalidID {
return false, nil, err
}
return true, schemadesc.NewTemporarySchema(name, scID, db.GetID()), nil
}
if id := db.GetSchemaID(name); id != descpb.InvalidID {
// TODO(ajwerner): Fill in flags here or, more likely, get rid of
// it on this path.
sc, err := tc.getSchemaByID(ctx, txn, id, tree.SchemaLookupFlags{
RequireMutable: mutable,
AvoidLeased: avoidLeased,
AvoidSynthetic: avoidSynthetic,
})
if errors.Is(err, catalog.ErrDescriptorDropped) {
err = nil
}
return sc != nil, sc, err
}
return false, nil, nil
}
func isTemporarySchema(name string) bool {
return strings.HasPrefix(name, catconstants.PgTempSchemaName)
}
// filterDescriptorState wraps the more general catalog function to swallow
// the error if the descriptor is being dropped and the descriptor is not
// required. In that case, dropped will be true. A return value of false, nil
// means this descriptor is okay given the flags.
// TODO (lucy): We would like the ByID methods to ignore the Required flag and
// unconditionally return an error for dropped descriptors if IncludeDropped is
// not set, so we can't just pass the flags passed into the methods into this
// function, hence the boolean argument. This is the only user of
// catalog.FilterDescriptorState which needs to pass in nontrivial flags, at
// time of writing, so we should clean up the interface around this bit of
// functionality.
func filterDescriptorState(
desc catalog.Descriptor, required bool, flags tree.CommonLookupFlags,
) (dropped bool, _ error) {
flags = tree.CommonLookupFlags{
Required: required,
IncludeOffline: flags.IncludeOffline,
IncludeDropped: flags.IncludeDropped,
}
if err := catalog.FilterDescriptorState(desc, flags); err != nil {
if required || !errors.Is(err, catalog.ErrDescriptorDropped) {
return false, err
}
return true, nil
}
return false, nil
}