Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stubbing out status variables #2414

Merged
merged 11 commits into from
Mar 28, 2024
64 changes: 50 additions & 14 deletions enginetest/queries/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -7848,41 +7848,63 @@ Select * from (
Expected: []sql.Row{},
},
{
Query: `SHOW STATUS LIKE 'use_secondary_engine'`,
Query: `SHOW STATUS LIKE 'Aborted_clients'`,
Expected: []sql.Row{
{"use_secondary_engine", "ON"},
{"Aborted_clients", 0},
},
},
{
Query: `SHOW GLOBAL STATUS LIKE 'admin_port'`,
Query: `SHOW GLOBAL STATUS LIKE 'Aborted_clients'`,
Expected: []sql.Row{
{"admin_port", 33062},
{"Aborted_clients", 0},
},
},
{
Query: `SHOW SESSION STATUS LIKE 'auto_increment_increment'`,
Query: `SHOW GLOBAL STATUS LIKE 'Bytes_sent'`,
Expected: []sql.Row{
{"auto_increment_increment", 1},
{"Bytes_sent", 0},
},
},
{
Query: `SHOW GLOBAL STATUS LIKE 'use_secondary_engine'`,
Expected: []sql.Row{},
Query: `SHOW SESSION STATUS LIKE 'Bytes_sent'`,
Expected: []sql.Row{
{"Bytes_sent", 0},
},
},
{
Query: `SHOW SESSION STATUS LIKE 'version'`,
Expected: []sql.Row{},
Query: `SHOW GLOBAL STATUS LIKE 'Com\_stmt\_%'`,
Expected: []sql.Row{
{"Com_stmt_close", 0},
{"Com_stmt_execute", 0},
{"Com_stmt_fetch", 0},
{"Com_stmt_prepare", 0},
{"Com_stmt_reprepare", 0},
{"Com_stmt_reset", 0},
{"Com_stmt_send_long_data", 0},
},
},
{
Query: `SHOW SESSION STATUS LIKE 'Ssl_cipher'`,
Expected: []sql.Row{}, // TODO: should be added at some point
Query: `SHOW SESSION STATUS LIKE 'Com\_stmt\_%'`,
Expected: []sql.Row{
{"Com_stmt_close", 0},
{"Com_stmt_execute", 0},
{"Com_stmt_fetch", 0},
{"Com_stmt_prepare", 0},
{"Com_stmt_reprepare", 0},
{"Com_stmt_reset", 0},
{"Com_stmt_send_long_data", 0},
},
},
{
Query: `SHOW SESSION STATUS WHERE Value < 0`,
Query: `SHOW SESSION STATUS LIKE 'Ssl_cipher'`,
Expected: []sql.Row{
{"optimizer_trace_offset", -1},
{"Ssl_cipher", ""},
},
},
{
Query: `SHOW SESSION STATUS WHERE Value < 0`,
Expected: []sql.Row{},
},
{
Query: `SELECT a.* FROM invert_pk as a, invert_pk as b WHERE a.y = b.z`,
Expected: []sql.Row{
Expand Down Expand Up @@ -9801,6 +9823,20 @@ FROM mytable;`,
{"DECIMAL"},
},
},
// show variables like ... is case-insensitive
{
Query: "SHOW VARIABLES LIKE 'VERSION'",
Expected: []sql.Row{
{"version", "8.0.11"},
},
},
// show status like ... is case-insensitive
{
Query: `SHOW VARIABLES LIKE 'aborted\_clients'`,
Expected: []sql.Row{
{"Aborted_clients", "0"},
},
},
}

var VersionedQueries = []QueryTest{
Expand Down
56 changes: 48 additions & 8 deletions sql/base_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type BaseSession struct {
currentDB string
transactionDb string
systemVars map[string]SystemVarValue
statusVars map[string]StatusVarValue
userVars SessionUserVariables
idxReg *IndexRegistry
viewReg *ViewRegistry
Expand Down Expand Up @@ -230,6 +231,31 @@ func (s *BaseSession) GetUserVariable(ctx *Context, varName string) (Type, inter
return s.userVars.GetUserVariable(ctx, varName)
}

// GetStatusVariable implements the Session interface.
func (s *BaseSession) GetStatusVariable(ctx *Context, statVarName string) (interface{}, error) {
s.mu.RLock()
defer s.mu.RUnlock()

statVar, ok := s.statusVars[statVarName]
if !ok {
return nil, ErrUnknownSystemVariable.New(statVarName)
}
return statVar.Val, nil
}

// SetStatusVariable implements the Session interface.
func (s *BaseSession) SetStatusVariable(ctx *Context, statVarName string, value interface{}) error {
s.mu.Lock()
defer s.mu.Unlock()
statVar, ok := s.statusVars[statVarName]
if !ok {
return ErrUnknownSystemVariable.New(statVarName)
}
statVar.Val = value
s.statusVars[statVarName] = statVar
return nil
}

// GetCharacterSet returns the character set for this session (defined by the system variable `character_set_connection`).
func (s *BaseSession) GetCharacterSet() CharacterSetID {
s.mu.RLock()
Expand Down Expand Up @@ -508,17 +534,24 @@ func BaseSessionFromConnection(ctx context.Context, c *mysql.Conn, addr string)
// NewBaseSessionWithClientServer creates a new session with data.
func NewBaseSessionWithClientServer(server string, client Client, id uint32) *BaseSession {
// TODO: if system variable "activate_all_roles_on_login" if set, activate all roles
var sessionVars map[string]SystemVarValue
var systemVars map[string]SystemVarValue
if SystemVariables != nil {
sessionVars = SystemVariables.NewSessionMap()
systemVars = SystemVariables.NewSessionMap()
} else {
sessionVars = make(map[string]SystemVarValue)
systemVars = make(map[string]SystemVarValue)
}
var statusVars map[string]StatusVarValue
if StatusVariables != nil {
statusVars = StatusVariables.NewSessionMap()
} else {
statusVars = make(map[string]StatusVarValue)
}
return &BaseSession{
addr: server,
client: client,
id: id,
systemVars: sessionVars,
systemVars: systemVars,
statusVars: statusVars,
userVars: NewUserVars(),
idxReg: NewIndexRegistry(),
viewReg: NewViewRegistry(),
Expand All @@ -532,15 +565,22 @@ func NewBaseSessionWithClientServer(server string, client Client, id uint32) *Ba
// NewBaseSession creates a new empty session.
func NewBaseSession() *BaseSession {
// TODO: if system variable "activate_all_roles_on_login" if set, activate all roles
var sessionVars map[string]SystemVarValue
var systemVars map[string]SystemVarValue
if SystemVariables != nil {
sessionVars = SystemVariables.NewSessionMap()
systemVars = SystemVariables.NewSessionMap()
} else {
systemVars = make(map[string]SystemVarValue)
}
var statusVars map[string]StatusVarValue
if StatusVariables != nil {
statusVars = StatusVariables.NewSessionMap()
} else {
sessionVars = make(map[string]SystemVarValue)
statusVars = make(map[string]StatusVarValue)
}
return &BaseSession{
id: atomic.AddUint32(&autoSessionIDs, 1),
systemVars: sessionVars,
systemVars: systemVars,
statusVars: statusVars,
userVars: NewUserVars(),
idxReg: NewIndexRegistry(),
viewReg: NewViewRegistry(),
Expand Down
63 changes: 62 additions & 1 deletion sql/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ func (m *MysqlSystemVariable) GetDefault() any {
return m.Default
}

// ForceSetValue implements SystemVariable.
// InitValue implements SystemVariable.
func (m *MysqlSystemVariable) InitValue(val any, global bool) (SystemVarValue, error) {
convertedVal, _, err := m.Type.Convert(val)
if err != nil {
Expand Down Expand Up @@ -695,3 +695,64 @@ type NameableNode interface {
Nameable
Node
}

var StatusVariables StatusVariableRegistry

// StatusVariableRegistry is a registry of status variables.
type StatusVariableRegistry interface {
NewSessionMap() map[string]StatusVarValue
GetGlobal(name string) (StatusVariable, interface{}, bool)
SetGlobal(name string, val interface{}) error
}

// StatusVariableScope represents the scope of a status variable.
type StatusVariableScope byte

const (
StatusVariableScope_Global StatusVariableScope = iota
StatusVariableScope_Session
StatusVariableScope_Both
)

type StatusVariable interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(minor) Even though this interface is super simple, it's still worth taking a couple of minutes to add some doc strings. If it's important enough to be an interface, it's generally worthwhile to add a sentence or two for the interface and methods.

(Alternatively, you could probably get rid of this interface and just expose the struct with this name. I see you matched the new interface for system vars that was added to give Doltgres more flexibility, but I don't think Postgres has the same concept of "status variables" like MySQL does, so it's probably not strictly needed. I don't have a strong opinion either way, and would probably just leave it in like you have it, just to match SystemVariables, but figured I'd mention it.)

GetName() string
GetScope() StatusVariableScope
GetType() Type
GetDefault() interface{}
}

// MySQLStatusVariable represents a mysql status variable.
type MySQLStatusVariable struct {
Name string
Scope StatusVariableScope
Type Type
Default interface{}
}

var _ StatusVariable = (*MySQLStatusVariable)(nil)

// GetName implements StatusVariable.
func (m *MySQLStatusVariable) GetName() string {
return m.Name
}

// GetScope implements StatusVariable.
func (m *MySQLStatusVariable) GetScope() StatusVariableScope {
return m.Scope
}

// GetType implements StatusVariable.
func (m *MySQLStatusVariable) GetType() Type {
return m.Type
}

// GetDefault implements StatusVariable.
func (m *MySQLStatusVariable) GetDefault() interface{} {
return m.Default
}

// StatusVarValue is a StatusVariable with a value.
type StatusVarValue struct {
Var StatusVariable
Val interface{}
}
3 changes: 3 additions & 0 deletions sql/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ var (
// ErrUnknownSystemVariable is returned when a query references a system variable that doesn't exist
ErrUnknownSystemVariable = errors.NewKind(`Unknown system variable '%s'`)

// ErrUnknownStatusVariable is returned when a query references a status variable that doesn't exist
ErrUnknownStatusVariable = errors.NewKind(`Unknown status variable '%s'`)

// ErrUnknownUserVariable is returned when a query references a user variable that doesn't exist
ErrUnknownUserVariable = errors.NewKind(`Unknown user variable '%s'`)

Expand Down
57 changes: 29 additions & 28 deletions sql/plan/show_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,24 @@ const ShowStatusVariableCol = "Variable_name"
const ShowStatusValueCol = "Value"

// ShowStatus implements the SHOW STATUS MySQL command.
// TODO: This is just a stub implementation that returns an empty set. The actual functionality needs to be implemented
// in the future.
type ShowStatus struct {
Modifier ShowStatusModifier
isGlobal bool
}

var _ sql.Node = (*ShowStatus)(nil)
var _ sql.CollationCoercible = (*ShowStatus)(nil)

type ShowStatusModifier byte

const (
ShowStatusModifier_Session ShowStatusModifier = iota
ShowStatusModifier_Global
)

// NewShowStatus returns a new ShowStatus reference.
func NewShowStatus(modifier ShowStatusModifier) *ShowStatus {
return &ShowStatus{Modifier: modifier}
func NewShowStatus(isGlobal bool) *ShowStatus {
return &ShowStatus{isGlobal: isGlobal}
}

// Resolved implements sql.Node interface.
func (s *ShowStatus) Resolved() bool {
return true
}

// IsReadOnly implements sql.Node interface.
func (s *ShowStatus) IsReadOnly() bool {
return true
}
Expand All @@ -66,8 +58,18 @@ func (s *ShowStatus) String() string {
// Schema implements sql.Node interface.
func (s *ShowStatus) Schema() sql.Schema {
return sql.Schema{
{Name: ShowStatusVariableCol, Type: types.MustCreateStringWithDefaults(sqltypes.VarChar, 64), Default: nil, Nullable: false},
{Name: ShowStatusValueCol, Type: types.MustCreateStringWithDefaults(sqltypes.VarChar, 2048), Default: nil, Nullable: false},
{
Name: ShowStatusVariableCol,
Type: types.MustCreateStringWithDefaults(sqltypes.VarChar, 64),
Default: nil,
Nullable: false,
},
{
Name: ShowStatusValueCol,
Type: types.MustCreateStringWithDefaults(sqltypes.VarChar, 2048),
Default: nil,
Nullable: false,
},
}
}

Expand All @@ -77,38 +79,37 @@ func (s *ShowStatus) Children() []sql.Node {
}

// RowIter implements sql.Node interface.
func (s *ShowStatus) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) {
func (s *ShowStatus) RowIter(ctx *sql.Context, _ sql.Row) (sql.RowIter, error) {
vars := sql.StatusVariables.NewSessionMap()
var names []string
for name := range sql.SystemVariables.NewSessionMap() {
for name := range vars {
names = append(names, name)
}
sort.Strings(names)

var rows []sql.Row
for _, name := range names {
sysVar, val, ok := sql.SystemVariables.GetGlobal(name)
sysVarVal, ok := vars[name]
if !ok {
return nil, fmt.Errorf("missing system variable %s", name)
}
msv, ok := sysVar.(*sql.MysqlSystemVariable)
if !ok {
continue
if s.isGlobal {
_, val, ok := sql.StatusVariables.GetGlobal(name)
if !ok {
return nil, fmt.Errorf("missing global system variable %s", name)
}
rows = append(rows, sql.Row{name, val})
} else {
rows = append(rows, sql.Row{name, sysVarVal.Val})
}

if s.Modifier == ShowStatusModifier_Session && msv.Scope.IsGlobalOnly() ||
s.Modifier == ShowStatusModifier_Global && msv.Scope.IsSessionOnly() {
continue
}

rows = append(rows, sql.Row{name, val})
}

return sql.RowsToRowIter(rows...), nil
}

// WithChildren implements sql.Node interface.
func (s *ShowStatus) WithChildren(node ...sql.Node) (sql.Node, error) {
return NewShowStatus(s.Modifier), nil
return NewShowStatus(s.isGlobal), nil
}

// CheckPrivileges implements the interface sql.Node.
Expand Down