github.com/dolthub/go-mysql-server@v0.18.0/sql/analyzer/catalog.go (about)

     1  // Copyright 2021 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package analyzer
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"sync"
    21  
    22  	"github.com/dolthub/go-mysql-server/internal/similartext"
    23  	"github.com/dolthub/go-mysql-server/memory"
    24  	"github.com/dolthub/go-mysql-server/sql"
    25  	"github.com/dolthub/go-mysql-server/sql/binlogreplication"
    26  	"github.com/dolthub/go-mysql-server/sql/expression/function"
    27  	"github.com/dolthub/go-mysql-server/sql/information_schema"
    28  	"github.com/dolthub/go-mysql-server/sql/mysql_db"
    29  )
    30  
    31  type Catalog struct {
    32  	MySQLDb       *mysql_db.MySQLDb
    33  	InfoSchema    sql.Database
    34  	StatsProvider sql.StatsProvider
    35  
    36  	DbProvider       sql.DatabaseProvider
    37  	builtInFunctions function.Registry
    38  
    39  	// BinlogReplicaController holds an optional controller that receives forwarded binlog
    40  	// replication messages (e.g. "start replica").
    41  	BinlogReplicaController binlogreplication.BinlogReplicaController
    42  
    43  	mu    sync.RWMutex
    44  	locks sessionLocks
    45  }
    46  
    47  func (c *Catalog) DropDbStats(ctx *sql.Context, db string, flush bool) error {
    48  	return c.StatsProvider.DropDbStats(ctx, db, flush)
    49  }
    50  
    51  var _ sql.Catalog = (*Catalog)(nil)
    52  var _ sql.FunctionProvider = (*Catalog)(nil)
    53  var _ sql.TableFunctionProvider = (*Catalog)(nil)
    54  var _ sql.ExternalStoredProcedureProvider = (*Catalog)(nil)
    55  var _ binlogreplication.BinlogReplicaCatalog = (*Catalog)(nil)
    56  
    57  type tableLocks map[string]struct{}
    58  
    59  type dbLocks map[string]tableLocks
    60  
    61  type sessionLocks map[uint32]dbLocks
    62  
    63  // NewCatalog returns a new empty Catalog with the given provider
    64  func NewCatalog(provider sql.DatabaseProvider) *Catalog {
    65  	return &Catalog{
    66  		MySQLDb:          mysql_db.CreateEmptyMySQLDb(),
    67  		InfoSchema:       information_schema.NewInformationSchemaDatabase(),
    68  		DbProvider:       provider,
    69  		builtInFunctions: function.NewRegistry(),
    70  		StatsProvider:    memory.NewStatsProv(),
    71  		locks:            make(sessionLocks),
    72  	}
    73  }
    74  
    75  // TODO: kill this
    76  func NewDatabaseProvider(dbs ...sql.Database) sql.DatabaseProvider {
    77  	return sql.NewDatabaseProvider(dbs...)
    78  }
    79  
    80  func (c *Catalog) IsBinlogReplicaCatalog() bool {
    81  	return c.BinlogReplicaController != nil
    82  }
    83  
    84  func (c *Catalog) GetBinlogReplicaController() binlogreplication.BinlogReplicaController {
    85  	return c.BinlogReplicaController
    86  }
    87  
    88  func (c *Catalog) WithTableFunctions(fns ...sql.TableFunction) (sql.TableFunctionProvider, error) {
    89  	if tfp, ok := c.DbProvider.(sql.TableFunctionProvider); !ok {
    90  		return nil, fmt.Errorf("catalog does not implement sql.TableFunctionProvider")
    91  	} else {
    92  		ret := *c
    93  		newProv, err := tfp.WithTableFunctions(fns...)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  		ret.DbProvider = newProv.(sql.DatabaseProvider)
    98  		return &ret, nil
    99  	}
   100  }
   101  
   102  func (c *Catalog) AllDatabases(ctx *sql.Context) []sql.Database {
   103  	var dbs []sql.Database
   104  	dbs = append(dbs, c.InfoSchema)
   105  
   106  	if c.MySQLDb.Enabled() {
   107  		dbs = append(dbs, mysql_db.NewPrivilegedDatabaseProvider(c.MySQLDb, c.DbProvider).AllDatabases(ctx)...)
   108  	} else {
   109  		dbs = append(dbs, c.DbProvider.AllDatabases(ctx)...)
   110  	}
   111  
   112  	return dbs
   113  }
   114  
   115  // CreateDatabase creates a new Database and adds it to the catalog.
   116  func (c *Catalog) CreateDatabase(ctx *sql.Context, dbName string, collation sql.CollationID) error {
   117  	c.mu.Lock()
   118  	defer c.mu.Unlock()
   119  
   120  	if collatedDbProvider, ok := c.DbProvider.(sql.CollatedDatabaseProvider); ok {
   121  		// If the database provider supports creation with a collation, then we call that function directly
   122  		return collatedDbProvider.CreateCollatedDatabase(ctx, dbName, collation)
   123  	} else if mut, ok := c.DbProvider.(sql.MutableDatabaseProvider); ok {
   124  		err := mut.CreateDatabase(ctx, dbName)
   125  		if err != nil {
   126  			return err
   127  		}
   128  		// It's possible that the db provider doesn't support creation with a collation, in which case we create the
   129  		// database and then set the collation. If the database doesn't support collations at all, then we ignore the
   130  		// provided collation rather than erroring.
   131  		if db, err := c.Database(ctx, dbName); err == nil {
   132  			if collatedDb, ok := db.(sql.CollatedDatabase); ok {
   133  				return collatedDb.SetCollation(ctx, collation)
   134  			}
   135  		}
   136  		return nil
   137  	} else {
   138  		return sql.ErrImmutableDatabaseProvider.New()
   139  	}
   140  }
   141  
   142  // RemoveDatabase removes a database from the catalog.
   143  func (c *Catalog) RemoveDatabase(ctx *sql.Context, dbName string) error {
   144  	c.mu.Lock()
   145  	defer c.mu.Unlock()
   146  
   147  	mut, ok := c.DbProvider.(sql.MutableDatabaseProvider)
   148  	if ok {
   149  		return mut.DropDatabase(ctx, dbName)
   150  	} else {
   151  		return sql.ErrImmutableDatabaseProvider.New()
   152  	}
   153  }
   154  
   155  func (c *Catalog) HasDatabase(ctx *sql.Context, db string) bool {
   156  	db = strings.ToLower(db)
   157  	if db == "information_schema" {
   158  		return true
   159  	} else if c.MySQLDb.Enabled() {
   160  		return mysql_db.NewPrivilegedDatabaseProvider(c.MySQLDb, c.DbProvider).HasDatabase(ctx, db)
   161  	} else {
   162  		return c.DbProvider.HasDatabase(ctx, db)
   163  	}
   164  }
   165  
   166  // Database returns the database with the given name.
   167  func (c *Catalog) Database(ctx *sql.Context, db string) (sql.Database, error) {
   168  	if strings.ToLower(db) == "information_schema" {
   169  		return c.InfoSchema, nil
   170  	} else if c.MySQLDb.Enabled() {
   171  		return mysql_db.NewPrivilegedDatabaseProvider(c.MySQLDb, c.DbProvider).Database(ctx, db)
   172  	} else {
   173  		return c.DbProvider.Database(ctx, db)
   174  	}
   175  }
   176  
   177  // LockTable adds a lock for the given table and session client. It is assumed
   178  // the database is the current database in use.
   179  func (c *Catalog) LockTable(ctx *sql.Context, table string) {
   180  	id := ctx.ID()
   181  	db := ctx.GetCurrentDatabase()
   182  
   183  	c.mu.Lock()
   184  	defer c.mu.Unlock()
   185  
   186  	if _, ok := c.locks[id]; !ok {
   187  		c.locks[id] = make(dbLocks)
   188  	}
   189  
   190  	if _, ok := c.locks[id][db]; !ok {
   191  		c.locks[id][db] = make(tableLocks)
   192  	}
   193  
   194  	c.locks[id][db][table] = struct{}{}
   195  }
   196  
   197  // UnlockTables unlocks all tables for which the given session client has a
   198  // lock.
   199  func (c *Catalog) UnlockTables(ctx *sql.Context, id uint32) error {
   200  	c.mu.Lock()
   201  	defer c.mu.Unlock()
   202  
   203  	var errors []string
   204  	for db, tables := range c.locks[id] {
   205  		for t := range tables {
   206  			database, err := c.DbProvider.Database(ctx, db)
   207  			if err != nil {
   208  				return err
   209  			}
   210  
   211  			table, _, err := database.GetTableInsensitive(ctx, t)
   212  			if err == nil {
   213  				if lockable, ok := table.(sql.Lockable); ok {
   214  					if e := lockable.Unlock(ctx, id); e != nil {
   215  						errors = append(errors, e.Error())
   216  					}
   217  				}
   218  			} else {
   219  				errors = append(errors, err.Error())
   220  			}
   221  		}
   222  	}
   223  
   224  	delete(c.locks, id)
   225  	if len(errors) > 0 {
   226  		return fmt.Errorf("error unlocking tables for %d: %s", id, strings.Join(errors, ", "))
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  // Table returns the table in the given database with the given name.
   233  func (c *Catalog) Table(ctx *sql.Context, dbName, tableName string) (sql.Table, sql.Database, error) {
   234  	c.mu.RLock()
   235  	defer c.mu.RUnlock()
   236  
   237  	db, err := c.Database(ctx, dbName)
   238  	if err != nil {
   239  		return nil, nil, err
   240  	}
   241  
   242  	return c.DatabaseTable(ctx, db, tableName)
   243  }
   244  
   245  func (c *Catalog) DatabaseTable(ctx *sql.Context, db sql.Database, tableName string) (sql.Table, sql.Database, error) {
   246  	_, ok := db.(sql.UnresolvedDatabase)
   247  	if ok {
   248  		return c.Table(ctx, db.Name(), tableName)
   249  	}
   250  
   251  	tbl, ok, err := db.GetTableInsensitive(ctx, tableName)
   252  	if err != nil {
   253  		return nil, nil, err
   254  	} else if !ok {
   255  		return nil, nil, suggestSimilarTables(db, ctx, tableName)
   256  	}
   257  
   258  	return tbl, db, nil
   259  }
   260  
   261  // TableAsOf returns the table in the given database with the given name, as it existed at the time given. The database
   262  // named must support timed queries.
   263  func (c *Catalog) TableAsOf(ctx *sql.Context, dbName, tableName string, asOf interface{}) (sql.Table, sql.Database, error) {
   264  	c.mu.RLock()
   265  	defer c.mu.RUnlock()
   266  
   267  	db, err := c.Database(ctx, dbName)
   268  	if err != nil {
   269  		return nil, nil, err
   270  	}
   271  
   272  	return c.DatabaseTableAsOf(ctx, db, tableName, asOf)
   273  }
   274  
   275  func (c *Catalog) DatabaseTableAsOf(ctx *sql.Context, db sql.Database, tableName string, asOf interface{}) (sql.Table, sql.Database, error) {
   276  	_, ok := db.(sql.UnresolvedDatabase)
   277  	if ok {
   278  		return c.TableAsOf(ctx, db.Name(), tableName, asOf)
   279  	}
   280  
   281  	versionedDb, ok := db.(sql.VersionedDatabase)
   282  	if !ok {
   283  		return nil, nil, sql.ErrAsOfNotSupported.New(db.Name())
   284  	}
   285  
   286  	tbl, ok, err := versionedDb.GetTableInsensitiveAsOf(ctx, tableName, asOf)
   287  
   288  	if err != nil {
   289  		return nil, nil, err
   290  	} else if !ok {
   291  		return nil, nil, suggestSimilarTablesAsOf(versionedDb, ctx, tableName, asOf)
   292  	}
   293  
   294  	return tbl, versionedDb, nil
   295  }
   296  
   297  // RegisterFunction registers the functions given, adding them to the built-in functions.
   298  // Integrators with custom functions should typically use the FunctionProvider interface instead.
   299  func (c *Catalog) RegisterFunction(ctx *sql.Context, fns ...sql.Function) {
   300  	for _, fn := range fns {
   301  		err := c.builtInFunctions.Register(fn)
   302  		if err != nil {
   303  			panic(err)
   304  		}
   305  	}
   306  }
   307  
   308  // Function returns the function with the name given, or sql.ErrFunctionNotFound if it doesn't exist
   309  func (c *Catalog) Function(ctx *sql.Context, name string) (sql.Function, error) {
   310  	if fp, ok := c.DbProvider.(sql.FunctionProvider); ok {
   311  		f, err := fp.Function(ctx, name)
   312  		if err != nil && !sql.ErrFunctionNotFound.Is(err) {
   313  			return nil, err
   314  		} else if f != nil {
   315  			return f, nil
   316  		}
   317  	}
   318  
   319  	return c.builtInFunctions.Function(ctx, name)
   320  }
   321  
   322  // ExternalStoredProcedure implements sql.ExternalStoredProcedureProvider
   323  func (c *Catalog) ExternalStoredProcedure(ctx *sql.Context, name string, numOfParams int) (*sql.ExternalStoredProcedureDetails, error) {
   324  	if espp, ok := c.DbProvider.(sql.ExternalStoredProcedureProvider); ok {
   325  		esp, err := espp.ExternalStoredProcedure(ctx, name, numOfParams)
   326  		if err != nil {
   327  			return nil, err
   328  		} else if esp != nil {
   329  			return esp, nil
   330  		}
   331  	}
   332  
   333  	return nil, nil
   334  }
   335  
   336  // ExternalStoredProcedures implements sql.ExternalStoredProcedureProvider
   337  func (c *Catalog) ExternalStoredProcedures(ctx *sql.Context, name string) ([]sql.ExternalStoredProcedureDetails, error) {
   338  	if espp, ok := c.DbProvider.(sql.ExternalStoredProcedureProvider); ok {
   339  		esps, err := espp.ExternalStoredProcedures(ctx, name)
   340  		if err != nil {
   341  			return nil, err
   342  		} else if esps != nil {
   343  			return esps, nil
   344  		}
   345  	}
   346  
   347  	return nil, nil
   348  }
   349  
   350  // TableFunction implements the TableFunctionProvider interface
   351  func (c *Catalog) TableFunction(ctx *sql.Context, name string) (sql.TableFunction, error) {
   352  	if fp, ok := c.DbProvider.(sql.TableFunctionProvider); ok {
   353  		tf, err := fp.TableFunction(ctx, name)
   354  		if err != nil {
   355  			return nil, err
   356  		} else if tf != nil {
   357  			return tf, nil
   358  		}
   359  	}
   360  
   361  	return nil, sql.ErrTableFunctionNotFound.New(name)
   362  }
   363  
   364  func (c *Catalog) RefreshTableStats(ctx *sql.Context, table sql.Table, db string) error {
   365  	return c.StatsProvider.RefreshTableStats(ctx, table, db)
   366  }
   367  
   368  func (c *Catalog) GetTableStats(ctx *sql.Context, db, table string) ([]sql.Statistic, error) {
   369  	return c.StatsProvider.GetTableStats(ctx, db, table)
   370  }
   371  
   372  func (c *Catalog) SetStats(ctx *sql.Context, stats sql.Statistic) error {
   373  	return c.StatsProvider.SetStats(ctx, stats)
   374  }
   375  
   376  func (c *Catalog) GetStats(ctx *sql.Context, qual sql.StatQualifier, cols []string) (sql.Statistic, bool) {
   377  	return c.StatsProvider.GetStats(ctx, qual, cols)
   378  }
   379  
   380  func (c *Catalog) DropStats(ctx *sql.Context, qual sql.StatQualifier, cols []string) error {
   381  	return c.StatsProvider.DropStats(ctx, qual, cols)
   382  }
   383  
   384  func (c *Catalog) RowCount(ctx *sql.Context, db, table string) (uint64, error) {
   385  	cnt, err := c.StatsProvider.RowCount(ctx, db, table)
   386  	if err == nil && cnt > 0 {
   387  		return cnt, nil
   388  	}
   389  	// fallback to on-table statistics
   390  	t, _, err := c.Table(ctx, db, table)
   391  	if err != nil {
   392  		return 0, err
   393  	}
   394  	st, ok := t.(sql.StatisticsTable)
   395  	if !ok {
   396  		return 0, nil
   397  	}
   398  	cnt, _, err = st.RowCount(ctx)
   399  	return cnt, err
   400  }
   401  
   402  func (c *Catalog) DataLength(ctx *sql.Context, db, table string) (uint64, error) {
   403  	length, err := c.StatsProvider.DataLength(ctx, db, table)
   404  	if err == nil && length > 0 {
   405  		return length, nil
   406  	}
   407  	// fallback to on-table statistics
   408  	t, _, err := c.Table(ctx, db, table)
   409  	if err != nil {
   410  		return 0, err
   411  	}
   412  	st, ok := t.(sql.StatisticsTable)
   413  	if !ok {
   414  		return 0, nil
   415  	}
   416  	return st.DataLength(ctx)
   417  }
   418  
   419  func suggestSimilarTables(db sql.Database, ctx *sql.Context, tableName string) error {
   420  	tableNames, err := db.GetTableNames(ctx)
   421  	if err != nil {
   422  		return err
   423  	}
   424  
   425  	similar := similartext.Find(tableNames, tableName)
   426  	return sql.ErrTableNotFound.New(tableName + similar)
   427  }
   428  
   429  func suggestSimilarTablesAsOf(db sql.VersionedDatabase, ctx *sql.Context, tableName string, time interface{}) error {
   430  	tableNames, err := db.GetTableNamesAsOf(ctx, time)
   431  	if err != nil {
   432  		return err
   433  	}
   434  
   435  	similar := similartext.Find(tableNames, tableName)
   436  	return sql.ErrTableNotFound.New(tableName + similar)
   437  }