Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/dolthub/go-mysql-server/sql/analyzer"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/expression/function"
_ "github.com/dolthub/go-mysql-server/sql/hooks/noop"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/go-mysql-server/sql/planbuilder"
"github.com/dolthub/go-mysql-server/sql/rowexec"
Expand Down
110 changes: 110 additions & 0 deletions sql/hooks/hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2025 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 hooks

import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
)

// Global is a variable that contains the interface for calling hooks. By default, this contains no-op hooks as
// integrators may implement their own hooks. This variable should be overwritten by the integrator.
var Global Hooks
Copy link
Member

Choose a reason for hiding this comment

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

Rest of the interface is fine, but we should avoid having a global here if at all possible. Instead, put this on the analyzer builder, and then it can be assigned to the session for use in the exec stuff.


// Hooks is an interface that represents various hooks that are called within a statement's lifecycle.
type Hooks interface {
// Table returns hooks related to direct table statements.
Table() Table
Copy link
Member

Choose a reason for hiding this comment

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

All of these should probably be plural, returning either a slice of hooks or an iterator over them. We should assume that there is going to be a chain of hooks that integrators set up to run in some order they choose.

// TableColumn returns hooks related to table column statements.
TableColumn() TableColumn
}

// Table contains hooks related to direct table statements.
type Table interface {
// Create returns hooks related to CREATE TABLE statements.
Create() CreateTable
// Rename returns hooks related to RENAME TABLE statements.
Rename() RenameTable
// Drop returns hooks related to DROP TABLE statements.
Drop() DropTable
}

// TableColumn contains hooks related to table column statements.
type TableColumn interface {
// Add returns hooks related to ALTER TABLE ... ADD COLUMN statements.
Add() TableAddColumn
// Rename returns hooks related to ALTER TABLE ... RENAME COLUMN statements.
Rename() TableRenameColumn
// Modify returns hooks related to ALTER TABLE ... MODIFY COLUMN statements.
Modify() TableModifyColumn
// Drop returns hooks related to ALTER TABLE ... DROP COLUMN statements.
Drop() TableDropColumn
}

// CreateTable contains hooks related to CREATE TABLE statements.
type CreateTable interface {
// PreSQLExecution is called before the final step of statement execution, after analysis.
PreSQLExecution(*sql.Context, *plan.CreateTable) (*plan.CreateTable, error)
// PostSQLExecution is called after the final step of statement execution, after analysis.
PostSQLExecution(*sql.Context, *plan.CreateTable) error
}

// RenameTable contains hooks related to RENAME TABLE statements.
type RenameTable interface {
// PreSQLExecution is called before the final step of statement execution, after analysis.
PreSQLExecution(*sql.Context, *plan.RenameTable) (*plan.RenameTable, error)
// PostSQLExecution is called after the final step of statement execution, after analysis.
PostSQLExecution(*sql.Context, *plan.RenameTable) error
}

// DropTable contains hooks related to DROP TABLE statements.
type DropTable interface {
// PreSQLExecution is called before the final step of statement execution, after analysis.
PreSQLExecution(*sql.Context, *plan.DropTable) (*plan.DropTable, error)
// PostSQLExecution is called after the final step of statement execution, after analysis.
PostSQLExecution(*sql.Context, *plan.DropTable) error
}

// TableAddColumn contains hooks related to ALTER TABLE ... ADD COLUMN statements.
type TableAddColumn interface {
// PreSQLExecution is called before the final step of statement execution, after analysis.
PreSQLExecution(*sql.Context, *plan.AddColumn) (*plan.AddColumn, error)
// PostSQLExecution is called after the final step of statement execution, after analysis.
PostSQLExecution(*sql.Context, *plan.AddColumn) error
}

// TableRenameColumn contains hooks related to ALTER TABLE ... RENAME COLUMN statements.
type TableRenameColumn interface {
// PreSQLExecution is called before the final step of statement execution, after analysis.
PreSQLExecution(*sql.Context, *plan.RenameColumn) (*plan.RenameColumn, error)
// PostSQLExecution is called after the final step of statement execution, after analysis.
PostSQLExecution(*sql.Context, *plan.RenameColumn) error
}

// TableModifyColumn contains hooks related to ALTER TABLE ... MODIFY COLUMN statements.
type TableModifyColumn interface {
// PreSQLExecution is called before the final step of statement execution, after analysis.
PreSQLExecution(*sql.Context, *plan.ModifyColumn) (*plan.ModifyColumn, error)
// PostSQLExecution is called after the final step of statement execution, after analysis.
PostSQLExecution(*sql.Context, *plan.ModifyColumn) error
}

