Skip to content

Commit ebddd6b

Browse files
committed
add mariadb-binlog.bats and fix test 1
1 parent fa2db67 commit ebddd6b

File tree

7 files changed

+329
-25
lines changed

7 files changed

+329
-25
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@ require (
4141
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
4242
)
4343

44+
replace github.com/dolthub/vitess => ../vitess
45+
4446
go 1.25.0

sql/collations.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,3 +972,40 @@ type TypeWithCollation interface {
972972
// whether to include the character set and/or collation information.
973973
StringWithTableCollation(tableCollation CollationID) string
974974
}
975+
976+
// ConvertCollationID converts numeric collation IDs to their string names.
977+
func ConvertCollationID(val any) (any, error) {
978+
if _, ok := val.(string); ok {
979+
return val, nil
980+
}
981+
982+
var collationID uint64
983+
switch v := val.(type) {
984+
case int8:
985+
collationID = uint64(v)
986+
case int16:
987+
collationID = uint64(v)
988+
case int:
989+
collationID = uint64(v)
990+
case int32:
991+
collationID = uint64(v)
992+
case int64:
993+
collationID = uint64(v)
994+
case uint8:
995+
collationID = uint64(v)
996+
case uint16:
997+
collationID = uint64(v)
998+
case uint:
999+
collationID = uint64(v)
1000+
case uint32:
1001+
collationID = uint64(v)
1002+
case uint64:
1003+
collationID = v
1004+
default:
1005+
return val, nil
1006+
}
1007+
1008+
// Convert numeric ID to collation name
1009+
collation := CollationID(collationID).Collation()
1010+
return collation.Name, nil
1011+
}

sql/plan/binlog.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2025 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package plan
16+
17+
import (
18+
"github.com/dolthub/go-mysql-server/sql"
19+
)
20+
21+
// Binlog represents the BINLOG statement, an internal-use statement generated by mysqlbinlog to execute binary log events (format description events and row events).
22+
// https://dev.mysql.com/doc/refman/8.4/en/binlog.html
23+
type Binlog struct {
24+
Base64Str string
25+
}
26+
27+
var _ sql.Node = (*Binlog)(nil)
28+
29+
// NewBinlog creates a new Binlog node.
30+
func NewBinlog(base64Str string) *Binlog {
31+
return &Binlog{
32+
Base64Str: base64Str,
33+
}
34+
}
35+
36+
// RowIter implements the sql.Node interface.
37+
func (b *Binlog) RowIter(ctx *sql.Context, _ sql.Row) (sql.RowIter, error) {
38+
// TODO: Decode base64 data and execute binary log events
39+
// For now, this is a no-op
40+
return sql.RowsToRowIter(), nil
41+
}
42+
43+
func (b *Binlog) String() string {
44+
return "BINLOG"
45+
}
46+
47+
func (b *Binlog) Resolved() bool {
48+
return true
49+
}
50+
51+
func (b *Binlog) Schema() sql.Schema {
52+
return nil
53+
}
54+
55+
func (b *Binlog) Children() []sql.Node {
56+
return nil
57+
}
58+
59+
func (b *Binlog) IsReadOnly() bool {
60+
return false
61+
}
62+
63+
// WithChildren implements the Node interface.
64+
func (b *Binlog) WithChildren(children ...sql.Node) (sql.Node, error) {
65+
if len(children) != 0 {
66+
return nil, sql.ErrInvalidChildrenNumber.New(b, len(children), 0)
67+
}
68+
return b, nil
69+
}

sql/planbuilder/builder.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ func (b *Builder) buildSubquery(inScope *scope, stmt ast.Statement, subQuery str
401401
return b.buildDeallocate(inScope, n)
402402
case ast.InjectedStatement:
403403
return b.buildInjectedStatement(inScope, n)
404+
case *ast.Binlog:
405+
outScope = inScope.push()
406+
outScope.node = plan.NewBinlog(n.Base64Str)
404407
}
405408
return
406409
}

sql/rowexec/rel_iters.go

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package rowexec
1616

