Files
2026-07-13 12:32:25 +08:00

851 lines
27 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 functions
import (
"sort"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/core/sequences"
"github.com/dolthub/doltgresql/core/typecollection"
"github.com/dolthub/doltgresql/server/tables"
"github.com/dolthub/doltgresql/server/types"
)
// Callbacks are a set of callbacks that are used to simplify and coalesce all iteration involving database elements and
// their OIDs. All callbacks should be left nil except for the ones that are desired. Search paths are also supported
// through SearchSchemas.
type Callbacks struct {
// Check is the callback for check constraints.
Check func(ctx *sql.Context, schema ItemSchema, table ItemTable, check ItemCheck) (cont bool, err error)
// ColumnDefault is the callback for column defaults.
ColumnDefault func(ctx *sql.Context, schema ItemSchema, table ItemTable, check ItemColumnDefault) (cont bool, err error)
// ForeignKey is the callback for foreign keys.
ForeignKey func(ctx *sql.Context, schema ItemSchema, table ItemTable, foreignKey ItemForeignKey) (cont bool, err error)
// Index is the callback for indexes.
Index func(ctx *sql.Context, schema ItemSchema, table ItemTable, index ItemIndex) (cont bool, err error)
// Schema is the callback for schemas/namespaces.
Schema func(ctx *sql.Context, schema ItemSchema) (cont bool, err error)
// Sequence is the callback for sequences.
Sequence func(ctx *sql.Context, schema ItemSchema, sequence ItemSequence) (cont bool, err error)
// Table is the callback for tables.
Table func(ctx *sql.Context, schema ItemSchema, table ItemTable) (cont bool, err error)
// Type is the callback for types.
Type func(ctx *sql.Context, schema ItemSchema, typ ItemType) (cont bool, err error)
// View is the callback for views.
View func(ctx *sql.Context, schema ItemSchema, view ItemView) (cont bool, err error)
// SearchSchemas represents the search path. If left empty, then all schemas are iterated over. If supplied, then
// schemas are iterated by their given order.
SearchSchemas []string
}
// ItemCheck contains the relevant information to pass to the Check callback.
type ItemCheck struct {
OID id.Check
Item sql.CheckDefinition
}
// ColumnWithIndex is a helper struct to pass the column and its index to the ColumnDefault callback.
type ColumnWithIndex struct {
Column *sql.Column
ColumnIndex int
}
// ItemColumnDefault contains the relevant information to pass to the ColumnDefault callback.
type ItemColumnDefault struct {
OID id.ColumnDefault
Item ColumnWithIndex
}
// ItemForeignKey contains the relevant information to pass to the ForeignKey callback.
type ItemForeignKey struct {
OID id.ForeignKey
Item sql.ForeignKeyConstraint
}
// ItemIndex contains the relevant information to pass to the Index callback.
type ItemIndex struct {
OID id.Index
Item sql.Index
}
// ItemSchema contains the relevant information to pass to the Schema callback.
type ItemSchema struct {
OID id.Namespace
Item sql.DatabaseSchema
}
func (is ItemSchema) IsSystemSchema() bool {
return is.Item.SchemaName() == "information_schema" || is.Item.SchemaName() == "pg_catalog"
}
// ItemSequence contains the relevant information to pass to the Sequence callback.
type ItemSequence struct {
OID id.Sequence
Item *sequences.Sequence
}
// ItemTable contains the relevant information to pass to the Table callback.
type ItemTable struct {
OID id.Table
Item sql.Table
}
// ItemType contains the relevant information to pass to the Type callback.
type ItemType struct {
Oid id.Type
Item *types.DoltgresType
}
// ItemView contains the relevant information to pass to the View callback.
type ItemView struct {
OID id.View
Item sql.ViewDefinition
}
// IterateDatabase iterates over the provided database, calling each callback as the relevant items are iterated
// over. This is a central function that homogenizes all iteration, since OIDs depend on a deterministic iteration over
// items. This function should be expanded as we add more items to iterate over.
func IterateDatabase(ctx *sql.Context, database string, callbacks Callbacks) error {
sess := ctx.Session.(*dsess.DoltSession)
currentDatabase, err := sess.Provider().Database(ctx, database)
if err != nil {
return err
}
// TODO: Consider overriding Provider.Database() to always wrap the returned db
currentDatabase = tables.WrapSqlDatabase(currentDatabase)
// Then we'll iterate over everything that is contained within a schema
if currentSchemaDatabase, ok := currentDatabase.(sql.SchemaDatabase); ok && callbacks.iteratesOverSchemas() {
// Load and sort all schemas by name ascending
schemas, err := currentSchemaDatabase.AllSchemas(ctx)
if err != nil {
return err
}
sort.Slice(schemas, func(i, j int) bool {
return schemas[i].SchemaName() < schemas[j].SchemaName()
})
// We'll load the sequences early (if the callback exists) since they're stored on the root.
// Within the schema iteration, we can simply
var sequenceMap map[string][]*sequences.Sequence
if callbacks.Sequence != nil {
collection, err := core.GetSequencesCollectionFromContext(ctx, database)
if err != nil {
return err
}
sequenceMap, _, _, err = collection.GetAllSequences(ctx)
if err != nil {
return err
}
}
var typeMap map[string][]*types.DoltgresType
if callbacks.Type != nil {
coll, err := core.GetTypesCollectionFromContext(ctx, database)
if err != nil {
return err
}
typeMap = typesBySchema(ctx, coll)
}
if err = iterateSchemas(ctx, callbacks, schemas, sequenceMap, typeMap); err != nil {
return err
}
}
return nil
}
// typesBySchema returns a map of schema name to types within that schema.
func typesBySchema(ctx *sql.Context, coll *typecollection.TypeCollection) map[string][]*types.DoltgresType {
m := make(map[string][]*types.DoltgresType)
_ = coll.IterateTypes(ctx, func(typ *types.DoltgresType) (stop bool, err error) {
m[typ.Schema()] = append(m[typ.Schema()], typ)
return false, nil
})
return m
}
// IterateCurrentDatabase iterates over the current database, calling each callback as the relevant items are iterated
// over. This is a central function that homogenizes all iteration, since OIDs depend on a deterministic iteration over
// items. This function should be expanded as we add more items to iterate over.
func IterateCurrentDatabase(ctx *sql.Context, callbacks Callbacks) error {
return IterateDatabase(ctx, ctx.GetCurrentDatabase(), callbacks)
}
// iterateSchemas is called by IterateCurrentDatabase to handle schemas and elements contained within schemas.
func iterateSchemas(
ctx *sql.Context,
callbacks Callbacks,
sortedSchemas []sql.DatabaseSchema,
sequenceMap map[string][]*sequences.Sequence,
typeMap map[string][]*types.DoltgresType,
) error {
// Iterate over the sorted schemas by the iteration order
for _, schemaIndex := range callbacks.schemaIterationOrder(sortedSchemas) {
schema := sortedSchemas[schemaIndex]
itemSchema := ItemSchema{
OID: id.NewNamespace(schema.SchemaName()),
Item: schema,
}
// Check for a schema callback
if callbacks.Schema != nil {
if cont, err := callbacks.Schema(ctx, itemSchema); err != nil {
return err
} else if !cont {
return nil
}
}
// Check for a view callback
if callbacks.View != nil {
if err := iterateViews(ctx, callbacks, itemSchema); err != nil {
return err
}
}
err := iterateSequences(ctx, callbacks, sequenceMap, schema, itemSchema)
if err != nil {
return err
}
// Check if we need to iterate over tables
if callbacks.iteratesOverTables() {
tableNames, err := schema.GetTableNames(ctx)
if err != nil {
return err
}
sort.Slice(tableNames, func(i, j int) bool {
return tableNames[i] < tableNames[j]
})
if err = iterateTables(ctx, callbacks, itemSchema, tableNames); err != nil {
return err
}
}
if callbacks.iteratesOverTypes() {
if err := iterateTypes(ctx, callbacks, itemSchema, typeMap); err != nil {
return err
}
}
}
return nil
}
// iterateSequences is called by iterateSchemas to handle sequence callbacks
func iterateSequences(ctx *sql.Context, callbacks Callbacks, sequenceMap map[string][]*sequences.Sequence, schema sql.DatabaseSchema, itemSchema ItemSchema) error {
for _, sequence := range sequenceMap[schema.SchemaName()] {
itemSequence := ItemSequence{
OID: sequence.Id,
Item: sequence,
}
if cont, err := callbacks.Sequence(ctx, itemSchema, itemSequence); err != nil {
return err
} else if !cont {
return nil
}
}
return nil
}
// iterateTypes is called by iterateSchemas to handle type callbacks
func iterateTypes(ctx *sql.Context, callbacks Callbacks, itemSchema ItemSchema, typeMap map[string][]*types.DoltgresType) error {
for _, typ := range typeMap[itemSchema.Item.SchemaName()] {
itemSchemaType := ItemType{
Oid: typ.ID,
Item: typ,
}
cont, err := callbacks.Type(ctx, itemSchema, itemSchemaType)
if err != nil {
return err
} else if !cont {
return nil
}
}
return nil
}
// iterateViews is called by iterateSchemas to handle views.
func iterateViews(ctx *sql.Context, callbacks Callbacks, itemSchema ItemSchema) error {
if viewDatabase, ok := itemSchema.Item.(sql.ViewDatabase); ok {
views, err := viewDatabase.AllViews(ctx)
if err != nil {
return err
}
sort.Slice(views, func(i, j int) bool {
return views[i].Name < views[j].Name
})
for _, view := range views {
itemView := ItemView{
OID: id.NewView(itemSchema.Item.SchemaName(), view.Name),
Item: view,
}
if cont, err := callbacks.View(ctx, itemSchema, itemView); err != nil {
return err
} else if !cont {
return nil
}
}
}
return nil
}
// iterateTables is called by iterateSchemas to handle tables and elements contained within tables.
func iterateTables(ctx *sql.Context, callbacks Callbacks, itemSchema ItemSchema, sortedTableNames []string) error {
// Start all of the counts at -1, since we always increment before using the count.
checkCount := -1
columnDefaultCount := -1
foreignKeyCount := -1
indexCount := -1
// Iterate over the sorted table names
for _, tableName := range sortedTableNames {
table, ok, err := itemSchema.Item.GetTableInsensitive(ctx, tableName)
if err != nil && !errors.Is(err, doltdb.ErrTableNotFound) {
return err
} else if !ok {
// We receive these names from the database, so these must be the names of root objects
continue
}
itemTable := ItemTable{
OID: id.NewTable(itemSchema.Item.SchemaName(), table.Name()),
Item: table,
}
// Check for a check constraint callback
if callbacks.Check != nil {
if err = iterateChecks(ctx, callbacks, itemSchema, itemTable, &checkCount); err != nil {
return err
}
}
// Check for a column default callback
if callbacks.ColumnDefault != nil {
// System tables don't have column defaults
if itemSchema.IsSystemSchema() {
continue
}
if err = iterateColumnDefaults(ctx, callbacks, itemSchema, itemTable, &columnDefaultCount); err != nil {
return err
}
}
// Check for a foreign key callback
if callbacks.ForeignKey != nil {
// System tables don't have foreign keys
if itemSchema.IsSystemSchema() {
continue
}
if err = iterateForeignKeys(ctx, callbacks, itemSchema, itemTable, &foreignKeyCount); err != nil {
return err
}
}
// Check for an index callback
if callbacks.Index != nil {
if err = iterateIndexes(ctx, callbacks, itemSchema, itemTable, &indexCount); err != nil {
return err
}
}
// Check for a table callback
if callbacks.Table != nil {
if cont, err := callbacks.Table(ctx, itemSchema, itemTable); err != nil {
return err
} else if !cont {
return nil
}
}
}
return nil
}
// iterateChecks is called by iterateTables to handle check constraints.
func iterateChecks(ctx *sql.Context, callbacks Callbacks, itemSchema ItemSchema, itemTable ItemTable, checkCount *int) error {
if checkTable, ok := itemTable.Item.(sql.CheckTable); ok {
checks, err := checkTable.GetChecks(ctx)
if err != nil {
return err
}
sort.Slice(checks, func(i, j int) bool {
return checks[i].Name < checks[j].Name
})
for _, check := range checks {
*checkCount++
itemCheck := ItemCheck{
OID: id.NewCheck(itemSchema.Item.SchemaName(), itemTable.Item.Name(), check.Name),
Item: check,
}
if cont, err := callbacks.Check(ctx, itemSchema, itemTable, itemCheck); err != nil {
return err
} else if !cont {
return nil
}
}
}
return nil
}
// iterateColumnDefaults is called by iterateTables to handle column defaults.
func iterateColumnDefaults(ctx *sql.Context, callbacks Callbacks, itemSchema ItemSchema, itemTable ItemTable, columnDefaultCount *int) error {
columns := itemTable.Item.Schema(ctx)
for i, col := range columns {
if col.Default != nil {
*columnDefaultCount++
itemColDefault := ItemColumnDefault{
OID: id.NewColumnDefault(itemSchema.Item.SchemaName(), itemTable.Item.Name(), col.Name),
Item: ColumnWithIndex{col, i},
}
if cont, err := callbacks.ColumnDefault(ctx, itemSchema, itemTable, itemColDefault); err != nil {
return err
} else if !cont {
return nil
}
}
}
return nil
}
// iterateForeignKeys is called by iterateTables to handle foreign keys.
func iterateForeignKeys(ctx *sql.Context, callbacks Callbacks, itemSchema ItemSchema, itemTable ItemTable, foreignKeyCount *int) error {
if fkTable, ok := itemTable.Item.(sql.ForeignKeyTable); ok {
foreignKeys, err := fkTable.GetDeclaredForeignKeys(ctx)
if err != nil {
return err
}
sort.Slice(foreignKeys, func(i, j int) bool {
return foreignKeys[i].Name < foreignKeys[j].Name
})
for _, foreignKey := range foreignKeys {
*foreignKeyCount++
itemForeignKey := ItemForeignKey{
OID: id.NewForeignKey(itemSchema.Item.SchemaName(), itemTable.Item.Name(), foreignKey.Name),
Item: foreignKey,
}
if cont, err := callbacks.ForeignKey(ctx, itemSchema, itemTable, itemForeignKey); err != nil {
return err
} else if !cont {
return nil
}
}
}
return nil
}
// iterateIndexes is called by iterateTables to handle indexes.
func iterateIndexes(ctx *sql.Context, callbacks Callbacks, itemSchema ItemSchema, itemTable ItemTable, indexCount *int) error {
if indexedTable, ok := itemTable.Item.(sql.IndexAddressable); ok {
indexes, err := indexedTable.GetIndexes(ctx)
if err != nil {
return err
}
sort.Slice(indexes, func(i, j int) bool {
return indexes[i].ID() < indexes[j].ID()
})
for _, index := range indexes {
*indexCount++
itemIndex := ItemIndex{
OID: id.NewIndex(itemSchema.Item.SchemaName(), itemTable.Item.Name(), index.ID()),
Item: index,
}
if cont, err := callbacks.Index(ctx, itemSchema, itemTable, itemIndex); err != nil {
return err
} else if !cont {
return nil
}
}
}
return nil
}
// RunCallback iterates over schemas, etc. to find the item that the given oid points to. Once the item has been found,
// the relevant callback is called with the item. This means that, at most, only one callback will be called. If the
// item cannot be found, then no callbacks are called.
func RunCallback(ctx *sql.Context, internalID id.Id, callbacks Callbacks) error {
if ok := runCallbackValidation(ctx, internalID, callbacks); !ok {
return nil
}
// We know we have the relevant callback for the given section, so we'll grab the schema
doltSession := dsess.DSessFromSess(ctx.Session)
currentDatabase, err := doltSession.Provider().Database(ctx, ctx.GetCurrentDatabase())
if err != nil {
return err
}
currentDatabase = tables.WrapSqlDatabase(currentDatabase)
if currentSchemaDatabase, ok := currentDatabase.(sql.SchemaDatabase); ok {
schemas, err := currentSchemaDatabase.AllSchemas(ctx)
if err != nil {
return err
}
sort.Slice(schemas, func(i, j int) bool {
return schemas[i].SchemaName() < schemas[j].SchemaName()
})
// First check if we're only looking for the schema
if internalID.Section() == id.Section_Namespace {
return runNamespace(ctx, internalID, callbacks, schemas)
}
// We're not looking for the schema, so we'll just select the target schema containing what we're looking for
var itemSchema ItemSchema
for _, schema := range schemas {
if schema.SchemaName() == internalID.Segment(0) {
itemSchema = ItemSchema{
OID: id.NewNamespace(schema.SchemaName()),
Item: schema,
}
}
}
if !itemSchema.OID.IsValid() {
return nil
}
// Check if we're looking for a sequence
if internalID.Section() == id.Section_Sequence {
return runSequence(ctx, internalID, callbacks, itemSchema)
}
// Check if we're looking for a view
if internalID.Section() == id.Section_View {
return runView(ctx, internalID, callbacks, itemSchema)
}
// Before diving inside of tables, we'll check if we're looking for a table
tableNames, err := itemSchema.Item.GetTableNames(ctx)
if err != nil {
return err
}
sort.Slice(tableNames, func(i, j int) bool {
return tableNames[i] < tableNames[j]
})
if internalID.Section() == id.Section_Table {
return runTable(ctx, internalID, callbacks, itemSchema, tableNames)
}
// All other sections are items on tables, so we'll iterate over those now.
// We hold the count here since it increments across tables.
countedIndex := 0
for _, tableName := range tableNames {
table, ok, err := itemSchema.Item.GetTableInsensitive(ctx, tableName)
if err != nil && !errors.Is(err, doltdb.ErrTableNotFound) {
return err
} else if !ok {
// We receive these names from the schema, so these must be the names of root objects
continue
}
itemTable := ItemTable{
OID: id.NewTable(itemSchema.Item.SchemaName(), table.Name()),
Item: table,
}
switch internalID.Section() {
case id.Section_Check:
if ok, err := runCheck(ctx, internalID, callbacks, itemSchema, itemTable, &countedIndex); err != nil {
return err
} else if ok {
continue
}
return nil
case id.Section_ColumnDefault:
if ok, err := runColumnDefault(ctx, internalID, callbacks, itemSchema, itemTable, &countedIndex); err != nil {
return err
} else if ok {
continue
}
return nil
case id.Section_ForeignKey:
if ok, err := runForeignKey(ctx, internalID, callbacks, itemSchema, itemTable, &countedIndex); err != nil {
return err
} else if ok {
continue
}
return nil
case id.Section_Index:
if ok, err := runIndex(ctx, internalID, callbacks, itemSchema, itemTable); err != nil {
return err
} else if ok {
continue
}
return nil
default: // This is unnecessary, but the linter complains without it
return nil
}
}
}
return nil
}
// runCheck is called by RunCallback to handle Section_Check.
func runCheck(ctx *sql.Context, internalID id.Id, callbacks Callbacks, itemSchema ItemSchema, itemTable ItemTable, countedIndex *int) (cont bool, err error) {
if itemSchema.Item.SchemaName() != internalID.Segment(0) && itemTable.Item.Name() != internalID.Segment(1) {
return true, nil
}
if checkTable, ok := itemTable.Item.(sql.CheckTable); ok {
checks, err := checkTable.GetChecks(ctx)
if err != nil {
return false, err
}
for _, check := range checks {
if check.Name == internalID.Segment(2) {
itemCheck := ItemCheck{
OID: id.Check(internalID),
Item: check,
}
_, err = callbacks.Check(ctx, itemSchema, itemTable, itemCheck)
return false, err
}
}
}
return true, nil
}
// runColumnDefault is called by RunCallback to handle Section_Column.
func runColumnDefault(ctx *sql.Context, internalID id.Id, callbacks Callbacks, itemSchema ItemSchema, itemTable ItemTable, countedIndex *int) (cont bool, err error) {
if itemSchema.Item.SchemaName() != internalID.Segment(0) && itemTable.Item.Name() != internalID.Segment(1) {
return true, nil
}
columns := itemTable.Item.Schema(ctx)
var colDefaults []ColumnWithIndex
for i, col := range columns {
if col.Default != nil {
colDefaults = append(colDefaults, ColumnWithIndex{col, i})
}
}
for _, col := range colDefaults {
if col.Column.Name == internalID.Segment(2) {
itemColDefault := ItemColumnDefault{
OID: id.ColumnDefault(internalID),
Item: col,
}
_, err = callbacks.ColumnDefault(ctx, itemSchema, itemTable, itemColDefault)
if err != nil {
return false, err
}
}
}
return true, nil
}
// runForeignKey is called by RunCallback to handle Section_ForeignKey.
func runForeignKey(ctx *sql.Context, internalID id.Id, callbacks Callbacks, itemSchema ItemSchema, itemTable ItemTable, countedIndex *int) (cont bool, err error) {
if fkTable, ok := itemTable.Item.(sql.ForeignKeyTable); ok && itemSchema.Item.SchemaName() == internalID.Segment(0) && itemTable.Item.Name() == internalID.Segment(1) {
foreignKeys, err := fkTable.GetDeclaredForeignKeys(ctx)
if err != nil {
return false, err
}
for _, foreignKey := range foreignKeys {
if foreignKey.Name == internalID.Segment(2) {
itemForeignKey := ItemForeignKey{
OID: id.ForeignKey(internalID),
Item: foreignKey,
}
_, err = callbacks.ForeignKey(ctx, itemSchema, itemTable, itemForeignKey)
return false, err
}
}
}
return true, nil
}
// runIndex is called by RunCallback to handle Section_Index.
func runIndex(ctx *sql.Context, internalID id.Id, callbacks Callbacks, itemSchema ItemSchema, itemTable ItemTable) (cont bool, err error) {
if indexedTable, ok := itemTable.Item.(sql.IndexAddressable); ok && itemSchema.Item.SchemaName() == internalID.Segment(0) && itemTable.Item.Name() == internalID.Segment(1) {
indexes, err := indexedTable.GetIndexes(ctx)
if err != nil {
return false, err
}
for _, index := range indexes {
if index.ID() == internalID.Segment(2) && itemTable.Item.Name() == index.Table() {
_, err = callbacks.Index(ctx, itemSchema, itemTable, ItemIndex{
OID: id.Index(internalID),
Item: index,
})
return false, err
}
}
return true, nil
}
return true, nil
}
// runNamespace is called by RunCallback to handle Section_Namespace.
func runNamespace(ctx *sql.Context, internalID id.Id, callbacks Callbacks, sortedSchemas []sql.DatabaseSchema) error {
for _, schema := range sortedSchemas {
if schema.SchemaName() == internalID.Segment(0) {
itemSchema := ItemSchema{
OID: id.Namespace(internalID),
Item: schema,
}
_, err := callbacks.Schema(ctx, itemSchema)
return err
}
}
return nil
}
// runSequence is called by RunCallback to handle Section_Sequence.
func runSequence(ctx *sql.Context, internalID id.Id, callbacks Callbacks, itemSchema ItemSchema) error {
collection, err := core.GetSequencesCollectionFromContext(ctx, itemSchema.Item.Name())
if err != nil {
return err
}
sequenceMap, _, _, err := collection.GetAllSequences(ctx)
if err != nil {
return err
}
sequencesInSchema, ok := sequenceMap[itemSchema.Item.SchemaName()]
if !ok {
return nil
}
for _, seq := range sequencesInSchema {
if id.Id(seq.Id) == internalID {
_, err = callbacks.Sequence(ctx, itemSchema, ItemSequence{
OID: id.Sequence(internalID),
Item: seq,
})
return err
}
}
return nil
}
// runTable is called by RunCallback to handle Section_Table.
func runTable(ctx *sql.Context, internalID id.Id, callbacks Callbacks, itemSchema ItemSchema, sortedTableNames []string) error {
table, ok, err := itemSchema.Item.GetTableInsensitive(ctx, internalID.Segment(1))
if err != nil {
return err
} else if !ok {
return sql.ErrTableNotFound.New(internalID.Segment(1))
}
itemTable := ItemTable{
OID: id.Table(internalID),
Item: table,
}
_, err = callbacks.Table(ctx, itemSchema, itemTable)
return err
}
// runView is called by RunCallback to handle Section_View.
func runView(ctx *sql.Context, internalID id.Id, callbacks Callbacks, itemSchema ItemSchema) error {
if viewDatabase, ok := itemSchema.Item.(sql.ViewDatabase); ok && itemSchema.Item.SchemaName() == internalID.Segment(0) {
views, err := viewDatabase.AllViews(ctx)
if err != nil {
return err
}
for _, view := range views {
if view.Name == internalID.Segment(1) {
_, err = callbacks.View(ctx, itemSchema, ItemView{
OID: id.View(internalID),
Item: view,
})
return err
}
}
}
return nil
}
// runCallbackValidation ensures that the callbacks match the given oid.
func runCallbackValidation(ctx *sql.Context, internalID id.Id, callbacks Callbacks) bool {
// Check that we have the relevant callback, and return early if we do not
switch internalID.Section() {
case id.Section_Check:
if callbacks.Check == nil {
return false
}
case id.Section_ColumnDefault:
if callbacks.ColumnDefault == nil {
return false
}
case id.Section_Database:
// TODO: we inject information_schema, so we need to figure out how to model that here
return false
case id.Section_ForeignKey:
if callbacks.ForeignKey == nil {
return false
}
case id.Section_Index:
if callbacks.Index == nil {
return false
}
case id.Section_Namespace:
if callbacks.Schema == nil {
return false
}
case id.Section_Sequence:
if callbacks.Sequence == nil {
return false
}
case id.Section_Table:
if callbacks.Table == nil {
return false
}
case id.Section_View:
if callbacks.View == nil {
return false
}
default:
return false
}
return true
}
// iteratesOverSchemas returns whether we need to iterate over schemas based on the given callbacks.
func (iter Callbacks) iteratesOverSchemas() bool {
return iter.Check != nil ||
iter.ColumnDefault != nil ||
iter.ForeignKey != nil ||
iter.Index != nil ||
iter.Schema != nil ||
iter.Sequence != nil ||
iter.Table != nil ||
iter.Type != nil ||
iter.View != nil
}
// iteratesOverTables returns whether we need to iterate over tables based on the given callbacks.
func (iter Callbacks) iteratesOverTables() bool {
return iter.Check != nil ||
iter.ColumnDefault != nil ||
iter.ForeignKey != nil ||
iter.Index != nil ||
iter.Table != nil
}
// iteratesOverTypes returns whether we need to iterate over types based on the given callbacks.
func (iter Callbacks) iteratesOverTypes() bool {
return iter.Type != nil
}
// schemaIterationOrder returns the order that the given schemas should be iterated over.
func (iter Callbacks) schemaIterationOrder(sortedSchemas []sql.DatabaseSchema) []int {
// If no search schemas are set, then we'll iterate over all of the schemas in sorted order
if len(iter.SearchSchemas) == 0 {
order := make([]int, len(sortedSchemas))
for i := range sortedSchemas {
order[i] = i
}
return order
}
sortedSchemasMap := make(map[string]int)
for i, schema := range sortedSchemas {
sortedSchemasMap[schema.SchemaName()] = i
}
// We only add the schemas that we can find
order := make([]int, 0, len(iter.SearchSchemas))
for _, searchSchema := range iter.SearchSchemas {
if schemaPosition, ok := sortedSchemasMap[searchSchema]; ok {
order = append(order, schemaPosition)
}
}
return order
}