/
search_path.go
226 lines (204 loc) · 8.06 KB
/
search_path.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
// Copyright 2016 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 sessiondata
import (
"strings"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
)
// PgDatabaseName is the name of the default postgres system database.
const PgDatabaseName = "postgres"
// DefaultTemporarySchemaName is the temporary schema new sessions that have not
// created a temporary table start off with.
// This is prefixed with `pg_` to ensure there is no clash with a user defined
// schema if/when CRDB supports them. In PG, schema names starting with `pg_`
// are "reserved", so this can never clash with an actual physical schema.
const DefaultTemporarySchemaName = "pg_no_temp_schema"
// DefaultDatabaseName is the name ofthe default CockroachDB database used
// for connections without a current db set.
const DefaultDatabaseName = "defaultdb"
// PgCatalogName is the name of the pg_catalog system schema.
const PgCatalogName = "pg_catalog"
// PgTempSchemaName is the alias for temporary schemas across sessions.
const PgTempSchemaName = "pg_temp"
// SearchPath represents a list of namespaces to search builtins in.
// The names must be normalized (as per Name.Normalize) already.
type SearchPath struct {
paths []string
containsPgCatalog bool
containsPgTempSchema bool
tempSchemaName string
}
// MakeSearchPath returns a new immutable SearchPath struct. The paths slice
// must not be modified after hand-off to MakeSearchPath.
func MakeSearchPath(paths []string, tempSchemaName string) SearchPath {
containsPgCatalog := false
containsPgTempSchema := false
for _, e := range paths {
if e == PgCatalogName {
containsPgCatalog = true
} else if e == PgTempSchemaName {
containsPgTempSchema = true
}
}
return SearchPath{
paths: paths,
containsPgCatalog: containsPgCatalog,
containsPgTempSchema: containsPgTempSchema,
tempSchemaName: tempSchemaName,
}
}
// WithTemporarySchemaName returns a new immutable SearchPath struct with
// the tempSchemaName supplied and the same paths as before.
// This should be called every time a session creates a temporary schema
// for the first time.
func (s SearchPath) WithTemporarySchemaName(tempSchemaName string) SearchPath {
return SearchPath{
paths: s.paths,
containsPgCatalog: s.containsPgCatalog,
containsPgTempSchema: s.containsPgTempSchema,
tempSchemaName: tempSchemaName,
}
}
// UpdatePaths returns a new immutable SearchPath struct with the paths supplied
// and the same tempSchemaName as before.
func (s SearchPath) UpdatePaths(paths []string) SearchPath {
return MakeSearchPath(paths, s.tempSchemaName)
}
// MaybeResolveTemporarySchema returns the session specific temporary schema
// for the pg_temp alias (only if a temporary schema exists). It acts as a pass
// through for all other schema names.
func (s SearchPath) MaybeResolveTemporarySchema(schemaName string) (string, error) {
// Only allow access to the session specific temporary schema.
if strings.HasPrefix(schemaName, PgTempSchemaName) && schemaName != PgTempSchemaName && schemaName != s.tempSchemaName {
return schemaName, pgerror.New(pgcode.FeatureNotSupported, "cannot access temporary tables of other sessions")
}
// If the schemaName is pg_temp and the tempSchemaName has been set, pg_temp
// is an alias the session specific temp schema.
if schemaName == PgTempSchemaName && s.tempSchemaName != DefaultTemporarySchemaName {
return s.tempSchemaName, nil
}
return schemaName, nil
}
// Iter returns an iterator through the search path. We must include the
// implicit pg_catalog and temporary schema at the beginning of the search path,
// unless they have been explicitly set later by the user.
// "The system catalog schema, pg_catalog, is always searched, whether it is
// mentioned in the path or not. If it is mentioned in the path then it will be
// searched in the specified order. If pg_catalog is not in the path then it
// will be searched before searching any of the path items."
// "Likewise, the current session's temporary-table schema, pg_temp_nnn, is
// always searched if it exists. It can be explicitly listed in the path by
// using the alias pg_temp. If it is not listed in the path then it is searched
// first (even before pg_catalog)."
// - https://www.postgresql.org/docs/9.1/static/runtime-config-client.html
func (s SearchPath) Iter() SearchPathIter {
implicitPgTempSchema := !s.containsPgTempSchema && s.tempSchemaName != DefaultTemporarySchemaName
sp := SearchPathIter{
paths: s.paths,
implicitPgCatalog: !s.containsPgCatalog,
implicitPgTempSchema: implicitPgTempSchema,
}
if s.tempSchemaName != DefaultTemporarySchemaName {
sp.tempSchemaName = s.tempSchemaName
}
return sp
}
// IterWithoutImplicitPGSchemas is the same as Iter, but does not include the
// implicit pg_temp and pg_catalog.
func (s SearchPath) IterWithoutImplicitPGSchemas() SearchPathIter {
sp := SearchPathIter{
paths: s.paths,
implicitPgCatalog: false,
implicitPgTempSchema: false,
}
if s.tempSchemaName != DefaultTemporarySchemaName {
sp.tempSchemaName = s.tempSchemaName
}
return sp
}
// GetPathArray returns the underlying path array of this SearchPath. The
// resultant slice is not to be modified.
func (s SearchPath) GetPathArray() []string {
return s.paths
}
// GetTemporarySchemaName returns the temporary schema specific to the current
// session.
func (s SearchPath) GetTemporarySchemaName() string {
return s.tempSchemaName
}
// Equals returns true if two SearchPaths are the same.
func (s SearchPath) Equals(other *SearchPath) bool {
if s.containsPgCatalog != other.containsPgCatalog {
return false
}
if s.containsPgTempSchema != other.containsPgTempSchema {
return false
}
if len(s.paths) != len(other.paths) {
return false
}
if s.tempSchemaName != other.tempSchemaName {
return false
}
// Fast path: skip the check if it is the same slice.
if &s.paths[0] != &other.paths[0] {
for i := range s.paths {
if s.paths[i] != other.paths[i] {
return false
}
}
}
return true
}
func (s SearchPath) String() string {
return strings.Join(s.paths, ", ")
}
// SearchPathIter enables iteration over the search paths without triggering an
// allocation. Use one of the SearchPath.Iter methods to get an instance of the
// iterator, and then repeatedly call the Next method in order to iterate over
// each search path. The tempSchemaName in the iterator is only set if the session
// has created a temporary schema.
type SearchPathIter struct {
paths []string
implicitPgCatalog bool
implicitPgTempSchema bool
tempSchemaName string
i int
}
// Next returns the next search path, or false if there are no remaining paths.
func (iter *SearchPathIter) Next() (path string, ok bool) {
// If the session specific temporary schema has not been created, we can
// preempt the name resolution failure by simply skipping the implicit pg_temp.
if iter.implicitPgTempSchema && iter.tempSchemaName != "" {
iter.implicitPgTempSchema = false
return iter.tempSchemaName, true
}
if iter.implicitPgCatalog {
iter.implicitPgCatalog = false
return PgCatalogName, true
}
if iter.i < len(iter.paths) {
iter.i++
// If pg_temp is explicitly present in the paths, it must be resolved to the
// session specific temp schema (if one exists). tempSchemaName is set in the
// iterator iff the session has created a temporary schema.
if iter.paths[iter.i-1] == PgTempSchemaName {
// If the session specific temporary schema has not been created we can
// preempt the resolution failure and iterate to the next entry.
if iter.tempSchemaName == "" {
return iter.Next()
}
return iter.tempSchemaName, true
}
return iter.paths[iter.i-1], true
}
return "", false
}