1717
import (
1818
"errors"
19+
"fmt"
1920
"io"
2021
"strings"
2122

@@ -392,18 +393,22 @@ func setSystemVar(ctx *sql.Context, sysVar *expression.SystemVar, right sql.Expr
392393
if err != nil {
393394
return err
394395
}
396+
395397
err = validateSystemVariableValue(sysVar.Name, val)
396398
if err != nil {
397399
return err
398400
}
399-
err = sysVar.Scope.SetValue(ctx, sysVar.Name, val)
400-
if err != nil {
401-
return err
402-
}
403401

404402
// Setting `character_set_connection` and `collation_connection` will set the corresponding variable
405403
// Setting `character_set_server` and `collation_server` will set the corresponding variable
404+
// Handle MariaDB binlog numeric conversions for sql_mode and collation IDs
406405
switch strings.ToLower(sysVar.Name) {
406+
case "sql_mode":
407+
val, err = sql.ConvertSqlModeBitmask(val)
408+
if err != nil {
409+
return err
410+
}
411+
return sysVar.Scope.SetValue(ctx, sysVar.Name, val)
407412
case "character_set_connection":
408413
if val == nil {
409414
return sysVar.Scope.SetValue(ctx, "collation_connection", val)
@@ -420,8 +425,17 @@ func setSystemVar(ctx *sql.Context, sysVar *expression.SystemVar, right sql.Expr
420425
collationName := charset.DefaultCollation().Name()
421426
return sysVar.Scope.SetValue(ctx, "collation_connection", collationName)
422427
case "collation_connection":
428+
// Convert numeric collation ID to name (from MariaDB binlogs)
429+
val, err = sql.ConvertCollationID(val)
430+
if err != nil {
431+
return err
432+
}
433+
err = sysVar.Scope.SetValue(ctx, sysVar.Name, val)
434+
if err != nil {
435+
return err
436+
}
423437
if val == nil {
424-
return sysVar.Scope.SetValue(ctx, "character_set_connection", val)
438+
return sysVar.Scope.SetValue(ctx, "character_set_connection", nil)
425439
}
426440
valStr, ok := val.(string)
427441
if !ok {
@@ -450,8 +464,17 @@ func setSystemVar(ctx *sql.Context, sysVar *expression.SystemVar, right sql.Expr
450464
collationName := charset.DefaultCollation().Name()
451465
return sysVar.Scope.SetValue(ctx, "collation_server", collationName)
452466
case "collation_server":
467+
// Convert numeric collation ID to name (from MariaDB binlogs)
468+
val, err = sql.ConvertCollationID(val)
469+
if err != nil {
470+
return err
471+
}
472+
err = sysVar.Scope.SetValue(ctx, sysVar.Name, val)
473+
if err != nil {
474+
return err
475+
}
453476
if val == nil {
454-
return sysVar.Scope.SetValue(ctx, "character_set_server", val)
477+
return sysVar.Scope.SetValue(ctx, "character_set_server", nil)
455478
}
456479
valStr, ok := val.(string)
457480
if !ok {
@@ -464,8 +487,23 @@ func setSystemVar(ctx *sql.Context, sysVar *expression.SystemVar, right sql.Expr
464487
}
465488
charsetName := collation.CharacterSet().Name()
466489
return sysVar.Scope.SetValue(ctx, "character_set_server", charsetName)
490+
case "collation_database":
491+
// Convert numeric collation ID to name (from MariaDB binlogs)
492+
val, err = sql.ConvertCollationID(val)
493+
if err != nil {
494+
return err
495+
}
496+
return sysVar.Scope.SetValue(ctx, sysVar.Name, val)
497+
case "lc_time_names":
498+
// TODO: convert numeric locale ID to locale name
499+
switch val.(type) {
500+
case int8, int16, int, int32, int64, uint8, uint16, uint, uint32, uint64:
501+
val = fmt.Sprintf("%v", val)
502+
}
503+
return sysVar.Scope.SetValue(ctx, sysVar.Name, val)
504+
default:
505+
return sysVar.Scope.SetValue(ctx, sysVar.Name, val)
467506
}
468-
return nil
469507
}
470508

471509
func validateSystemVariableValue(sysVarName string, val interface{}) error {

sql/sql_mode.go

Lines changed: 117 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,33 +24,86 @@ import (
2424
const (
2525
SqlModeSessionVar = "SQL_MODE"
2626

27+
RealAsFloat = "REAL_AS_FLOAT"
28+
PipesAsConcat = "PIPES_AS_CONCAT"
29+
ANSIQuotes = "ANSI_QUOTES"
30+
IgnoreSpace = "IGNORE_SPACE"
31+
OnlyFullGroupBy = "ONLY_FULL_GROUP_BY"
32+
NoUnsignedSubtraction = "NO_UNSIGNED_SUBTRACTION"
33+
NoDirInCreate = "NO_DIR_IN_CREATE"
34+
// ANSI mode includes REAL_AS_FLOAT, PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, and ONLY_FULL_GROUP_BY
35+
ANSI = "ANSI"
36+
NoAutoValueOnZero = "NO_AUTO_VALUE_ON_ZERO"
37+
NoBackslashEscapes = "NO_BACKSLASH_ESCAPES"
38+
StrictTransTables = "STRICT_TRANS_TABLES"
39+
StrictAllTables = "STRICT_ALL_TABLES"
40+
NoZeroInDate = "NO_ZERO_IN_DATE"
2741
AllowInvalidDates = "ALLOW_INVALID_DATES"
28-
ANSIQuotes = "ANSI_QUOTES"
2942
ErrorForDivisionByZero = "ERROR_FOR_DIVISION_BY_ZERO"
43+
// Traditional mode includes STRICT_TRANS_TABLES, STRICT_ALL_TABLES, NO_ZERO_IN_DATE, ERROR_FOR_DIVISION_BY_ZERO,
44+
// and NO_ENGINE_SUBSTITUTION
45+
Traditional = "TRADITIONAL"
3046
HighNotPrecedence = "HIGH_NOT_PRECEDENCE"
31-
IgnoreSpaces = "IGNORE_SPACE"
32-
NoAutoValueOnZero = "NO_AUTO_VALUE_ON_ZERO"
33-
NoBackslashEscapes = "NO_BACKSLASH_ESCAPES"
34-
NoDirInCreate = "NO_DIR_IN_CREATE"
3547
NoEngineSubstitution = "NO_ENGINE_SUBSTITUTION"
36-
NoUnsignedSubtraction = "NO_UNSIGNED_SUBTRACTION"
37-
NoZeroInDate = "NO_ZERO_IN_DATE"
38-
OnlyFullGroupBy = "ONLY_FULL_GROUP_BY"
3948
PadCharToFullLength = "PAD_CHAR_TO_FULL_LENGTH"
40-
PipesAsConcat = "PIPES_AS_CONCAT"
41-
RealAsFloat = "REAL_AS_FLOAT"
42-
StrictTransTables = "STRICT_TRANS_TABLES"
43-
StrictAllTables = "STRICT_ALL_TABLES"
4449
TimeTruncateFractional = "TIME_TRUNCATE_FRACTIONAL"
50+
)
4551

46-
// ANSI mode includes REAL_AS_FLOAT, PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, and ONLY_FULL_GROUP_BY
47-
ANSI = "ANSI"
48-
// Traditional mode includes STRICT_TRANS_TABLES, STRICT_ALL_TABLES, NO_ZERO_IN_DATE, ERROR_FOR_DIVISION_BY_ZERO,
49-
// and NO_ENGINE_SUBSTITUTION
50-
Traditional = "TRADITIONAL"
51-
DefaultSqlMode = "NO_ENGINE_SUBSTITUTION,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES"
52+
// Bits for different SQL modes (from mysql-server/sql/system_variables.h)
53+
const (
54+
modeRealAsFloat = 1
55+
modePipesAsConcat = 2
56+
modeAnsiQuotes = 4
57+
modeIgnoreSpace = 8
58+
modeOnlyFullGroupBy = 32
59+
modeNoUnsignedSubtraction = 64
60+
modeNoDirInCreate = 128
61+
modeAnsi = 0x40000
62+
modeNoAutoValueOnZero = modeAnsi * 2
63+
modeNoBackslashEscapes = modeNoAutoValueOnZero * 2
64+
modeStrictTransTables = modeNoBackslashEscapes * 2
65+
modeStrictAllTables = modeStrictTransTables * 2
66+
modeNoZeroInDate = modeStrictAllTables * 2
67+
modeNoZeroDate = modeNoZeroInDate * 2
68+
modeAllowInvalidDates = modeNoZeroDate * 2
69+
modeErrorForDivisionByZero = modeAllowInvalidDates * 2
70+
modeTraditional = modeErrorForDivisionByZero * 2
71+
modeHighNotPrecedence = 1 << 29
72+
modeNoEngineSubstitution = modeHighNotPrecedence * 2
73+
modePadCharToFullLength = 1 << 31
74+
modeTimeTruncateFractional = 1 << 32
5275
)
5376

77+
// sqlModeBitMap maps SQL mode bit flags to their string names.
78+
var sqlModeBitMap = map[uint64]string{
79+
modeRealAsFloat: RealAsFloat,
80+
modePipesAsConcat: PipesAsConcat,
81+
modeAnsiQuotes: ANSIQuotes,
82+
modeIgnoreSpace: IgnoreSpace,
83+
modeOnlyFullGroupBy: OnlyFullGroupBy,
84+
modeNoUnsignedSubtraction: NoUnsignedSubtraction,
85+
modeNoDirInCreate: NoDirInCreate,
86+
modeAnsi: ANSI,
87+
modeNoAutoValueOnZero: NoAutoValueOnZero,
88+
modeNoBackslashEscapes: NoBackslashEscapes,
89+
modeStrictTransTables: StrictTransTables,
90+
modeStrictAllTables: StrictAllTables,
91+
modeNoZeroInDate: NoZeroInDate,
92+
modeAllowInvalidDates: AllowInvalidDates,
93+
modeErrorForDivisionByZero: ErrorForDivisionByZero,
94+
modeTraditional: Traditional,
95+
modeHighNotPrecedence: HighNotPrecedence,
96+
modeNoEngineSubstitution: NoEngineSubstitution,
97+
modePadCharToFullLength: PadCharToFullLength,
98+
modeTimeTruncateFractional: TimeTruncateFractional,
99+
}
100+
101+
var DefaultSqlMode = strings.Join([]string{
102+
NoEngineSubstitution,
103+
OnlyFullGroupBy,
104+
StrictTransTables,
105+
}, ",")
106+
54107
var defaultMode *SqlMode
55108

56109
func init() {
@@ -170,3 +223,49 @@ func (s *SqlMode) ParserOptions() sqlparser.ParserOptions {
170223
func (s *SqlMode) String() string {
171224
return s.modeString
172225
}
226+
227+
// ConvertSqlModeBitmask converts sql_mode values to their string representation.
228+
func ConvertSqlModeBitmask(val any) (any, error) {
229+
if _, ok := val.(string); ok {
230+
return val, nil
231+
}
232+
233+
var bitmask uint64
234+
switch v := val.(type) {
235+
case int8:
236+
bitmask = uint64(v)
237+
case int16:
238+
bitmask = uint64(v)
239+
case int:
240+
bitmask = uint64(v)
241+
case int32:
242+
bitmask = uint64(v)
243+
case int64:
244+
bitmask = uint64(v)
245+
case uint8:
246+
bitmask = uint64(v)
247+
case uint16:
248+
bitmask = uint64(v)
249+
case uint:
250+
bitmask = uint64(v)
251+
case uint32:
252+
bitmask = uint64(v)
253+
case uint64:
254+
bitmask = v
255+
default:
256+
return val, nil
257+
}
258+
259+
var modes []string
260+
for bit, modeName := range sqlModeBitMap {
261+
if bitmask&bit != 0 {
262+
modes = append(modes, modeName)
263+
}
264+
}
265+
266+
if len(modes) == 0 {
267+
return "", nil
268+
}
269+
270+
return strings.Join(modes, ","), nil
271+
}

0 commit comments

Comments
 (0)