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 }