// TableDropColumn contains hooks related to ALTER TABLE ... DROP COLUMN statements.
type TableDropColumn interface {
// PreSQLExecution is called before the final step of statement execution, after analysis.
PreSQLExecution(*sql.Context, *plan.DropColumn) (*plan.DropColumn, error)
// PostSQLExecution is called after the final step of statement execution, after analysis.
PostSQLExecution(*sql.Context, *plan.DropColumn) error
}
192 changes: 192 additions & 0 deletions sql/hooks/noop/impl.go
Copy link
Member

Choose a reason for hiding this comment

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

Probably just put this in noop.go in the same package as the defn

Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2025 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 noop

import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/hooks"
"github.com/dolthub/go-mysql-server/sql/plan"
)

// init sets the global hooks to be no-ops by default. It is intended that the variable will be replaced by the
// integrator.
func init() {
hooks.Global = NoOp{}
}

// NoOp implements hooks.Hooks while having all operations be no-ops. Integrators may supply their own implementation.
type NoOp struct{}

var _ hooks.Hooks = NoOp{}

// Table implements the interface hooks.Hooks.
func (NoOp) Table() hooks.Table {
return Table{}
}

// TableColumn implements the interface hooks.Hooks.
func (NoOp) TableColumn() hooks.TableColumn {
return TableColumn{}
}

// Table implements no-ops for the hooks.Table interface.
type Table struct{}

var _ hooks.Table = Table{}

// Create implements the interface hooks.Table.
func (Table) Create() hooks.CreateTable {
return CreateTable{}
}

// Rename implements the interface hooks.Table.
func (Table) Rename() hooks.RenameTable {
return RenameTable{}
}

// Drop implements the interface hooks.Table.
func (Table) Drop() hooks.DropTable {
return DropTable{}
}

// TableColumn implements no-ops for the hooks.TableColumn interface.
type TableColumn struct{}

var _ hooks.TableColumn = TableColumn{}

// Add implements the interface hooks.TableColumn.
func (TableColumn) Add() hooks.TableAddColumn {
return TableAddColumn{}
}

// Rename implements the interface hooks.TableColumn.
func (TableColumn) Rename() hooks.TableRenameColumn {
return TableRenameColumn{}
}

// Modify implements the interface hooks.TableColumn.
func (TableColumn) Modify() hooks.TableModifyColumn {
return TableModifyColumn{}
}

// Drop implements the interface hooks.TableColumn.
func (TableColumn) Drop() hooks.TableDropColumn {
return TableDropColumn{}
}

// CreateTable implements no-ops for the hooks.CreateTable interface.
type CreateTable struct{}

var _ hooks.CreateTable = CreateTable{}

// PreSQLExecution implements the interface hooks.CreateTable.
func (CreateTable) PreSQLExecution(_ *sql.Context, n *plan.CreateTable) (*plan.CreateTable, error) {
return n, nil
}

// PostSQLExecution implements the interface hooks.CreateTable.
func (CreateTable) PostSQLExecution(_ *sql.Context, n *plan.CreateTable) error {
return nil
}

// RenameTable implements no-ops for the hooks.RenameTable interface.
type RenameTable struct{}

var _ hooks.RenameTable = RenameTable{}

// PreSQLExecution implements the interface hooks.RenameTable.
func (RenameTable) PreSQLExecution(_ *sql.Context, n *plan.RenameTable) (*plan.RenameTable, error) {
return n, nil
}

// PostSQLExecution implements the interface hooks.RenameTable.
func (RenameTable) PostSQLExecution(_ *sql.Context, n *plan.RenameTable) error {
return nil
}

// DropTable implements no-ops for the hooks.DropTable interface.
type DropTable struct{}

var _ hooks.DropTable = DropTable{}

// PreSQLExecution implements the interface hooks.DropTable.
func (DropTable) PreSQLExecution(_ *sql.Context, n *plan.DropTable) (*plan.DropTable, error) {
return n, nil
}

// PostSQLExecution implements the interface hooks.DropTable.
func (DropTable) PostSQLExecution(_ *sql.Context, n *plan.DropTable) error {
return nil
}

// TableAddColumn implements no-ops for the hooks.TableAddColumn interface.
type TableAddColumn struct{}

var _ hooks.TableAddColumn = TableAddColumn{}

// PreSQLExecution implements the interface hooks.TableAddColumn.
func (TableAddColumn) PreSQLExecution(_ *sql.Context, n *plan.AddColumn) (*plan.AddColumn, error) {
return n, nil
}

// PostSQLExecution implements the interface hooks.TableAddColumn.
func (TableAddColumn) PostSQLExecution(_ *sql.Context, n *plan.AddColumn) error {
return nil
}

