251 lines
7.8 KiB
Go
251 lines
7.8 KiB
Go
// Copyright 2024 Dolthub, Inc.
|
||
//
|
||
// 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.
|
||
|
||
package pgcatalog
|
||
|
||
import (
|
||
"github.com/cockroachdb/errors"
|
||
"github.com/dolthub/go-mysql-server/sql"
|
||
|
||
"github.com/dolthub/doltgresql/core"
|
||
"github.com/dolthub/doltgresql/core/id"
|
||
"github.com/dolthub/doltgresql/server/functions"
|
||
)
|
||
|
||
// pgNamespace represents a row in the pg_namespace table.
|
||
// We store oids in their native format as well so that we can do range scans on them.
|
||
type pgNamespace struct {
|
||
oid id.Id
|
||
oidNative uint32
|
||
name string
|
||
}
|
||
|
||
// pgCatalogCache is a session cache that stores the contents of pg_catalog tables. Since this cache instance is only
|
||
// ever used by a single session, it does not include any synchronization for concurrent data access. A pgCatalogCache
|
||
// only caches data for the length of a single query, using |pid| to identify the current query. This means that the
|
||
// initial read of data from a pg_catalog table will always be generated fresh, then stored in this cache for any other
|
||
// table reads needed as part of that same query.
|
||
type pgCatalogCache struct {
|
||
// pid marks the process id for the query this cache data represents. pgCatalogCache instances currently only
|
||
// cache data within the context of a single query – not across multiple queries.
|
||
pid uint64
|
||
|
||
// pg_classes
|
||
pgClasses *pgClassCache
|
||
|
||
// pg_type
|
||
pgTypes *pgTypeCache
|
||
|
||
// pg_constraints
|
||
pgConstraints *pgConstraintCache
|
||
|
||
// pg_namespace
|
||
pgNamespaces *pgNamespaceCache
|
||
|
||
// pg_attribute
|
||
pgAttributes *pgAttributeCache
|
||
|
||
// pg_index / pg_indexes
|
||
pgIndexes *pgIndexCache
|
||
|
||
// pg_sequence / pg_sequences
|
||
sequences []*pgSequence
|
||
|
||
// pg_attrdef
|
||
attrdefCols []functions.ItemColumnDefault
|
||
attrdefTableOIDs []id.Id
|
||
|
||
// pg_views
|
||
views []sql.ViewDefinition
|
||
viewSchemas []string
|
||
|
||
// pg_tables
|
||
tables []pgTableRow
|
||
}
|
||
|
||
// pgClassCache holds cached data for the pg_class table, including two btree indexes for fast lookups by OID and
|
||
// by relname
|
||
type pgClassCache struct {
|
||
classes []*pgClass
|
||
nameIdx *inMemIndexStorage[*pgClass]
|
||
oidIdx *inMemIndexStorage[*pgClass]
|
||
}
|
||
|
||
// getIndex implements BTreeStorageAccess.
|
||
func (p pgClassCache) getIndex(name string) *inMemIndexStorage[*pgClass] {
|
||
switch name {
|
||
case "pg_class_oid_index":
|
||
return p.oidIdx
|
||
case "pg_class_relname_nsp_index":
|
||
return p.nameIdx
|
||
default:
|
||
panic("unknown pg_class index: " + name)
|
||
}
|
||
}
|
||
|
||
var _ BTreeStorageAccess[*pgClass] = &pgClassCache{}
|
||
|
||
// pgTypeCache holds cached data for the pg_type table, including two btree indexes for fast lookups by OID and
|
||
// by typname
|
||
type pgTypeCache struct {
|
||
types []*pgType
|
||
nameIdx *inMemIndexStorage[*pgType]
|
||
oidIdx *inMemIndexStorage[*pgType]
|
||
}
|
||
|
||
// getIndex implements BTreeStorageAccess.
|
||
func (p pgTypeCache) getIndex(name string) *inMemIndexStorage[*pgType] {
|
||
switch name {
|
||
case pgTypeOidIndex:
|
||
return p.oidIdx
|
||
case pgTypnameIndex:
|
||
return p.nameIdx
|
||
default:
|
||
panic("unknown pg_type index: " + name)
|
||
}
|
||
}
|
||
|
||
var _ BTreeStorageAccess[*pgType] = &pgTypeCache{}
|
||
|
||
// pgIndexCache holds cached data for the pg_index table, including two btree indexes for fast lookups by index OID
|
||
type pgIndexCache struct {
|
||
indexes []*pgIndex
|
||
tableNames map[id.Id]string
|
||
indexOidIdx *inMemIndexStorage[*pgIndex]
|
||
indrelidIdx *inMemIndexStorage[*pgIndex]
|
||
}
|
||
|
||
var _ BTreeStorageAccess[*pgIndex] = &pgIndexCache{}
|
||
|
||
// pgConstraintCache holds cached data for the pg_constraint table, including three btree indexes for fast lookups
|
||
type pgConstraintCache struct {
|
||
constraints []*pgConstraint
|
||
oidIdx *inMemIndexStorage[*pgConstraint]
|
||
relidTypNameIdx *inMemIndexStorage[*pgConstraint]
|
||
nameSchemaIdx *inMemIndexStorage[*pgConstraint]
|
||
typIdx *inMemIndexStorage[*pgConstraint]
|
||
}
|
||
|
||
var _ BTreeStorageAccess[*pgConstraint] = &pgConstraintCache{}
|
||
|
||
// getIndex implements BTreeStorageAccess.
|
||
func (p pgConstraintCache) getIndex(name string) *inMemIndexStorage[*pgConstraint] {
|
||
switch name {
|
||
case "pg_constraint_oid_index":
|
||
return p.oidIdx
|
||
case "pg_constraint_conrelid_contypid_conname_index":
|
||
return p.relidTypNameIdx
|
||
case "pg_constraint_conname_nsp_index":
|
||
return p.nameSchemaIdx
|
||
case "pg_constraint_contypid_index":
|
||
return p.typIdx
|
||
default:
|
||
panic("unknown pg_constraint index: " + name)
|
||
}
|
||
}
|
||
|
||
// pgAttributeCache holds cached data for the pg_attribute table, including two btree indexes for fast lookups
|
||
type pgAttributeCache struct {
|
||
attributes []*pgAttribute
|
||
attrelidIdx *inMemIndexStorage[*pgAttribute]
|
||
attrelidAttnameIdx *inMemIndexStorage[*pgAttribute]
|
||
}
|
||
|
||
var _ BTreeStorageAccess[*pgAttribute] = &pgAttributeCache{}
|
||
|
||
// pgNamespaceCache holds cached data for the pg_namespace table, including two btree indexes for fast lookups by OID and by name
|
||
type pgNamespaceCache struct {
|
||
namespaces []*pgNamespace
|
||
oidIdx *inMemIndexStorage[*pgNamespace]
|
||
nameIdx *inMemIndexStorage[*pgNamespace]
|
||
}
|
||
|
||
var _ BTreeStorageAccess[*pgNamespace] = &pgNamespaceCache{}
|
||
|
||
// getIndex implements BTreeStorageAccess.
|
||
func (p pgNamespaceCache) getIndex(name string) *inMemIndexStorage[*pgNamespace] {
|
||
switch name {
|
||
case "pg_namespace_oid_index":
|
||
return p.oidIdx
|
||
case "pg_namespace_nspname_index":
|
||
return p.nameIdx
|
||
default:
|
||
panic("unknown pg_namespace index: " + name)
|
||
}
|
||
}
|
||
|
||
func (p pgAttributeCache) getIndex(name string) *inMemIndexStorage[*pgAttribute] {
|
||
switch name {
|
||
case "pg_attribute_relid_attnum_index":
|
||
return p.attrelidIdx
|
||
case "pg_attribute_relid_attnam_index":
|
||
return p.attrelidAttnameIdx
|
||
default:
|
||
panic("unknown pg_attribute index: " + name)
|
||
}
|
||
}
|
||
|
||
// getIndex implements BTreeStorageAccess.
|
||
func (p pgIndexCache) getIndex(name string) *inMemIndexStorage[*pgIndex] {
|
||
switch name {
|
||
case "pg_index_indexrelid_index":
|
||
return p.indexOidIdx
|
||
case "pg_index_indrelid_index":
|
||
return p.indrelidIdx
|
||
default:
|
||
panic("unknown pg_index index: " + name)
|
||
}
|
||
}
|
||
|
||
// newPgCatalogCache creates a new pgCatalogCache, with the query/process ID set to |pid|. The PID is important,
|
||
// since pgCatalogCache instances only cache data for the duration of a single query.
|
||
func newPgCatalogCache(pid uint64) *pgCatalogCache {
|
||
return &pgCatalogCache{
|
||
pid: pid,
|
||
}
|
||
}
|
||
|
||
// getPgCatalogCache returns the pgCatalogCache instance for the current query in this session. If no cache exists
|
||
// yet, then a new one is created and returned. Note that pgCatalogCache only caches catalog data for a single query,
|
||
// so if the PID of the current context does not match the PID of the context when the pgCatalogCache was created,
|
||
// then a new cache will be created.
|
||
func getPgCatalogCache(ctx *sql.Context) (*pgCatalogCache, error) {
|
||
untypedPgCatalogCache, err := core.GetPgCatalogCache(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if untypedPgCatalogCache == nil {
|
||
return initializeNewPgCatalogCache(ctx)
|
||
}
|
||
|
||
cache, ok := untypedPgCatalogCache.(*pgCatalogCache)
|
||
if !ok {
|
||
return nil, errors.Errorf("unexpected type %T for pg_catalog cache", untypedPgCatalogCache)
|
||
}
|
||
if cache.pid != ctx.Pid() {
|
||
return initializeNewPgCatalogCache(ctx)
|
||
}
|
||
return cache, nil
|
||
}
|
||
|
||
// initializeNewPgCatalogCache creates a new pgCatalogCache instance and sets it in the context. This function should
|
||
// not be used directly, and should only be used directly by getPgCatalogCache(*sql.Context).
|
||
func initializeNewPgCatalogCache(ctx *sql.Context) (*pgCatalogCache, error) {
|
||
cache := newPgCatalogCache(ctx.Pid())
|
||
if err := core.SetPgCatalogCache(ctx, cache); err != nil {
|
||
return nil, err
|
||
}
|
||
return cache, nil
|
||
}
|