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

Added trigger statements #204

Merged
merged 2 commits into from
Oct 7, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
488 changes: 487 additions & 1 deletion enginetest/trigger_queries.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/dolthub/go-mysql-server
require (
github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
github.com/dolthub/vitess v0.0.0-20200925174744-823c7e177c3f
github.com/dolthub/vitess v0.0.0-20201006100455-a51dcc1cc2da
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
github.com/go-kit/kit v0.9.0
github.com/go-sql-driver/mysql v1.4.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dolthub/vitess v0.0.0-20200925174744-823c7e177c3f h1:37HLaUKuZMDOxKT8Lxq7Z1IZLiwTp6UKaDR7xC5aihM=
github.com/dolthub/vitess v0.0.0-20200925174744-823c7e177c3f/go.mod h1:hUE8oSk2H5JZnvtlLBhJPYC8WZCA5AoSntdLTcBvdBM=
github.com/dolthub/vitess v0.0.0-20201006100455-a51dcc1cc2da h1:2VuGtLb/uea8sI6SVMeODo59j7QvPNaXozKABJsIIMg=
github.com/dolthub/vitess v0.0.0-20201006100455-a51dcc1cc2da/go.mod h1:hUE8oSk2H5JZnvtlLBhJPYC8WZCA5AoSntdLTcBvdBM=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
Expand Down
4 changes: 2 additions & 2 deletions memory/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ var _ sql.TriggerDatabase = (*Database)(nil)
// NewDatabase creates a new database with the given name.
func NewDatabase(name string) *Database {
return &Database{
name: name,
tables: map[string]sql.Table{},
name: name,
tables: map[string]sql.Table{},
}
}

Expand Down
58 changes: 58 additions & 0 deletions sql/analyzer/load_show_triggers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2020 Liquidata, 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 analyzer

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

func loadShowTriggers(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql.Node, error) {
span, _ := ctx.Span("loadShowTriggers")
defer span.Finish()

return plan.TransformUp(n, func(n sql.Node) (sql.Node, error) {
switch node := n.(type) {
case *plan.ShowTriggers:
Hydrocharged marked this conversation as resolved.
Show resolved Hide resolved
newShowTriggers := *node
db := newShowTriggers.Database()
if triggerDb, ok := db.(sql.TriggerDatabase); ok {
triggers, err := triggerDb.GetTriggers(ctx)
if err != nil {
return nil, err
}
triggerPlans := make([]*plan.CreateTrigger, len(triggers))
for i, trigger := range triggers {
parsedTrigger, err := parse.Parse(ctx, trigger.CreateStatement)
if err != nil {
return nil, err
}
triggerPlan, ok := parsedTrigger.(*plan.CreateTrigger)
if !ok {
return nil, sql.ErrTriggerCreateStatementInvalid.New(trigger.CreateStatement)
}
triggerPlans[i] = triggerPlan
}
newShowTriggers.Triggers = triggerPlans
} else {
newShowTriggers.Triggers = make([]*plan.CreateTrigger, 0)
}
return &newShowTriggers, nil
default:
return node, nil
}
})
}
1 change: 1 addition & 0 deletions sql/analyzer/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var DefaultRules = []Rule{
// OnceAfterDefault contains the rules to be applied just once after the
// DefaultRules.
var OnceAfterDefault = []Rule{
{"load_show_triggers", loadShowTriggers},
{"resolve_column_defaults", resolveColumnDefaults},
{"resolve_generators", resolveGenerators},
{"remove_unnecessary_converts", removeUnnecessaryConverts},
Expand Down
6 changes: 3 additions & 3 deletions sql/analyzer/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func applyTriggers(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql
return n, nil
}

triggers := orderTriggers(affectedTriggers)
triggers := OrderTriggers(affectedTriggers)
originalNode := n

for _, trigger := range triggers {
Expand Down Expand Up @@ -320,7 +320,7 @@ func validateNoCircularUpdates(trigger *plan.CreateTrigger, n sql.Node, scope *S
return circularRef
}

func orderTriggers(triggers []*plan.CreateTrigger) []*plan.CreateTrigger {
func OrderTriggers(triggers []*plan.CreateTrigger) []*plan.CreateTrigger {
orderedTriggers := make([]*plan.CreateTrigger, len(triggers))
copy(orderedTriggers, triggers)

Expand All @@ -336,7 +336,7 @@ Top:
if trigger.TriggerOrder.PrecedesOrFollows == sqlparser.PrecedesStr {
orderedTriggers = append(orderedTriggers[:j], append(triggers[i:i+1], orderedTriggers[j:]...)...)
} else if trigger.TriggerOrder.PrecedesOrFollows == sqlparser.FollowsStr {
if len(orderedTriggers) == j - 1 {
if len(orderedTriggers) == j-1 {
orderedTriggers = append(orderedTriggers, triggers[i])
} else {
orderedTriggers = append(orderedTriggers[:j+1], append(triggers[i:i+1], orderedTriggers[j+1:]...)...)
Expand Down
5 changes: 4 additions & 1 deletion sql/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,15 @@ var (
// ErrDropColumnReferencedInDefault is returned when a column cannot be dropped as it is referenced by another column's default value.
ErrDropColumnReferencedInDefault = errors.NewKind(`cannot drop column "%s" as default value of column "%s" references it`)

// ErrTriggersNotSupported is returned when attepting to create a trigger on a database that doesn't support them
// ErrTriggersNotSupported is returned when attempting to create a trigger on a database that doesn't support them
ErrTriggersNotSupported = errors.NewKind(`database "%s" doesn't support triggers`)

// ErrTriggerCreateStatementInvalid is returned when a TriggerDatabase returns a CREATE TRIGGER statement that is invalid
ErrTriggerCreateStatementInvalid = errors.NewKind(`Invalid CREATE TRIGGER statement: %s`)

// ErrTriggerDoesNotExist is returned when a trigger does not exist.
ErrTriggerDoesNotExist = errors.NewKind(`trigger "%s" does not exist`)

// ErrTriggerTableInUse is returned when trigger execution calls for a table that invoked a trigger being updated by it
ErrTriggerTableInUse = errors.NewKind("Can't update table %s in stored function/trigger because it is already used by statement which invoked this stored function/trigger")

Expand Down
132 changes: 113 additions & 19 deletions sql/information_schema/information_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import (
"fmt"
"io"
"strings"
"time"

. "github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/analyzer"
"github.com/dolthub/go-mysql-server/sql/parse"
"github.com/dolthub/go-mysql-server/sql/plan"

"github.com/dolthub/vitess/go/vt/sqlparser"
)

const (
Expand Down Expand Up @@ -56,7 +61,7 @@ type informationSchemaTable struct {
name string
schema Schema
catalog *Catalog
rowIter func(*Context, *Catalog) RowIter
rowIter func(*Context, *Catalog) (RowIter, error)
}

type informationSchemaPartition struct {
Expand Down Expand Up @@ -355,7 +360,7 @@ var userPrivilegesSchema = Schema{
{Name: "is_grantable", Type: LongText, Default: nil, Nullable: false, Source: UserPrivilegesTableName},
}

func tablesRowIter(ctx *Context, cat *Catalog) RowIter {
func tablesRowIter(ctx *Context, cat *Catalog) (RowIter, error) {
var rows []Row
for _, db := range cat.AllDatabases() {
tableType := "BASE TABLE"
Expand Down Expand Up @@ -421,16 +426,15 @@ func tablesRowIter(ctx *Context, cat *Catalog) RowIter {
})
}

// TODO: fix panics
if err != nil {
panic(err)
return nil, err
}
}

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

func columnsRowIter(ctx *Context, cat *Catalog) RowIter {
func columnsRowIter(ctx *Context, cat *Catalog) (RowIter, error) {
var rows []Row
for _, db := range cat.AllDatabases() {
err := DBTableIter(ctx, db, func(t Table) (cont bool, err error) {
Expand Down Expand Up @@ -476,15 +480,14 @@ func columnsRowIter(ctx *Context, cat *Catalog) RowIter {
return true, nil
})

// TODO: fix panics
if err != nil {
panic(err)
return nil, err
}
}
return RowsToRowIter(rows...)
return RowsToRowIter(rows...), nil
}

func schemataRowIter(ctx *Context, c *Catalog) RowIter {
func schemataRowIter(ctx *Context, c *Catalog) (RowIter, error) {
dbs := c.AllDatabases()

var rows []Row
Expand All @@ -498,10 +501,10 @@ func schemataRowIter(ctx *Context, c *Catalog) RowIter {
})
}

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

func collationsRowIter(ctx *Context, c *Catalog) RowIter {
func collationsRowIter(ctx *Context, c *Catalog) (RowIter, error) {
var rows []Row
for c := range CollationToMySQLVals {
rows = append(rows, Row{
Expand All @@ -514,11 +517,102 @@ func collationsRowIter(ctx *Context, c *Catalog) RowIter {
c.PadSpace(),
})
}
return RowsToRowIter(rows...)
return RowsToRowIter(rows...), nil
}

func emptyRowIter(ctx *Context, c *Catalog) RowIter {
return RowsToRowIter()
func triggersRowIter(ctx *Context, c *Catalog) (RowIter, error) {
var rows []Row
for _, db := range c.AllDatabases() {
triggerDb, ok := db.(TriggerDatabase)
if ok {
triggers, err := triggerDb.GetTriggers(ctx)
if err != nil {
return nil, err
}
var triggerPlans []*plan.CreateTrigger
for _, trigger := range triggers {
parsedTrigger, err := parse.Parse(ctx, trigger.CreateStatement)
if err != nil {
return nil, err
}
triggerPlan, ok := parsedTrigger.(*plan.CreateTrigger)
if !ok {
return nil, ErrTriggerCreateStatementInvalid.New(trigger.CreateStatement)
}
triggerPlans = append(triggerPlans, triggerPlan)
}

triggerPlans = analyzer.OrderTriggers(triggerPlans)
var beforeDelete []*plan.CreateTrigger
var beforeInsert []*plan.CreateTrigger
var beforeUpdate []*plan.CreateTrigger
var afterDelete []*plan.CreateTrigger
var afterInsert []*plan.CreateTrigger
var afterUpdate []*plan.CreateTrigger
for _, triggerPlan := range triggerPlans {
switch triggerPlan.TriggerTime {
case sqlparser.BeforeStr:
switch triggerPlan.TriggerEvent {
case sqlparser.DeleteStr:
beforeDelete = append(beforeDelete, triggerPlan)
case sqlparser.InsertStr:
beforeInsert = append(beforeInsert, triggerPlan)
case sqlparser.UpdateStr:
beforeUpdate = append(beforeUpdate, triggerPlan)
}
case sqlparser.AfterStr:
// OrderTriggers reverses AFTER, so we must reverse them again
Hydrocharged marked this conversation as resolved.
Show resolved Hide resolved
switch triggerPlan.TriggerEvent {
case sqlparser.DeleteStr:
afterDelete = append([]*plan.CreateTrigger{triggerPlan}, afterDelete...)
case sqlparser.InsertStr:
afterInsert = append([]*plan.CreateTrigger{triggerPlan}, afterInsert...)
case sqlparser.UpdateStr:
afterUpdate = append([]*plan.CreateTrigger{triggerPlan}, afterUpdate...)
}
}
}

for _, planGroup := range [][]*plan.CreateTrigger{beforeDelete, beforeInsert, beforeUpdate, afterDelete, afterInsert, afterUpdate} {
Hydrocharged marked this conversation as resolved.
Show resolved Hide resolved
for order, triggerPlan := range planGroup {
triggerEvent := strings.ToUpper(triggerPlan.TriggerEvent)
triggerTime := strings.ToUpper(triggerPlan.TriggerTime)
tableName := triggerPlan.Table.(*plan.UnresolvedTable).Name()
_, characterSetClient := ctx.Get("character_set_client")
_, collationConnection := ctx.Get("collation_connection")
rows = append(rows, Row{
"def", // trigger_catalog
triggerDb.Name(), // trigger_schema
triggerPlan.TriggerName, // trigger_name
triggerEvent, // event_manipulation
"def", // event_object_catalog
triggerDb.Name(), // event_object_schema //TODO: table may be in a different db
tableName, // event_object_table
int64(order+1), // action_order
nil, // action_condition
triggerPlan.BodyString, // action_statement
"ROW", // action_orientation
triggerTime, // action_timing
nil, // action_reference_old_table
nil, // action_reference_new_table
"OLD", // action_reference_old_row
"NEW", // action_reference_new_row
time.Unix(0, 0).UTC(), // created
"", // sql_mode
"", // definer
characterSetClient, // character_set_client
collationConnection, // collation_connection
Collation_Default.String(), // database_collation //TODO: add support for databases to set collation
})
}
}
}
}
return RowsToRowIter(rows...), nil
}

func emptyRowIter(ctx *Context, c *Catalog) (RowIter, error) {
return RowsToRowIter(), nil
}

// NewInformationSchemaDatabase creates a new INFORMATION_SCHEMA Database.
Expand Down Expand Up @@ -588,7 +682,7 @@ func NewInformationSchemaDatabase(cat *Catalog) Database {
name: TriggersTableName,
schema: triggersSchema,
catalog: cat,
rowIter: emptyRowIter,
rowIter: triggersRowIter,
},
EventsTableName: &informationSchemaTable{
name: EventsTableName,
Expand Down Expand Up @@ -618,7 +712,7 @@ func NewInformationSchemaDatabase(cat *Catalog) Database {
}
}

func viewRowIter(context *Context, catalog *Catalog) RowIter {
func viewRowIter(context *Context, catalog *Catalog) (RowIter, error) {
var rows []Row
for _, db := range catalog.AllDatabases() {
database := db.Name()
Expand All @@ -637,7 +731,7 @@ func viewRowIter(context *Context, catalog *Catalog) RowIter {
})
}
}
return RowsToRowIter(rows...)
return RowsToRowIter(rows...), nil
}

// Name implements the sql.Database interface.
Expand Down Expand Up @@ -686,7 +780,7 @@ func (t *informationSchemaTable) PartitionRows(ctx *Context, partition Partition
return RowsToRowIter(), nil
}

return t.rowIter(ctx, t.catalog), nil
return t.rowIter(ctx, t.catalog)
}

// PartitionCount implements the sql.PartitionCounter interface.
Expand Down