673 lines
20 KiB
Go
673 lines
20 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 core
|
|
|
|
import (
|
|
"maps"
|
|
"slices"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/resolve"
|
|
"github.com/dolthub/go-mysql-server/sql"
|
|
|
|
"github.com/dolthub/doltgresql/core/casts"
|
|
"github.com/dolthub/doltgresql/core/extensions"
|
|
"github.com/dolthub/doltgresql/core/functions"
|
|
"github.com/dolthub/doltgresql/core/procedures"
|
|
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
|
"github.com/dolthub/doltgresql/core/sequences"
|
|
"github.com/dolthub/doltgresql/core/triggers"
|
|
"github.com/dolthub/doltgresql/core/typecollection"
|
|
)
|
|
|
|
// contextValues contains a set of cached data passed alongside the context. This data is considered temporary
|
|
// and may be refreshed at any point, including during the middle of a query. Callers should not assume that
|
|
// data stored in contextValues is persisted, and other types of data should not be added to contextValues.
|
|
type contextValues struct {
|
|
seqs map[string]*sequences.Collection
|
|
types map[string]*typecollection.TypeCollection
|
|
funcs map[string]*functions.Collection
|
|
procs map[string]*procedures.Collection
|
|
trigs map[string]*triggers.Collection
|
|
exts map[string]*extensions.Collection
|
|
casts map[string]*casts.Collection
|
|
|
|
pgCatalogCache any
|
|
runner sql.StatementRunner
|
|
|
|
// cache the dateOutputFormat, this is refreshed on SET
|
|
dateOutputFormat string
|
|
}
|
|
|
|
// getContextValues accesses the contextValues in the given context. If the context does not have a contextValues, then
|
|
// it creates one and adds it to the context.
|
|
func getContextValues(ctx *sql.Context) (*contextValues, error) {
|
|
if ctx == nil {
|
|
return nil, errors.New("context is nil")
|
|
}
|
|
sess := dsess.DSessFromSess(ctx.Session)
|
|
if sess.DoltgresSessObj == nil {
|
|
cv := &contextValues{}
|
|
sess.DoltgresSessObj = cv
|
|
return cv, nil
|
|
}
|
|
cv, ok := sess.DoltgresSessObj.(*contextValues)
|
|
if !ok {
|
|
return nil, errors.Errorf("context contains an unknown values struct of type: %T", sess.DoltgresSessObj)
|
|
}
|
|
return cv, nil
|
|
}
|
|
|
|
// ClearContextValues clears all context values. This is primarily for operations that are directly called from Dolt, as
|
|
// Dolt does not have the Doltgres concept of context values. Care must be taken to ensure that intermediate state
|
|
// written to the context values are not overwritten.
|
|
func ClearContextValues(ctx *sql.Context) {
|
|
sess := dsess.DSessFromSess(ctx.Session)
|
|
if sess.DoltgresSessObj != nil {
|
|
// We want to persist the runner between clears since it's static
|
|
var runner sql.StatementRunner
|
|
if cv, ok := sess.DoltgresSessObj.(*contextValues); ok {
|
|
runner = cv.runner
|
|
}
|
|
sess.DoltgresSessObj = &contextValues{
|
|
runner: runner,
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetRootFromContext returns the working session's root from the context, along with the session.
|
|
func GetRootFromContext(ctx *sql.Context) (*dsess.DoltSession, *RootValue, error) {
|
|
return getRootFromContextForDatabase(ctx, "")
|
|
}
|
|
|
|
// getRootFromContextForDatabase returns the working session's root from the context for a specific database, along with the session.
|
|
func getRootFromContextForDatabase(ctx *sql.Context, database string) (*dsess.DoltSession, *RootValue, error) {
|
|
session := dsess.DSessFromSess(ctx.Session)
|
|
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
state, ok, err := session.LookupDbState(ctx, database)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if !ok {
|
|
return nil, nil, sql.ErrDatabaseNotFound.New(database)
|
|
}
|
|
return session, state.WorkingRoot().(*RootValue), nil
|
|
}
|
|
|
|
// IsContextValid returns whether the context is valid for use with any of the functions in the package. If this is not
|
|
// false, then there's a high likelihood that the context is being used in a temporary scenario (such as setting up the
|
|
// database, etc.).
|
|
func IsContextValid(ctx *sql.Context) bool {
|
|
if ctx == nil {
|
|
return false
|
|
}
|
|
_, ok := ctx.Session.(*dsess.DoltSession)
|
|
return ok
|
|
}
|
|
|
|
// GetPgCatalogCache returns a cache of data for pg_catalog tables. This function should only be used by
|
|
// pg_catalog table handlers. The catalog cache instance stores generated pg_catalog table data so that
|
|
// it only has to generated table data once per query.
|
|
//
|
|
// TODO: The return type here is currently any, to avoid a package import cycle. This could be cleaned up by
|
|
// introducing a new interface type, in a package that does not depend on core or pgcatalog packages.
|
|
func GetPgCatalogCache(ctx *sql.Context) (any, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cv.pgCatalogCache, nil
|
|
}
|
|
|
|
// SetPgCatalogCache sets |pgCatalogCache| as the catalog cache instance for this session.
|
|
//
|
|
// TODO: The input type here is currently any, to avoid a package import cycle. This could be cleaned up by
|
|
// introducing a new interface type, in a package that does not depend on core or pgcatalog packages.
|
|
func SetPgCatalogCache(ctx *sql.Context, pgCatalogCache any) error {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cv.pgCatalogCache = pgCatalogCache
|
|
return nil
|
|
}
|
|
|
|
// SetRunnerOnContext sets the given runner within the context values.
|
|
func SetRunnerOnContext(ctx *sql.Context, runner sql.StatementRunner) error {
|
|
if runner == nil {
|
|
return nil
|
|
}
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cv.runner = runner
|
|
return nil
|
|
}
|
|
|
|
// GetRunnerFromContext returns the sql.StatementRunner from within the context.
|
|
func GetRunnerFromContext(ctx *sql.Context) (sql.StatementRunner, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cv.runner, nil
|
|
}
|
|
|
|
// GetDoltTableFromContext returns the Dolt table from the context. Returns nil if no table was found.
|
|
func GetDoltTableFromContext(ctx *sql.Context, tableName doltdb.TableName) (*doltdb.Table, error) {
|
|
_, root, err := GetRootFromContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var table *doltdb.Table
|
|
if tableName.Schema == "" {
|
|
_, table, _, err = resolve.Table(ctx, root, tableName.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
table, _, err = root.GetTable(ctx, tableName)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return table, nil
|
|
}
|
|
|
|
// GetSqlDatabaseFromContext returns the database from the context. Uses the context's current database if an empty
|
|
// string is provided. Returns nil if the database was not found.
|
|
func GetSqlDatabaseFromContext(ctx *sql.Context, database string) (sql.Database, error) {
|
|
session := dsess.DSessFromSess(ctx.Session)
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
db, err := session.Provider().Database(ctx, database)
|
|
if err != nil {
|
|
if sql.ErrDatabaseNotFound.Is(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return db, nil
|
|
}
|
|
|
|
// GetSqlTableFromContext returns the table from the context. Uses the context's current database if an empty database
|
|
// name is provided. Returns nil if no table was found.
|
|
func GetSqlTableFromContext(ctx *sql.Context, databaseName string, tableName doltdb.TableName) (sql.Table, error) {
|
|
db, err := GetSqlDatabaseFromContext(ctx, databaseName)
|
|
if err != nil || db == nil {
|
|
return nil, err
|
|
}
|
|
schemaDb, ok := db.(sql.SchemaDatabase)
|
|
if !ok {
|
|
// Fairly confident that Dolt only has database implementations that inherit sql.SchemaDatabase, so only GMS
|
|
// databases may fail here (like information_schema). In this scenario, we expect that no schema will be passed,
|
|
// so we'll special-case it here.
|
|
if len(tableName.Schema) == 0 {
|
|
tbl, ok, err := db.GetTableInsensitive(ctx, tableName.Name)
|
|
if err != nil || !ok {
|
|
return nil, err
|
|
}
|
|
return tbl, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
var searchPath []string
|
|
if len(tableName.Schema) == 0 {
|
|
// If a schema was not provided, then we'll use the search path
|
|
searchPath, err = SearchPath(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
// A specific schema is given, so we'll only use that one for the search path
|
|
searchPath = []string{tableName.Schema}
|
|
}
|
|
|
|
for _, schema := range searchPath {
|
|
db, ok, err = schemaDb.GetSchema(ctx, schema)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
continue
|
|
}
|
|
tbl, ok, err := db.GetTableInsensitive(ctx, tableName.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
continue
|
|
}
|
|
return tbl, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// SearchPath returns the effective schema search path for the current session
|
|
func SearchPath(ctx *sql.Context) ([]string, error) {
|
|
path, err := resolve.SearchPath(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// pg_catalog is *always* implicitly part of the search path as the first element, unless it's specifically
|
|
// included later. This allows users to override built-in names with user-defined names, but they have to
|
|
// opt in to that behavior.
|
|
hasPgCatalog := false
|
|
for _, schema := range path {
|
|
if schema == "pg_catalog" {
|
|
hasPgCatalog = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !hasPgCatalog {
|
|
path = append([]string{"pg_catalog"}, path...)
|
|
}
|
|
return path, nil
|
|
}
|
|
|
|
// GetExtensionsCollectionFromContext returns the extensions collection from the given context. Will always return a
|
|
// collection if no error is returned.
|
|
func GetExtensionsCollectionFromContext(ctx *sql.Context, database string) (*extensions.Collection, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cv.exts == nil {
|
|
cv.exts = make(map[string]*extensions.Collection)
|
|
}
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
if cv.exts[database] == nil {
|
|
_, root, err := getRootFromContextForDatabase(ctx, database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cv.exts[database], err = extensions.LoadExtensions(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return cv.exts[database], nil
|
|
}
|
|
|
|
// GetFunctionsCollectionFromContext returns the functions collection from the given context. Will always return a
|
|
// collection if no error is returned.
|
|
func GetFunctionsCollectionFromContext(ctx *sql.Context, database string) (*functions.Collection, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cv.funcs == nil {
|
|
cv.funcs = make(map[string]*functions.Collection)
|
|
}
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
if cv.funcs[database] == nil {
|
|
_, root, err := getRootFromContextForDatabase(ctx, database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cv.funcs[database], err = functions.LoadFunctions(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return cv.funcs[database], nil
|
|
}
|
|
|
|
// GetProceduresCollectionFromContext returns the procedures collection from the given context. Will always return a
|
|
// collection if no error is returned.
|
|
func GetProceduresCollectionFromContext(ctx *sql.Context, database string) (*procedures.Collection, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cv.procs == nil {
|
|
cv.procs = make(map[string]*procedures.Collection)
|
|
}
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
if cv.procs[database] == nil {
|
|
_, root, err := getRootFromContextForDatabase(ctx, database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cv.procs[database], err = procedures.LoadProcedures(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return cv.procs[database], nil
|
|
}
|
|
|
|
// GetSequencesCollectionFromContext returns the given sequence collection from the context for the database
|
|
// named. If no database is provided, the context's current database is used.
|
|
// Will always return a collection if no error is returned.
|
|
func GetSequencesCollectionFromContext(ctx *sql.Context, database string) (*sequences.Collection, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cv.seqs == nil {
|
|
cv.seqs = make(map[string]*sequences.Collection)
|
|
}
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
if cv.seqs[database] == nil {
|
|
_, root, err := getRootFromContextForDatabase(ctx, database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cv.seqs[database], err = sequences.LoadSequences(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return cv.seqs[database], nil
|
|
}
|
|
|
|
// GetTriggersCollectionFromContext returns the triggers collection from the given context. Will always return a
|
|
// collection if no error is returned.
|
|
func GetTriggersCollectionFromContext(ctx *sql.Context, database string) (*triggers.Collection, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cv.trigs == nil {
|
|
cv.trigs = make(map[string]*triggers.Collection)
|
|
}
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
if cv.trigs[database] == nil {
|
|
_, root, err := getRootFromContextForDatabase(ctx, database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cv.trigs[database], err = triggers.LoadTriggers(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return cv.trigs[database], nil
|
|
}
|
|
|
|
// GetTypesCollectionFromContext returns the given type collection from the context.
|
|
// Will always return a collection if no error is returned.
|
|
func GetTypesCollectionFromContext(ctx *sql.Context, database string) (*typecollection.TypeCollection, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cv.types == nil {
|
|
cv.types = make(map[string]*typecollection.TypeCollection)
|
|
}
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
if cv.types[database] == nil {
|
|
_, root, err := getRootFromContextForDatabase(ctx, database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cv.types[database], err = typecollection.LoadTypes(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return cv.types[database], nil
|
|
}
|
|
|
|
// GetCastsCollectionFromContext returns the given casts collection from the context.
|
|
// Will always return a collection if no error is returned.
|
|
func GetCastsCollectionFromContext(ctx *sql.Context, database string) (*casts.Collection, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cv.casts == nil {
|
|
cv.casts = make(map[string]*casts.Collection)
|
|
}
|
|
if len(database) == 0 {
|
|
database = ctx.GetCurrentDatabase()
|
|
}
|
|
if cv.casts[database] == nil {
|
|
_, root, err := getRootFromContextForDatabase(ctx, database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cv.casts[database], err = casts.LoadCasts(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return cv.casts[database], nil
|
|
}
|
|
|
|
// CloseContextRootFinalizer finalizes any changes persisted within the context by writing them to the working root.
|
|
// This should ONLY be called by the ContextRootFinalizer node.
|
|
func CloseContextRootFinalizer(ctx *sql.Context) error {
|
|
sess := dsess.DSessFromSess(ctx.Session)
|
|
if sess.DoltgresSessObj == nil {
|
|
return nil
|
|
}
|
|
cv, ok := sess.DoltgresSessObj.(*contextValues)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// We need to update the root for all databases used by this context. This logic parallels what happens during
|
|
// transaction commit in the dolt/sqle layer, where we check each branch state to see if it's dirty
|
|
for _, db := range databasesInContext(ctx, cv) {
|
|
err := updateSessionRootForDatabase(ctx, db, cv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetDateStyleOutputFormat returns the cached DateOutputFormat
|
|
func GetDateStyleOutputFormat(ctx *sql.Context) (string, error) {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return cv.dateOutputFormat, nil
|
|
}
|
|
|
|
// SetDateStyleOutputFormat cached the provided dateOutputFormat
|
|
func SetDateStyleOutputFormat(ctx *sql.Context, dateOutputFormat string) error {
|
|
cv, err := getContextValues(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cv.dateOutputFormat = dateOutputFormat
|
|
return nil
|
|
}
|
|
|
|
// updateSessionRootForDatabase updates the root for all changes made to root object collections within the context
|
|
// values.
|
|
func updateSessionRootForDatabase(ctx *sql.Context, db string, cv *contextValues) error {
|
|
session, root, err := getRootFromContextForDatabase(ctx, db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newRoot := root
|
|
if cv.seqs != nil && cv.seqs[db] != nil {
|
|
retRoot, err := cv.seqs[db].UpdateRoot(ctx, newRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newRoot = retRoot.(*RootValue)
|
|
delete(cv.seqs, db)
|
|
}
|
|
|
|
if cv.funcs != nil && cv.funcs[db] != nil && cv.funcs[db].DiffersFrom(ctx, root) {
|
|
retRoot, err := cv.funcs[db].UpdateRoot(ctx, newRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newRoot = retRoot.(*RootValue)
|
|
delete(cv.funcs, db)
|
|
}
|
|
|
|
if cv.procs != nil && cv.procs[db] != nil && cv.procs[db].DiffersFrom(ctx, root) {
|
|
retRoot, err := cv.procs[db].UpdateRoot(ctx, newRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newRoot = retRoot.(*RootValue)
|
|
delete(cv.procs, db)
|
|
}
|
|
|
|
if cv.trigs != nil && cv.trigs[db] != nil && cv.trigs[db].DiffersFrom(ctx, root) {
|
|
retRoot, err := cv.trigs[db].UpdateRoot(ctx, newRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newRoot = retRoot.(*RootValue)
|
|
delete(cv.trigs, db)
|
|
}
|
|
|
|
if cv.exts != nil && cv.exts[db] != nil && cv.exts[db].DiffersFrom(ctx, root) {
|
|
retRoot, err := cv.exts[db].UpdateRoot(ctx, newRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newRoot = retRoot.(*RootValue)
|
|
delete(cv.exts, db)
|
|
}
|
|
|
|
if cv.types != nil && cv.types[db] != nil {
|
|
retRoot, err := cv.types[db].UpdateRoot(ctx, newRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newRoot = retRoot.(*RootValue)
|
|
delete(cv.types, db)
|
|
}
|
|
|
|
if cv.casts != nil && cv.casts[db] != nil && cv.casts[db].DiffersFrom(ctx, root) {
|
|
retRoot, err := cv.casts[db].UpdateRoot(ctx, newRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newRoot = retRoot.(*RootValue)
|
|
delete(cv.casts, db)
|
|
}
|
|
|
|
// Setting the session working root doesn't do a check to see if anything actually changed or not before marking that
|
|
// branch state dirty, and dolt only allows a single dirty working set per commit. So it's important here to only
|
|
// update the session root if something actually changed for that db.
|
|
if err, rootChanged := rootValueChanged(newRoot, root); rootChanged {
|
|
if err = session.SetWorkingRoot(ctx, db, newRoot); err != nil {
|
|
// TODO: We need a way to see if the session has a writeable working root
|
|
// (new interface method on session probably), and avoid setting it if so
|
|
if errors.Is(err, doltdb.ErrOperationNotSupportedInDetachedHead) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// rootValueChanged returns whether the new root value is different from the old one
|
|
func rootValueChanged(newRoot *RootValue, root *RootValue) (error, bool) {
|
|
if newRoot == root {
|
|
return nil, false
|
|
}
|
|
|
|
newHash, err := newRoot.HashOf()
|
|
if err != nil {
|
|
return err, false
|
|
}
|
|
|
|
oldHash, err := root.HashOf()
|
|
if err != nil {
|
|
return err, false
|
|
}
|
|
|
|
if newHash == oldHash {
|
|
return nil, false
|
|
}
|
|
|
|
return nil, true
|
|
}
|
|
|
|
// databasesInContext returns all databases found within the context values.
|
|
func databasesInContext(ctx *sql.Context, cv *contextValues) []string {
|
|
dbs := make(map[string]struct{})
|
|
if cv.seqs != nil {
|
|
for db := range cv.seqs {
|
|
dbs[db] = struct{}{}
|
|
}
|
|
}
|
|
currentDb := ctx.GetCurrentDatabase()
|
|
if len(currentDb) > 0 {
|
|
dbs[currentDb] = struct{}{}
|
|
}
|
|
|
|
return slices.Sorted(maps.Keys(dbs))
|
|
}
|
|
|
|
// clear removes the collection from the cache.
|
|
func (cv *contextValues) clear(objID objinterface.RootObjectID) {
|
|
switch objID {
|
|
case objinterface.RootObjectID_None:
|
|
// Nothing to cache with this
|
|
case objinterface.RootObjectID_Sequences:
|
|
cv.seqs = nil
|
|
case objinterface.RootObjectID_Types:
|
|
cv.types = nil
|
|
case objinterface.RootObjectID_Functions:
|
|
cv.funcs = nil
|
|
case objinterface.RootObjectID_Triggers:
|
|
cv.trigs = nil
|
|
case objinterface.RootObjectID_Extensions:
|
|
// We don't cache these
|
|
case objinterface.RootObjectID_Conflicts:
|
|
// We don't cache these
|
|
case objinterface.RootObjectID_Procedures:
|
|
cv.procs = nil
|
|
case objinterface.RootObjectID_Casts:
|
|
cv.casts = nil
|
|
default:
|
|
panic("unhandled context clear object ID")
|
|
}
|
|
}
|