748 lines
26 KiB
Go
748 lines
26 KiB
Go
// Copyright 2021 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 engine
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"maps"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
gms "github.com/dolthub/go-mysql-server"
|
|
"github.com/dolthub/go-mysql-server/eventscheduler"
|
|
"github.com/dolthub/go-mysql-server/sql"
|
|
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
|
"github.com/dolthub/go-mysql-server/sql/binlogreplication"
|
|
"github.com/dolthub/go-mysql-server/sql/mysql_db"
|
|
"github.com/dolthub/go-mysql-server/sql/rowexec"
|
|
_ "github.com/dolthub/go-mysql-server/sql/variables"
|
|
"github.com/dolthub/vitess/go/vt/sqlparser"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/dolthub/dolt/go/cmd/dolt/cli"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/branch_control"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/gcctx"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/servercfg"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle"
|
|
dblr "github.com/dolthub/dolt/go/libraries/doltcore/sqle/binlogreplication"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/cluster"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dprocedures"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/kvexec"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/mysql_file_handler"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/statspro"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/writer"
|
|
"github.com/dolthub/dolt/go/libraries/utils/config"
|
|
dherrors "github.com/dolthub/dolt/go/libraries/utils/errors"
|
|
"github.com/dolthub/dolt/go/libraries/utils/filesys"
|
|
"github.com/dolthub/dolt/go/libraries/utils/valctx"
|
|
)
|
|
|
|
// SqlEngine packages up the context necessary to run sql queries against dsqle.
|
|
type SqlEngine struct {
|
|
provider *sqle.DoltDatabaseProvider
|
|
ContextFactory sql.ContextFactory
|
|
dsessFactory sessionFactory
|
|
engine *gms.Engine
|
|
fs filesys.Filesys
|
|
}
|
|
|
|
type sessionFactory func(mysqlSess *sql.BaseSession, pro sql.DatabaseProvider) (*dsess.DoltSession, error)
|
|
type contextFactory func(ctx context.Context, session sql.Session) (*sql.Context, error)
|
|
|
|
type SystemVariables map[string]interface{}
|
|
|
|
type SqlEngineConfig struct {
|
|
IsReadOnly bool
|
|
IsServerLocked bool
|
|
DoltCfgDirPath string
|
|
PrivFilePath string
|
|
BranchCtrlFilePath string
|
|
ServerUser string
|
|
ServerPass string
|
|
ServerHost string
|
|
SkipRootUserInitialization bool
|
|
Autocommit bool
|
|
DoltTransactionCommit bool
|
|
Bulk bool
|
|
JwksConfig []servercfg.JwksConfig
|
|
SystemVariables SystemVariables
|
|
ClusterController *cluster.Controller
|
|
AutoGCController *sqle.AutoGCController
|
|
BinlogReplicaController binlogreplication.BinlogReplicaController
|
|
EventSchedulerStatus eventscheduler.SchedulerStatus
|
|
BranchActivityTracking bool
|
|
EngineOverrides sql.EngineOverrides
|
|
|
|
// DBLoadParams are optional parameters passed through to database loading for local file-backed databases.
|
|
// These are merged into the params map used by doltdb/env load routines.
|
|
//
|
|
// Intended for embedded-driver use-cases that need to influence dbfactory / storage open behavior.
|
|
DBLoadParams map[string]interface{}
|
|
|
|
// ProviderFactory controls how the DatabaseProvider is created. If nil, sqle.DoltProviderFactory is used.
|
|
ProviderFactory sqle.ProviderFactory
|
|
|
|
FatalBehavior dherrors.FatalBehavior
|
|
}
|
|
|
|
type SqlEngineConfigOption func(*SqlEngineConfig)
|
|
|
|
// NewSqlEngine returns a SqlEngine
|
|
func NewSqlEngine(
|
|
ctx context.Context,
|
|
mrEnv *env.MultiRepoEnv,
|
|
config *SqlEngineConfig,
|
|
) (*SqlEngine, error) {
|
|
// Context validation is a testing mode that we run Dolt in
|
|
// during integration tests. It asserts that `context.Context`
|
|
// instances which reach the storage layer have gone through
|
|
// GC session lifecycle callbacks. This is only relevant in
|
|
// sql mode, so we only enable it here. This is potentially
|
|
// relevant in non-sql-server contexts, because things like
|
|
// replication and events can still cause concurrency during a
|
|
// GC, so we put this here instead of in sql-server.
|
|
const contextValidationEnabledEnvVar = "DOLT_CONTEXT_VALIDATION_ENABLED"
|
|
if val := os.Getenv(contextValidationEnabledEnvVar); val != "" && val != "0" && strings.ToLower(val) != "false" {
|
|
valctx.EnableContextValidation()
|
|
}
|
|
|
|
gcSafepointController := gcctx.NewGCSafepointController()
|
|
ctx = gcctx.WithGCSafepointController(ctx, gcSafepointController)
|
|
|
|
defer gcctx.SessionEnd(ctx)
|
|
gcctx.SessionCommandBegin(ctx)
|
|
defer gcctx.SessionCommandEnd(ctx)
|
|
|
|
// Thread DB load params into each environment before any DB is loaded.
|
|
// (For already-loaded envs, these will not affect the existing instance.)
|
|
if config != nil && len(config.DBLoadParams) > 0 {
|
|
_ = mrEnv.Iter(func(_ string, dEnv *env.DoltEnv) (stop bool, err error) {
|
|
if dEnv.DBLoadParams == nil {
|
|
dEnv.DBLoadParams = maps.Clone(config.DBLoadParams)
|
|
} else {
|
|
maps.Copy(dEnv.DBLoadParams, config.DBLoadParams)
|
|
}
|
|
return false, nil
|
|
})
|
|
}
|
|
|
|
// Some database initialization logic depends on system variables, load them before the databases
|
|
err := applySystemVariables(sql.SystemVariables, config.SystemVariables)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dbs, locations, err := CollectDBs(ctx, mrEnv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bThreads := sql.NewBackgroundThreads()
|
|
var runAsyncThreads sqle.RunAsyncThreads
|
|
dbs, runAsyncThreads, err = sqle.ApplyReplicationConfig(ctx, mrEnv, cli.CliOut, dbs...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config.ClusterController.ManageSystemVariables(sql.SystemVariables)
|
|
|
|
err = config.ClusterController.ApplyStandbyReplicationConfig(ctx, mrEnv, dbs...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Make a copy of the databases. |all| is going to be provided
|
|
// as the set of all initial databases to dsqle
|
|
// DatabaseProvider. |dbs| is only the databases that came
|
|
// from MultiRepoEnv, and they are all real databases based on
|
|
// DoltDB instances. |all| is going to include some extension,
|
|
// informational databases like |dolt_cluster| sometimes,
|
|
// depending on config.
|
|
all := make([]dsess.SqlDatabase, len(dbs))
|
|
copy(all, dbs)
|
|
|
|
clusterDB := config.ClusterController.ClusterDatabase()
|
|
if clusterDB != nil {
|
|
all = append(all, clusterDB.(dsess.SqlDatabase))
|
|
locations = append(locations, nil)
|
|
}
|
|
|
|
factory := config.ProviderFactory
|
|
if factory == nil {
|
|
factory = sqle.DoltProviderFactory{}
|
|
}
|
|
|
|
b := env.GetDefaultInitBranch(mrEnv.Config())
|
|
engineProvider, err := factory.NewProvider(b, mrEnv.FileSystem(), all, locations, config.EngineOverrides)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Extract the underlying *DoltDatabaseProvider for Dolt-specific configuration. For the
|
|
// default DoltProviderFactory the result IS a *DoltDatabaseProvider; custom factories
|
|
// (e.g. Doltgres) return a wrapper and implement DoltProviderUnwrapper to expose it.
|
|
var pro *sqle.DoltDatabaseProvider
|
|
switch p := engineProvider.(type) {
|
|
case *sqle.DoltDatabaseProvider:
|
|
pro = p
|
|
case sqle.DoltProviderUnwrapper:
|
|
pro = p.UnderlyingDoltProvider()
|
|
default:
|
|
return nil, fmt.Errorf("provider %T must be or wrap a *sqle.DoltDatabaseProvider", engineProvider)
|
|
}
|
|
|
|
pro.SetRemoteDialer(mrEnv.RemoteDialProvider())
|
|
if config != nil && len(config.DBLoadParams) > 0 {
|
|
pro.SetDBLoadParams(config.DBLoadParams)
|
|
}
|
|
|
|
config.ClusterController.RegisterStoredProcedures(pro)
|
|
if config.ClusterController != nil {
|
|
pro.InitDatabaseHooks = append(pro.InitDatabaseHooks, cluster.NewInitDatabaseHook(config.ClusterController, bThreads))
|
|
pro.DropDatabaseHooks = append(pro.DropDatabaseHooks, config.ClusterController.DropDatabaseHook())
|
|
config.ClusterController.SetDropDatabase(pro.DropDatabase)
|
|
}
|
|
|
|
sqlEngine := &SqlEngine{}
|
|
// Create the engine
|
|
engine := gms.New(analyzer.NewBuilder(engineProvider).AddOverrides(config.EngineOverrides).Build(), &gms.Config{
|
|
IsReadOnly: config.IsReadOnly,
|
|
IsServerLocked: config.IsServerLocked,
|
|
}).WithBackgroundThreads(bThreads)
|
|
|
|
if err := configureBinlogPrimaryController(engine); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config.ClusterController.SetIsStandbyCallback(func(isStandby bool) {
|
|
pro.SetIsStandby(isStandby)
|
|
|
|
// Standbys are read only, primaries respect the config. Update both engine.ReadOnly and the read_only system
|
|
// variable.
|
|
readOnly := isStandby || config.IsReadOnly
|
|
engine.ReadOnly.Store(readOnly)
|
|
|
|
readOnlyInt := int8(0)
|
|
if readOnly {
|
|
readOnlyInt = 1
|
|
}
|
|
sql.SystemVariables.AssignValues(map[string]interface{}{
|
|
"read_only": readOnlyInt,
|
|
})
|
|
})
|
|
|
|
// Load in privileges from file, if it exists
|
|
var persister cluster.MySQLDbPersister
|
|
persister = mysql_file_handler.NewPersister(config.PrivFilePath, config.DoltCfgDirPath)
|
|
|
|
persister = config.ClusterController.HookMySQLDbPersister(persister, engine.Analyzer.Catalog.MySQLDb)
|
|
data, err := persister.LoadData(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load the branch control permissions, if they exist
|
|
var bcController *branch_control.Controller
|
|
if bcController, err = branch_control.LoadData(ctx, config.BranchCtrlFilePath, config.DoltCfgDirPath); err != nil {
|
|
return nil, err
|
|
}
|
|
config.ClusterController.HookBranchControlPersistence(bcController, mrEnv.FileSystem())
|
|
|
|
// Setup the engine.
|
|
engine.Analyzer.Catalog.MySQLDb.SetPersister(persister)
|
|
|
|
engine.Analyzer.Catalog.MySQLDb.SetPlugins(map[string]mysql_db.PlaintextAuthPlugin{
|
|
"authentication_dolt_jwt": NewAuthenticateDoltJWTPlugin(config.JwksConfig),
|
|
})
|
|
|
|
if config.AutoGCController != nil {
|
|
err = config.AutoGCController.RunBackgroundThread(bThreads, sqlEngine.NewDefaultContext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.AutoGCController.ApplyCommitHooks(ctx, mrEnv, dbs...)
|
|
pro.InitDatabaseHooks = append(pro.InitDatabaseHooks, config.AutoGCController.InitDatabaseHook())
|
|
pro.DropDatabaseHooks = append(pro.DropDatabaseHooks, config.AutoGCController.DropDatabaseHook())
|
|
// XXX: We force session aware safepoint controller if auto_gc is on.
|
|
dprocedures.UseSessionAwareSafepointController = true
|
|
sql.SystemVariables.AssignValues(map[string]interface{}{
|
|
dsess.DoltAutoGCEnabled: int8(1),
|
|
})
|
|
} else {
|
|
sql.SystemVariables.AssignValues(map[string]interface{}{
|
|
dsess.DoltAutoGCEnabled: int8(0),
|
|
})
|
|
}
|
|
|
|
var statsPro sql.StatsProvider
|
|
_, enabled, _ := sql.SystemVariables.GetGlobal(dsess.DoltStatsEnabled)
|
|
if enabled.(int8) == 1 {
|
|
statsPro = statspro.NewStatsController(logrus.StandardLogger(), bThreads, mrEnv.GetEnv(mrEnv.GetFirstDatabase()))
|
|
} else {
|
|
statsPro = statspro.StatsNoop{}
|
|
}
|
|
|
|
engine.Analyzer.Catalog.StatsProvider = statsPro
|
|
|
|
branchActivityTracker := doltdb.NewBranchActivityTracker(ctx, config.BranchActivityTracking)
|
|
|
|
engine.Analyzer.ExecBuilder = rowexec.NewBuilder(kvexec.Builder{}, engine.Analyzer.Overrides)
|
|
engine.Analyzer.ExecBuilder.Runner = engine.Analyzer.Runner
|
|
sessFactory := doltSessionFactory(pro, statsPro, mrEnv.Config(), bcController, gcSafepointController, config.Autocommit, branchActivityTracker)
|
|
sqlEngine.provider = pro
|
|
sqlEngine.dsessFactory = sessFactory
|
|
sqlEngine.ContextFactory = sqlContextFactory
|
|
sqlEngine.engine = engine
|
|
sqlEngine.fs = pro.FileSystem()
|
|
|
|
pro.InstallReplicationInitDatabaseHook(bThreads, sqlEngine.NewDefaultContext)
|
|
if err = config.ClusterController.RunCommitHooks(bThreads, sqlEngine.NewDefaultContext); err != nil {
|
|
return nil, err
|
|
}
|
|
if runAsyncThreads != nil {
|
|
if err = runAsyncThreads(bThreads, sqlEngine.NewDefaultContext); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
sqlCtx, err := sqlEngine.NewDefaultContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load MySQL Db information
|
|
if err = engine.Analyzer.Catalog.MySQLDb.LoadData(sqlCtx, data); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if dbg, ok := os.LookupEnv(dconfig.EnvSqlDebugLog); ok && strings.EqualFold(dbg, "true") {
|
|
engine.Analyzer.Debug = true
|
|
if verbose, ok := os.LookupEnv(dconfig.EnvSqlDebugLogVerbose); ok && strings.EqualFold(verbose, "true") {
|
|
engine.Analyzer.Verbose = true
|
|
}
|
|
}
|
|
|
|
err = sql.SystemVariables.SetGlobal(sqlCtx, dsess.DoltCommitOnTransactionCommit, config.DoltTransactionCommit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if engine.EventScheduler == nil {
|
|
err = configureEventScheduler(config, engine, sqlEngine.ContextFactory, sessFactory, pro)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
dblr.DoltBinlogConsumer.SetEngine(engine)
|
|
engine.Analyzer.Catalog.BinlogConsumer = dblr.DoltBinlogConsumer
|
|
|
|
if config.BinlogReplicaController != nil {
|
|
binLogSession, err := sessFactory(sql.NewBaseSession(), pro)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = configureBinlogReplicaController(config, engine, sqlEngine.ContextFactory, binLogSession)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return sqlEngine, nil
|
|
}
|
|
|
|
// NewRebasedSqlEngine returns a smalled rebased engine primarily used in filterbranch.
|
|
// TODO: migrate to provider
|
|
func NewRebasedSqlEngine(engine *gms.Engine, dbs map[string]dsess.SqlDatabase) *SqlEngine {
|
|
return &SqlEngine{
|
|
engine: engine,
|
|
}
|
|
}
|
|
|
|
func applySystemVariables(vars sql.SystemVariableRegistry, cfg SystemVariables) error {
|
|
if cfg != nil {
|
|
return vars.AssignValues(cfg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// InitStats initalizes stats threads. We separate construction
|
|
// from initialization because the session provider needs the
|
|
// *StatsCoordinator handle (stats and provider are cyclically
|
|
// dependent), but several other initialization steps race on
|
|
// session variables if stats threads are started too early.
|
|
// xxx: separating provider/stats dependency is tough because
|
|
// the session is the runtime source of truth for both.
|
|
func (se *SqlEngine) InitStats(ctx context.Context) error {
|
|
// configuring stats depends on sessionBuilder
|
|
// sessionBuilder needs ref to statsProv
|
|
pro := se.GetUnderlyingEngine().Analyzer.Catalog.DbProvider.(dsess.DoltDatabaseProvider)
|
|
sqlCtx, err := se.NewLocalContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer sql.SessionEnd(sqlCtx.Session)
|
|
sql.SessionCommandBegin(sqlCtx.Session)
|
|
defer sql.SessionCommandEnd(sqlCtx.Session)
|
|
dbs := pro.AllDatabases(sqlCtx)
|
|
statsPro := se.GetUnderlyingEngine().Analyzer.Catalog.StatsProvider
|
|
if sc, ok := statsPro.(*statspro.StatsController); ok {
|
|
_, memOnly, _ := sql.SystemVariables.GetGlobal(dsess.DoltStatsMemoryOnly)
|
|
sc.SetMemOnly(memOnly.(int8) == 1)
|
|
|
|
if adder, ok := se.GetUnderlyingEngine().Analyzer.Catalog.DbProvider.(sqle.DatabaseHookRegistrar); ok {
|
|
adder.AddInitDatabaseHook(statspro.NewInitDatabaseHook(sc))
|
|
adder.AddDropDatabaseHook(statspro.NewDropDatabaseHook(sc))
|
|
}
|
|
|
|
var sqlDbs []sql.Database
|
|
for _, db := range dbs {
|
|
sqlDbs = append(sqlDbs, db)
|
|
}
|
|
|
|
err = sc.Init(sqlCtx, pro, se.NewDefaultContext, sqlDbs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, paused, _ := sql.SystemVariables.GetGlobal(dsess.DoltStatsPaused); paused.(int8) == 0 {
|
|
if err = sc.Restart(sqlCtx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Databases returns a slice of all databases in the engine
|
|
func (se *SqlEngine) Databases(ctx *sql.Context) []dsess.SqlDatabase {
|
|
databases := se.provider.AllDatabases(ctx)
|
|
dbs := make([]dsess.SqlDatabase, len(databases))
|
|
for i := range databases {
|
|
dbs[i] = databases[i].(dsess.SqlDatabase)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewContext returns a new sql.Context with the given session.
|
|
func (se *SqlEngine) NewContext(ctx context.Context, session sql.Session) (*sql.Context, error) {
|
|
return se.ContextFactory(ctx, sql.WithSession(session)), nil
|
|
}
|
|
|
|
// NewDefaultContext returns a new sql.Context with a new default dolt session.
|
|
func (se *SqlEngine) NewDefaultContext(ctx context.Context) (*sql.Context, error) {
|
|
session, err := se.NewDoltSession(ctx, sql.NewBaseSession())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return se.ContextFactory(ctx, sql.WithSession(session)), nil
|
|
}
|
|
|
|
// NewLocalContext returns a new |sql.Context| with its client set to |root|
|
|
func (se *SqlEngine) NewLocalContext(ctx context.Context) (*sql.Context, error) {
|
|
sqlCtx, err := se.NewDefaultContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sqlCtx.Session.SetClient(sql.Client{User: "root", Address: "%", Capabilities: 0})
|
|
return sqlCtx, nil
|
|
}
|
|
|
|
// NewDoltSession creates a new DoltSession from a BaseSession
|
|
func (se *SqlEngine) NewDoltSession(_ context.Context, mysqlSess *sql.BaseSession) (*dsess.DoltSession, error) {
|
|
return se.dsessFactory(mysqlSess, se.provider)
|
|
}
|
|
|
|
// Query execute a SQL statement and return values for printing.
|
|
func (se *SqlEngine) Query(ctx *sql.Context, query string) (sql.Schema, sql.RowIter, *sql.QueryFlags, error) {
|
|
return se.engine.Query(ctx, query)
|
|
}
|
|
|
|
func (se *SqlEngine) QueryWithBindings(ctx *sql.Context, query string, parsed sqlparser.Statement, bindings map[string]sqlparser.Expr, qFlags *sql.QueryFlags) (sql.Schema, sql.RowIter, *sql.QueryFlags, error) {
|
|
return se.engine.QueryWithBindings(ctx, query, parsed, bindings, qFlags)
|
|
}
|
|
|
|
// Analyze analyzes a node.
|
|
func (se *SqlEngine) Analyze(ctx *sql.Context, n sql.Node, qFlags *sql.QueryFlags) (sql.Node, error) {
|
|
return se.engine.Analyzer.Analyze(ctx, n, nil, qFlags)
|
|
}
|
|
|
|
func (se *SqlEngine) GetUnderlyingEngine() *gms.Engine {
|
|
return se.engine
|
|
}
|
|
|
|
func (se *SqlEngine) FileSystem() filesys.Filesys {
|
|
return se.fs
|
|
}
|
|
|
|
func (se *SqlEngine) Close() error {
|
|
var err error
|
|
if se.engine != nil {
|
|
if se.engine.Analyzer.Catalog.BinlogReplicaController != nil {
|
|
dblr.DoltBinlogReplicaController.Close()
|
|
}
|
|
err = se.engine.Close()
|
|
}
|
|
if se.provider != nil {
|
|
se.provider.Teardown(context.Background())
|
|
se.provider.Close()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// configureBinlogReplicaController configures the binlog replication controller with the |engine|.
|
|
func configureBinlogReplicaController(config *SqlEngineConfig, engine *gms.Engine, ctxFactory sql.ContextFactory, session *dsess.DoltSession) error {
|
|
executionCtx := ctxFactory(context.Background(), sql.WithSession(session))
|
|
dblr.DoltBinlogReplicaController.SetExecutionContext(executionCtx)
|
|
dblr.DoltBinlogReplicaController.SetEngine(engine)
|
|
engine.Analyzer.Catalog.BinlogReplicaController = config.BinlogReplicaController
|
|
|
|
return nil
|
|
}
|
|
|
|
// configureBinlogPrimaryController configures the |engine| to use the default Dolt binlog primary controller, as well
|
|
// as enabling the binlog producer if @@log_bin has been set to 1.
|
|
//
|
|
// NOTE: By default, binary logging for Dolt is not enabled, which differs from MySQL's @@log_bin default. Dolt's
|
|
// binary logging is initially an opt-in feature, but we may change that after measuring and tuning the
|
|
// performance hit that binary logging adds.
|
|
func configureBinlogPrimaryController(engine *gms.Engine) error {
|
|
primaryController := dblr.NewDoltBinlogPrimaryController()
|
|
engine.Analyzer.Catalog.BinlogPrimaryController = primaryController
|
|
return nil
|
|
}
|
|
|
|
// configureEventScheduler configures the event scheduler with the |engine| for executing events, a |sessFactory|
|
|
// for creating sessions, and a DoltDatabaseProvider, |pro|.
|
|
func configureEventScheduler(config *SqlEngineConfig, engine *gms.Engine, ctxFactory sql.ContextFactory, sessFactory sessionFactory, pro *sqle.DoltDatabaseProvider) error {
|
|
// getCtxFunc is used to create new session with a new context for event scheduler.
|
|
// The session's client is set to the event_scheduler superuser so that the event
|
|
// executor has access to all databases when privileges are enabled.
|
|
getCtxFunc := func() (*sql.Context, error) {
|
|
sess, err := sessFactory(sql.NewBaseSession(), pro)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rootCtx := context.Background()
|
|
markedCtx := context.WithValue(rootCtx, doltdb.EventSessionContextKey, true)
|
|
|
|
sqlCtx := ctxFactory(markedCtx, sql.WithSession(sess))
|
|
sqlCtx.Session.SetClient(sql.Client{
|
|
User: "event_scheduler",
|
|
Address: "localhost",
|
|
})
|
|
return sqlCtx, nil
|
|
}
|
|
|
|
// A hidden env var allows overriding the event scheduler period for testing. This option is not
|
|
// exposed via configuration because we do not want to encourage customers to use it. If the value
|
|
// is equal to or less than 0, then the period is ignored and the default period, 30s, is used.
|
|
eventSchedulerPeriod := 0
|
|
eventSchedulerPeriodEnvVar := "DOLT_EVENT_SCHEDULER_PERIOD"
|
|
if s, ok := os.LookupEnv(eventSchedulerPeriodEnvVar); ok {
|
|
i, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
logrus.Warnf("unable to parse value '%s' from env var '%s' as an integer", s, eventSchedulerPeriodEnvVar)
|
|
} else {
|
|
logrus.Warnf("overriding Dolt event scheduler period to %d seconds", i)
|
|
eventSchedulerPeriod = i
|
|
}
|
|
}
|
|
|
|
err := engine.InitializeEventScheduler(getCtxFunc, config.EventSchedulerStatus, eventSchedulerPeriod)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// When a new database is registered (via clone, restore, or undrop), wake the
|
|
// event executor so it can discover any events in the new database.
|
|
pro.InitDatabaseHooks = append(pro.InitDatabaseHooks, func(ctx *sql.Context, _ *sqle.DoltDatabaseProvider, _ string, _ *env.DoltEnv, _ dsess.SqlDatabase) error {
|
|
engine.EventScheduler.NotifyDatabaseAdded()
|
|
return nil
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// sqlContextFactory returns a contextFactory that creates a new sql.Context with the given session
|
|
func sqlContextFactory(ctx context.Context, opts ...sql.ContextOption) *sql.Context {
|
|
ctx = valctx.WithContextValidation(ctx)
|
|
sqlCtx := sql.NewContext(ctx, opts...)
|
|
if sqlCtx.Session != nil {
|
|
valctx.SetContextValidation(ctx, dsess.DSessFromSess(sqlCtx.Session).Validate)
|
|
}
|
|
return sqlCtx
|
|
}
|
|
|
|
// doltSessionFactory returns a sessionFactory that creates a new DoltSession
|
|
func doltSessionFactory(
|
|
pro *sqle.DoltDatabaseProvider,
|
|
statsPro sql.StatsProvider,
|
|
config config.ReadWriteConfig,
|
|
bc *branch_control.Controller,
|
|
gcSafepointController *gcctx.GCSafepointController,
|
|
autocommit bool,
|
|
tracker *doltdb.BranchActivityTracker) sessionFactory {
|
|
return func(mysqlSess *sql.BaseSession, provider sql.DatabaseProvider) (*dsess.DoltSession, error) {
|
|
doltSession, err := dsess.NewDoltSession(mysqlSess, pro, config, bc, statsPro, writer.NewWriteSession, gcSafepointController, tracker)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// nil ctx is actually fine in this context, not used in setting a session variable. Creating a new context isn't
|
|
// free, and would be throwaway work, since we need to create a session before creating a sql.Context for user work.
|
|
err = doltSession.SetSessionVariable(nil, sql.AutoCommitSessionVar, autocommit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return doltSession, nil
|
|
}
|
|
}
|
|
|
|
var ErrFailedToInitCommitIdent = fmt.Errorf("failed to initialize commit identity session variables from environment")
|
|
|
|
// envSessionVar pairs an environment variable with the session variable it populates when non-empty.
|
|
type envSessionVar struct {
|
|
envVar string
|
|
sessionVar string
|
|
}
|
|
|
|
// commitIdentEnvOverrides lists the DOLT_AUTHOR_* and DOLT_COMMITTER_* environment variables
|
|
// that [InitClientCommitIdentSession] maps to their corresponding session variables.
|
|
var commitIdentEnvOverrides = []envSessionVar{
|
|
{dconfig.EnvDoltAuthorName, dsess.DoltAuthorName},
|
|
{dconfig.EnvDoltAuthorEmail, dsess.DoltAuthorEmail},
|
|
{dconfig.EnvDoltAuthorDate, dsess.DoltAuthorDate},
|
|
{dconfig.EnvDoltCommitterName, dsess.DoltCommitterName},
|
|
{dconfig.EnvDoltCommitterEmail, dsess.DoltCommitterEmail},
|
|
{dconfig.EnvDoltCommitterDate, dsess.DoltCommitterDate},
|
|
}
|
|
|
|
// InitClientCommitIdentSession seeds the DOLT_ author and committer session variables on the
|
|
// dolt CLI client's session so later DOLT_COMMIT calls attribute the commit correctly. |name|
|
|
// and |email| supply the default identity for both author and committer; any non-empty
|
|
// DOLT_AUTHOR_* or DOLT_COMMITTER_* environment variable overrides the matching field. Must run
|
|
// after [sql.SessionCommandBegin] so the session is ready to accept SET statements. Silently
|
|
// skips variables not recognised by |queryist|, so older servers remain usable.
|
|
func InitClientCommitIdentSession(queryist cli.Queryist, sqlCtx *sql.Context, name, email string) error {
|
|
sessionVars := map[string]string{
|
|
dsess.DoltAuthorName: name,
|
|
dsess.DoltAuthorEmail: email,
|
|
dsess.DoltCommitterName: name,
|
|
dsess.DoltCommitterEmail: email,
|
|
}
|
|
|
|
for _, pair := range commitIdentEnvOverrides {
|
|
if val := os.Getenv(pair.envVar); val != "" {
|
|
sessionVars[pair.sessionVar] = val
|
|
}
|
|
}
|
|
|
|
var showQuery strings.Builder
|
|
showQuery.WriteString("SHOW VARIABLES WHERE Variable_name IN (")
|
|
hasVariableNames := false
|
|
for variableName, value := range sessionVars {
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if hasVariableNames {
|
|
showQuery.WriteByte(',')
|
|
}
|
|
showQuery.WriteByte('\'')
|
|
showQuery.WriteString(variableName)
|
|
showQuery.WriteByte('\'')
|
|
hasVariableNames = true
|
|
}
|
|
if !hasVariableNames {
|
|
return nil
|
|
}
|
|
showQuery.WriteByte(')')
|
|
variableRows, err := cli.GetRowsForSql(queryist, sqlCtx, showQuery.String())
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", ErrFailedToInitCommitIdent, err)
|
|
}
|
|
|
|
var setStatement strings.Builder
|
|
setStatement.WriteString("SET ")
|
|
hasAssignments := false
|
|
for _, row := range variableRows {
|
|
variableName, ok := row[0].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
value, ok := sessionVars[strings.ToLower(variableName)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if hasAssignments {
|
|
setStatement.WriteString(", ")
|
|
}
|
|
fmt.Fprintf(&setStatement, "@@SESSION.%s = %q", variableName, value)
|
|
hasAssignments = true
|
|
}
|
|
if !hasAssignments {
|
|
return nil
|
|
}
|
|
if _, _, _, err := queryist.Query(sqlCtx, setStatement.String()); err != nil {
|
|
return fmt.Errorf("%w: %v", ErrFailedToInitCommitIdent, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ConfigOption func(*SqlEngineConfig)
|
|
|
|
// NewSqlEngineForEnv returns a SqlEngine configured for the environment provided, with a single root user.
|
|
// Returns the new engine, the first database name, and any error that occurred.
|
|
func NewSqlEngineForEnv(ctx context.Context, dEnv *env.DoltEnv, options ...ConfigOption) (*SqlEngine, string, error) {
|
|
mrEnv, err := env.MultiEnvForDirectory(ctx, dEnv.FS, dEnv)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
config := &SqlEngineConfig{
|
|
ServerUser: "root",
|
|
ServerHost: "localhost",
|
|
}
|
|
for _, opt := range options {
|
|
opt(config)
|
|
}
|
|
|
|
engine, err := NewSqlEngine(
|
|
ctx,
|
|
mrEnv,
|
|
config,
|
|
)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
if err := engine.InitStats(ctx); err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
return engine, mrEnv.GetFirstDatabase(), err
|
|
}
|