// TableRenameColumn implements no-ops for the hooks.TableRenameColumn interface.
type TableRenameColumn struct{}

var _ hooks.TableRenameColumn = TableRenameColumn{}

// PreSQLExecution implements the interface hooks.TableRenameColumn.
func (TableRenameColumn) PreSQLExecution(_ *sql.Context, n *plan.RenameColumn) (*plan.RenameColumn, error) {
return n, nil
}

// PostSQLExecution implements the interface hooks.TableRenameColumn.
func (TableRenameColumn) PostSQLExecution(_ *sql.Context, n *plan.RenameColumn) error {
return nil
}

// TableModifyColumn implements no-ops for the hooks.TableModifyColumn interface.
type TableModifyColumn struct{}

var _ hooks.TableModifyColumn = TableModifyColumn{}

// PreSQLExecution implements the interface hooks.TableModifyColumn.
func (TableModifyColumn) PreSQLExecution(_ *sql.Context, n *plan.ModifyColumn) (*plan.ModifyColumn, error) {
return n, nil
}

// PostSQLExecution implements the interface hooks.TableModifyColumn.
func (TableModifyColumn) PostSQLExecution(_ *sql.Context, n *plan.ModifyColumn) error {
return nil
}

// TableDropColumn implements no-ops for the hooks.TableDropColumn interface.
type TableDropColumn struct{}

var _ hooks.TableDropColumn = TableDropColumn{}

// PreSQLExecution implements the interface hooks.TableDropColumn.
func (TableDropColumn) PreSQLExecution(_ *sql.Context, n *plan.DropColumn) (*plan.DropColumn, error) {
return n, nil
}

// PostSQLExecution implements the interface hooks.TableDropColumn.
func (TableDropColumn) PostSQLExecution(_ *sql.Context, n *plan.DropColumn) error {
return nil
}
30 changes: 3 additions & 27 deletions sql/plan/alter_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,30 +59,6 @@ func (r *RenameTable) IsReadOnly() bool {
return false
}

func (r *RenameTable) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) {
renamer, _ := r.Db.(sql.TableRenamer)
viewDb, _ := r.Db.(sql.ViewDatabase)
viewRegistry := ctx.GetViewRegistry()

for i, oldName := range r.OldNames {
if tbl, exists := r.tableExists(ctx, oldName); exists {
err := r.renameTable(ctx, renamer, tbl, oldName, r.NewNames[i])
if err != nil {
return nil, err
}
} else {
success, err := r.renameView(ctx, viewDb, viewRegistry, oldName, r.NewNames[i])
if err != nil {
return nil, err
} else if !success {
return nil, sql.ErrTableNotFound.New(oldName)
}
}
}

return sql.RowsToRowIter(sql.NewRow(types.NewOkResult(0))), nil
}

func (r *RenameTable) WithChildren(children ...sql.Node) (sql.Node, error) {
return NillaryWithChildren(r, children...)
}
Expand All @@ -92,15 +68,15 @@ func (*RenameTable) CollationCoercibility(ctx *sql.Context) (collation sql.Colla
return sql.Collation_binary, 7
}

func (r *RenameTable) tableExists(ctx *sql.Context, name string) (sql.Table, bool) {
func (r *RenameTable) TableExists(ctx *sql.Context, name string) (sql.Table, bool) {
tbl, ok, err := r.Db.GetTableInsensitive(ctx, name)
if err != nil || !ok {
return nil, false
}
return tbl, true
}

func (r *RenameTable) renameTable(ctx *sql.Context, renamer sql.TableRenamer, tbl sql.Table, oldName, newName string) error {
func (r *RenameTable) RenameTable(ctx *sql.Context, renamer sql.TableRenamer, tbl sql.Table, oldName, newName string) error {
if renamer == nil {
return sql.ErrRenameTableNotSupported.New(r.Db.Name())
}
Expand Down Expand Up @@ -160,7 +136,7 @@ func (r *RenameTable) renameTable(ctx *sql.Context, renamer sql.TableRenamer, tb
return nil
}

func (r *RenameTable) renameView(ctx *sql.Context, viewDb sql.ViewDatabase, vr *sql.ViewRegistry, oldName, newName string) (bool, error) {
func (r *RenameTable) RenameView(ctx *sql.Context, viewDb sql.ViewDatabase, vr *sql.ViewRegistry, oldName, newName string) (bool, error) {
if viewDb != nil {
oldView, exists, err := viewDb.GetViewDefinition(ctx, oldName)
if err != nil {
Expand Down
Loading
Loading