forked from cockroachdb/cockroach
/
virtual_schema.go
224 lines (200 loc) · 7.56 KB
/
virtual_schema.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
// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// Author: Nathan VanBenschoten (nvanbenschoten@gmail.com)
package sql
import (
"fmt"
"sort"
"github.com/cockroachdb/cockroach/keys"
"github.com/cockroachdb/cockroach/sql/parser"
"github.com/cockroachdb/cockroach/sql/sqlbase"
)
//
// Programmer interface to define virtual schemas.
//
// virtualSchema represents a database with a set of virtual tables. Virtual
// tables differ from standard tables in that they are not persisted to storage,
// and instead their contents are populated whenever they are queried.
//
// The virtual database and its virtual tables also differ from standard databases
// and tables in that their descriptors are not distributed, but instead live statically
// in code. This means that they are accessed separately from standard descriptors.
type virtualSchema struct {
name string
tables []virtualSchemaTable
}
// virtualSchemaTable represents a table within a virtualSchema.
// The table used at runtime is the TableDescriptor in the `desc` field.
// The schema strings are *not* used at runtime, but should be checked by a test
// like `TestVirtualTableLiterals` that compares the table generated by
// evaluating the `CREATE TABLE` statement to the descriptor literal.
// The `TestVirtualTableLiterals` test is also useful for authoring new literals
// as one can write the CREATE statement and just use a dummy/blank literal --
// when the test then fails on the dummy desc, it prints out the descriptor it
// expected, which (sometimes with a little cleanup by hand) can be pasted into
// the `desc` field.
type virtualSchemaTable struct {
schema string
desc sqlbase.TableDescriptor
populate func(p *planner, addRow func(...parser.Datum) error) error
}
// virtualSchemas holds a slice of statically registered virtualSchema objects.
//
// When adding a new virtualSchema, define a virtualSchema in a separate file, and
// add that object to this slice.
var virtualSchemas = []virtualSchema{
informationSchema,
}
//
// SQL-layer interface to work with virtual schemas.
//
// Statically populated map used to provide convenient access to virtual
// database and table descriptors. virtualSchemaMap, virtualSchemaEntry,
// and virtualTableEntry make up the statically generated data structure
// which the virtualSchemas slice is mapped to. Because of this, they
// should not be created directly, but instead will be populated in the
// init function below.
var virtualSchemaMap map[string]virtualSchemaEntry
type virtualSchemaEntry struct {
desc *sqlbase.DatabaseDescriptor
tables map[string]virtualTableEntry
orderedTableNames []string
}
func (e virtualSchemaEntry) tableNames() parser.TableNames {
var res parser.TableNames
for _, tableName := range e.orderedTableNames {
tn := parser.TableName{
DatabaseName: parser.Name(e.desc.Name),
TableName: parser.Name(tableName),
}
res = append(res, tn)
}
return res
}
type virtualTableEntry struct {
tableDef virtualSchemaTable
desc *sqlbase.TableDescriptor
}
// getValuesNode returns a new valuesNode for the virtual table using the
// provided planner.
func (e virtualTableEntry) getValuesNode(p *planner) (*valuesNode, error) {
var columns ResultColumns
for _, col := range e.desc.Columns {
columns = append(columns, ResultColumn{
Name: col.Name,
Typ: col.Type.ToDatumType(),
})
}
v := p.newContainerValuesNode(columns, 0)
err := e.tableDef.populate(p, func(datum ...parser.Datum) error {
if r, c := len(datum), len(v.columns); r != c {
panic(fmt.Sprintf("datum row count and column count differ: %d vs %d", r, c))
}
return v.rows.AddRow(datum)
})
if err != nil {
v.Close()
return nil, err
}
return v, nil
}
func init() {
virtualSchemaMap = make(map[string]virtualSchemaEntry, len(virtualSchemas))
for _, schema := range virtualSchemas {
dbName := schema.name
dbDesc := initVirtualDatabaseDesc(dbName)
tables := make(map[string]virtualTableEntry, len(schema.tables))
orderedTableNames := make([]string, 0, len(schema.tables))
for _, table := range schema.tables {
tableDesc := initVirtualTableDesc(table)
tables[tableDesc.Name] = virtualTableEntry{
tableDef: table,
desc: tableDesc,
}
orderedTableNames = append(orderedTableNames, tableDesc.Name)
}
sort.Strings(orderedTableNames)
virtualSchemaMap[dbName] = virtualSchemaEntry{
desc: dbDesc,
tables: tables,
orderedTableNames: orderedTableNames,
}
}
}
// Virtual databases and tables each have an empty set of privileges. In practice,
// all users have SELECT privileges on the database/tables, but this is handled
// separately from normal SELECT privileges, because the virtual schemas need more
// fine-grained access control. For instance, information_schema will only expose
// rows to a given user which that user has access to.
var emptyPrivileges = &sqlbase.PrivilegeDescriptor{}
func initVirtualDatabaseDesc(name string) *sqlbase.DatabaseDescriptor {
return &sqlbase.DatabaseDescriptor{
Name: name,
ID: keys.VirtualDescriptorID,
Privileges: emptyPrivileges,
}
}
func initVirtualTableDesc(t virtualSchemaTable) *sqlbase.TableDescriptor {
desc := t.desc
desc.ParentID = 0
desc.ID = keys.VirtualDescriptorID
desc.Privileges = emptyPrivileges
return &desc
}
// getVirtualSchemaEntry retrieves a virtual schema entry given a database name.
func getVirtualSchemaEntry(name string) (virtualSchemaEntry, bool) {
e, ok := virtualSchemaMap[name]
return e, ok
}
// getVirtualDatabaseDesc checks if the provided name matches a virtual database,
// and if so, returns that database's descriptor.
func getVirtualDatabaseDesc(name string) *sqlbase.DatabaseDescriptor {
if e, ok := getVirtualSchemaEntry(name); ok {
return e.desc
}
return nil
}
// IsVirtualDatabase checks if the provided name corresponds to a virtual database.
func IsVirtualDatabase(name string) bool {
_, ok := getVirtualSchemaEntry(name)
return ok
}
// getVirtualTableEntry checks if the provided name matches a virtual database/table
// pair. The function will return the table's virtual table entry if the name matches
// a specific table. It will return an error if the name references a virtual database
// but the table is non-existent.
func getVirtualTableEntry(tn *parser.TableName) (virtualTableEntry, error) {
if db, ok := getVirtualSchemaEntry(tn.Database()); ok {
if t, ok := db.tables[sqlbase.NormalizeName(tn.TableName)]; ok {
return t, nil
}
return virtualTableEntry{}, sqlbase.NewUndefinedTableError(tn.String())
}
return virtualTableEntry{}, nil
}
// getVirtualTableDesc checks if the provided name matches a virtual database/table
// pair, and returns its descriptor if it does.
func getVirtualTableDesc(tn *parser.TableName) (*sqlbase.TableDescriptor, error) {
t, err := getVirtualTableEntry(tn)
if err != nil {
return nil, err
}
return t.desc, nil
}
// isVirtualDescriptor checks if the provided DescriptorProto is an instance of
// a Virtual Descriptor.
func isVirtualDescriptor(desc sqlbase.DescriptorProto) bool {
return desc.GetID() == keys.VirtualDescriptorID
}