[MySQL] Optimizer hints
This commit is contained in:
parent
c9967d151e
commit
f772f90336
12 changed files with 207 additions and 27 deletions
|
|
@ -16,11 +16,35 @@ type ClauseWithProjections interface {
|
|||
Projections() ProjectionList
|
||||
}
|
||||
|
||||
// OptimizerHint provides a way to optimize query execution per-statement basis
|
||||
type OptimizerHint string
|
||||
|
||||
type optimizerHints []OptimizerHint
|
||||
|
||||
func (o optimizerHints) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(o) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("/*+")
|
||||
for i, hint := range o {
|
||||
if i > 0 {
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
|
||||
out.WriteString(string(hint))
|
||||
}
|
||||
out.WriteString("*/")
|
||||
}
|
||||
|
||||
// ClauseSelect struct
|
||||
type ClauseSelect struct {
|
||||
Distinct bool
|
||||
DistinctOnColumns []ColumnExpression
|
||||
ProjectionList []Projection
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
||||
// Projections returns list of projections for select clause
|
||||
|
|
@ -32,6 +56,7 @@ func (s *ClauseSelect) Projections() ProjectionList {
|
|||
func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.NewLine()
|
||||
out.WriteString("SELECT")
|
||||
s.OptimizerHints.Serialize(statementType, out, options...)
|
||||
|
||||
if s.Distinct {
|
||||
out.WriteString("DISTINCT")
|
||||
|
|
@ -286,12 +311,16 @@ func (s *ClauseSetStmtOperator) Serialize(statementType StatementType, out *SQLB
|
|||
// ClauseUpdate struct
|
||||
type ClauseUpdate struct {
|
||||
Table SerializerTable
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (u *ClauseUpdate) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.NewLine()
|
||||
out.WriteString("UPDATE")
|
||||
u.OptimizerHints.Serialize(statementType, out, options...)
|
||||
|
||||
if utils.IsNil(u.Table) {
|
||||
panic("jet: table to update is nil")
|
||||
|
|
@ -342,6 +371,9 @@ func (s *SetClause) Serialize(statementType StatementType, out *SQLBuilder, opti
|
|||
type ClauseInsert struct {
|
||||
Table SerializerTable
|
||||
Columns []Column
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
||||
// GetColumns gets list of columns for insert
|
||||
|
|
@ -355,13 +387,15 @@ func (i *ClauseInsert) GetColumns() []Column {
|
|||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (i *ClauseInsert) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.NewLine()
|
||||
out.WriteString("INSERT INTO")
|
||||
|
||||
if utils.IsNil(i.Table) {
|
||||
panic("jet: table is nil for INSERT clause")
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("INSERT")
|
||||
i.OptimizerHints.Serialize(statementType, out, options...)
|
||||
out.WriteString("INTO")
|
||||
|
||||
i.Table.serialize(statementType, out)
|
||||
|
||||
if len(i.Columns) > 0 {
|
||||
|
|
@ -449,17 +483,17 @@ func (v *ClauseQuery) Serialize(statementType StatementType, out *SQLBuilder, op
|
|||
// ClauseDelete struct
|
||||
type ClauseDelete struct {
|
||||
Table SerializerTable
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (d *ClauseDelete) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.NewLine()
|
||||
out.WriteString("DELETE FROM")
|
||||
|
||||
if d.Table == nil {
|
||||
panic("jet: nil table in DELETE clause")
|
||||
}
|
||||
|
||||
out.WriteString("DELETE")
|
||||
d.OptimizerHints.Serialize(statementType, out, options...)
|
||||
out.WriteString("FROM")
|
||||
d.Table.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
|||
type DeleteStatement interface {
|
||||
Statement
|
||||
|
||||
OPTIMIZER_HINTS(hints ...OptimizerHint) DeleteStatement
|
||||
|
||||
USING(tables ...ReadableTable) DeleteStatement
|
||||
WHERE(expression BoolExpression) DeleteStatement
|
||||
ORDER_BY(orderByClauses ...OrderByClause) DeleteStatement
|
||||
|
|
@ -15,7 +17,7 @@ type DeleteStatement interface {
|
|||
type deleteStatementImpl struct {
|
||||
jet.SerializerStatement
|
||||
|
||||
Delete jet.ClauseStatementBegin
|
||||
Delete jet.ClauseDelete
|
||||
Using jet.ClauseFrom
|
||||
Where jet.ClauseWhere
|
||||
OrderBy jet.ClauseOrderBy
|
||||
|
|
@ -29,17 +31,22 @@ func newDeleteStatement(table Table) DeleteStatement {
|
|||
&newDelete.Using,
|
||||
&newDelete.Where,
|
||||
&newDelete.OrderBy,
|
||||
&newDelete.Limit)
|
||||
&newDelete.Limit,
|
||||
)
|
||||
|
||||
newDelete.Delete.Name = "DELETE FROM"
|
||||
newDelete.Delete.Table = table
|
||||
newDelete.Using.Name = "USING"
|
||||
newDelete.Delete.Tables = append(newDelete.Delete.Tables, table)
|
||||
newDelete.Where.Mandatory = true
|
||||
newDelete.Limit.Count = -1
|
||||
|
||||
return newDelete
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) DeleteStatement {
|
||||
d.Delete.OptimizerHints = hints
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *deleteStatementImpl) USING(tables ...ReadableTable) DeleteStatement {
|
||||
d.Using.Tables = readableTablesToSerializerList(tables)
|
||||
return d
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
|||
type InsertStatement interface {
|
||||
Statement
|
||||
|
||||
OPTIMIZER_HINTS(hints ...OptimizerHint) InsertStatement
|
||||
|
||||
// Insert row of values
|
||||
VALUES(value interface{}, values ...interface{}) InsertStatement
|
||||
// Insert row of values, where value for each column is extracted from filed of structure data.
|
||||
|
|
@ -22,7 +24,10 @@ type InsertStatement interface {
|
|||
func newInsertStatement(table Table, columns []jet.Column) InsertStatement {
|
||||
newInsert := &insertStatementImpl{}
|
||||
newInsert.SerializerStatement = jet.NewStatementImpl(Dialect, jet.InsertStatementType, newInsert,
|
||||
&newInsert.Insert, &newInsert.ValuesQuery, &newInsert.OnDuplicateKey)
|
||||
&newInsert.Insert,
|
||||
&newInsert.ValuesQuery,
|
||||
&newInsert.OnDuplicateKey,
|
||||
)
|
||||
|
||||
newInsert.Insert.Table = table
|
||||
newInsert.Insert.Columns = columns
|
||||
|
|
@ -38,6 +43,11 @@ type insertStatementImpl struct {
|
|||
OnDuplicateKey onDuplicateKeyUpdateClause
|
||||
}
|
||||
|
||||
func (is *insertStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) InsertStatement {
|
||||
is.Insert.OptimizerHints = hints
|
||||
return is
|
||||
}
|
||||
|
||||
func (is *insertStatementImpl) VALUES(value interface{}, values ...interface{}) InsertStatement {
|
||||
is.ValuesQuery.Rows = append(is.ValuesQuery.Rows, jet.UnwindRowFromValues(value, values))
|
||||
return is
|
||||
|
|
|
|||
19
mysql/optimizer_hints.go
Normal file
19
mysql/optimizer_hints.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/internal/jet"
|
||||
)
|
||||
|
||||
// OptimizerHint provides a way to optimize query execution per-statement basis
|
||||
type OptimizerHint = jet.OptimizerHint
|
||||
|
||||
// MAX_EXECUTION_TIME limits statement execution time
|
||||
func MAX_EXECUTION_TIME(miliseconds int) OptimizerHint {
|
||||
return OptimizerHint(fmt.Sprintf("MAX_EXECUTION_TIME(%d)", miliseconds))
|
||||
}
|
||||
|
||||
// QB_NAME assigns name to query block
|
||||
func QB_NAME(name string) OptimizerHint {
|
||||
return OptimizerHint(fmt.Sprintf("QB_NAME(%s)", name))
|
||||
}
|
||||
|
|
@ -40,6 +40,8 @@ type SelectStatement interface {
|
|||
jet.HasProjections
|
||||
Expression
|
||||
|
||||
OPTIMIZER_HINTS(hints ...OptimizerHint) SelectStatement
|
||||
|
||||
DISTINCT() SelectStatement
|
||||
FROM(tables ...ReadableTable) SelectStatement
|
||||
WHERE(expression BoolExpression) SelectStatement
|
||||
|
|
@ -65,9 +67,19 @@ func SELECT(projection Projection, projections ...Projection) SelectStatement {
|
|||
|
||||
func newSelectStatement(table ReadableTable, projections []Projection) SelectStatement {
|
||||
newSelect := &selectStatementImpl{}
|
||||
newSelect.ExpressionStatement = jet.NewExpressionStatementImpl(Dialect, jet.SelectStatementType, newSelect, &newSelect.Select,
|
||||
&newSelect.From, &newSelect.Where, &newSelect.GroupBy, &newSelect.Having, &newSelect.Window, &newSelect.OrderBy,
|
||||
&newSelect.Limit, &newSelect.Offset, &newSelect.For, &newSelect.ShareLock)
|
||||
newSelect.ExpressionStatement = jet.NewExpressionStatementImpl(Dialect, jet.SelectStatementType, newSelect,
|
||||
&newSelect.Select,
|
||||
&newSelect.From,
|
||||
&newSelect.Where,
|
||||
&newSelect.GroupBy,
|
||||
&newSelect.Having,
|
||||
&newSelect.Window,
|
||||
&newSelect.OrderBy,
|
||||
&newSelect.Limit,
|
||||
&newSelect.Offset,
|
||||
&newSelect.For,
|
||||
&newSelect.ShareLock,
|
||||
)
|
||||
|
||||
newSelect.Select.ProjectionList = projections
|
||||
if table != nil {
|
||||
|
|
@ -100,6 +112,11 @@ type selectStatementImpl struct {
|
|||
ShareLock jet.ClauseOptional
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) SelectStatement {
|
||||
s.Select.OptimizerHints = hints
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *selectStatementImpl) DISTINCT() SelectStatement {
|
||||
s.Select.Distinct = true
|
||||
return s
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
|||
type UpdateStatement interface {
|
||||
jet.Statement
|
||||
|
||||
OPTIMIZER_HINTS(hints ...OptimizerHint) UpdateStatement
|
||||
|
||||
SET(value interface{}, values ...interface{}) UpdateStatement
|
||||
MODEL(data interface{}) UpdateStatement
|
||||
|
||||
|
|
@ -36,6 +38,11 @@ func newUpdateStatement(table Table, columns []jet.Column) UpdateStatement {
|
|||
return update
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) OPTIMIZER_HINTS(hints ...OptimizerHint) UpdateStatement {
|
||||
u.Update.OptimizerHints = hints
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *updateStatementImpl) SET(value interface{}, values ...interface{}) UpdateStatement {
|
||||
columnAssigment, isColumnAssigment := value.(ColumnAssigment)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ type DeleteStatement interface {
|
|||
type deleteStatementImpl struct {
|
||||
jet.SerializerStatement
|
||||
|
||||
Delete jet.ClauseStatementBegin
|
||||
Delete jet.ClauseDelete
|
||||
Using jet.ClauseFrom
|
||||
Where jet.ClauseWhere
|
||||
Returning jet.ClauseReturning
|
||||
|
|
@ -28,8 +28,7 @@ func newDeleteStatement(table WritableTable) DeleteStatement {
|
|||
&newDelete.Where,
|
||||
&newDelete.Returning)
|
||||
|
||||
newDelete.Delete.Name = "DELETE FROM"
|
||||
newDelete.Delete.Tables = append(newDelete.Delete.Tables, table)
|
||||
newDelete.Delete.Table = table
|
||||
newDelete.Using.Name = "USING"
|
||||
newDelete.Where.Mandatory = true
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ type DeleteStatement interface {
|
|||
type deleteStatementImpl struct {
|
||||
jet.SerializerStatement
|
||||
|
||||
Delete jet.ClauseStatementBegin
|
||||
Delete jet.ClauseDelete
|
||||
Where jet.ClauseWhere
|
||||
OrderBy jet.ClauseOrderBy
|
||||
Limit jet.ClauseLimit
|
||||
|
|
@ -32,8 +32,7 @@ func newDeleteStatement(table Table) DeleteStatement {
|
|||
&newDelete.Returning,
|
||||
)
|
||||
|
||||
newDelete.Delete.Name = "DELETE FROM"
|
||||
newDelete.Delete.Tables = append(newDelete.Delete.Tables, table)
|
||||
newDelete.Delete.Table = table
|
||||
newDelete.Where.Mandatory = true
|
||||
newDelete.Limit.Count = -1
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package mysql
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/go-jet/jet/v2/internal/testutils"
|
||||
. "github.com/go-jet/jet/v2/mysql"
|
||||
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table"
|
||||
|
|
@ -98,3 +99,22 @@ WHERE (staff.staff_id != ?) AND (rental.rental_id < ?);
|
|||
|
||||
testutils.AssertExecAndRollback(t, stmt, db)
|
||||
}
|
||||
|
||||
func TestDeleteOptimizerHints(t *testing.T) {
|
||||
|
||||
stmt := Link.DELETE().
|
||||
OPTIMIZER_HINTS(QB_NAME("deleteIns"), "MRR(link)").
|
||||
WHERE(
|
||||
Link.Name.IN(String("Gmail"), String("Outlook")),
|
||||
)
|
||||
|
||||
testutils.AssertDebugStatementSql(t, stmt, `
|
||||
DELETE /*+ QB_NAME(deleteIns) MRR(link) */ FROM test_sample.link
|
||||
WHERE link.name IN ('Gmail', 'Outlook');
|
||||
`)
|
||||
|
||||
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||
_, err := stmt.Exec(tx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -370,3 +370,23 @@ func TestInsertWithExecContext(t *testing.T) {
|
|||
|
||||
require.Error(t, err, "context deadline exceeded")
|
||||
}
|
||||
|
||||
func TestInsertOptimizerHints(t *testing.T) {
|
||||
|
||||
stmt := Link.INSERT(Link.MutableColumns).
|
||||
OPTIMIZER_HINTS(QB_NAME("qbIns"), "NO_ICP(link)").
|
||||
MODEL(model.Link{
|
||||
URL: "http://www.google.com",
|
||||
Name: "Google",
|
||||
})
|
||||
|
||||
testutils.AssertDebugStatementSql(t, stmt, `
|
||||
INSERT /*+ QB_NAME(qbIns) NO_ICP(link) */ INTO test_sample.link (url, name, description)
|
||||
VALUES ('http://www.google.com', 'Google', NULL);
|
||||
`)
|
||||
|
||||
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||
_, err := stmt.Exec(tx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1189,3 +1189,25 @@ ORDER BY film.film_id;
|
|||
]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSelectOptimizerHints(t *testing.T) {
|
||||
|
||||
stmt := SELECT(Actor.AllColumns).
|
||||
OPTIMIZER_HINTS(MAX_EXECUTION_TIME(1), QB_NAME("mainQueryBlock"), "NO_ICP(actor)").
|
||||
DISTINCT().
|
||||
FROM(Actor)
|
||||
|
||||
testutils.AssertDebugStatementSql(t, stmt, `
|
||||
SELECT /*+ MAX_EXECUTION_TIME(1) QB_NAME(mainQueryBlock) NO_ICP(actor) */ DISTINCT actor.actor_id AS "actor.actor_id",
|
||||
actor.first_name AS "actor.first_name",
|
||||
actor.last_name AS "actor.last_name",
|
||||
actor.last_update AS "actor.last_update"
|
||||
FROM dvds.actor;
|
||||
`)
|
||||
|
||||
var actors []model.Actor
|
||||
|
||||
err := stmt.QueryContext(context.Background(), db, &actors)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, actors, 200)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"database/sql"
|
||||
"github.com/go-jet/jet/v2/internal/testutils"
|
||||
. "github.com/go-jet/jet/v2/mysql"
|
||||
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table"
|
||||
. "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table"
|
||||
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model"
|
||||
. "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/table"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -260,10 +260,10 @@ func TestUpdateExecContext(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateWithJoin(t *testing.T) {
|
||||
statement := table.Staff.INNER_JOIN(table.Address, table.Address.AddressID.EQ(table.Staff.AddressID)).
|
||||
UPDATE(table.Staff.LastName).
|
||||
statement := Staff.INNER_JOIN(Address, Address.AddressID.EQ(Staff.AddressID)).
|
||||
UPDATE(Staff.LastName).
|
||||
SET(String("New staff name")).
|
||||
WHERE(table.Staff.StaffID.EQ(Int(1)))
|
||||
WHERE(Staff.StaffID.EQ(Int(1)))
|
||||
|
||||
testutils.AssertStatementSql(t, statement, `
|
||||
UPDATE dvds.staff
|
||||
|
|
@ -274,3 +274,29 @@ WHERE staff.staff_id = ?;
|
|||
|
||||
testutils.AssertExecAndRollback(t, statement, db)
|
||||
}
|
||||
|
||||
func TestUpdateOptimizerHints(t *testing.T) {
|
||||
|
||||
stmt := Link.UPDATE(Link.AllColumns).
|
||||
OPTIMIZER_HINTS(QB_NAME("qbInsert"), "MRR(link)").
|
||||
MODEL(model.Link{
|
||||
ID: 501,
|
||||
URL: "http://www.duckduckgo.com",
|
||||
Name: "DuckDuckGo",
|
||||
}).
|
||||
WHERE(Link.Name.EQ(String("Bing")))
|
||||
|
||||
testutils.AssertDebugStatementSql(t, stmt, `
|
||||
UPDATE /*+ QB_NAME(qbInsert) MRR(link) */ test_sample.link
|
||||
SET id = 501,
|
||||
url = 'http://www.duckduckgo.com',
|
||||
name = 'DuckDuckGo',
|
||||
description = NULL
|
||||
WHERE link.name = 'Bing';
|
||||
`)
|
||||
|
||||
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||
_, err := stmt.Exec(tx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue