Skip to content
Open
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
14 changes: 0 additions & 14 deletions mariadb/deparse/deparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ func deparseSelectStmtCtx(stmt *ast.SelectStmt, suppressAlias bool) string {
// - for update
// - for share
// - lock in share mode (legacy syntax)
// - for update of `t`
// - for update nowait
// - for update skip locked
func deparseForUpdate(fu *ast.ForUpdate) string {
Expand All @@ -195,19 +194,6 @@ func deparseForUpdate(fu *ast.ForUpdate) string {
b.WriteString("for update")
}

// OF table list
if len(fu.Tables) > 0 {
b.WriteString(" of ")
for i, tbl := range fu.Tables {
if i > 0 {
b.WriteString(",")
}
b.WriteString("`")
b.WriteString(tbl.Name)
b.WriteString("`")
}
}

// NOWAIT / SKIP LOCKED
if fu.NoWait {
b.WriteString(" nowait")
Expand Down
5 changes: 0 additions & 5 deletions mariadb/deparse/deparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1339,11 +1339,6 @@ func TestDeparse_Section_5_8_ForUpdate(t *testing.T) {
"SELECT a FROM t LOCK IN SHARE MODE",
"select `a` AS `a` from `t` lock in share mode",
},
{
"for_update_of_table",
"SELECT a FROM t FOR UPDATE OF t",
"select `a` AS `a` from `t` for update of `t`",
},
{
"for_update_nowait",
"SELECT a FROM t FOR UPDATE NOWAIT",
Expand Down
8 changes: 7 additions & 1 deletion mariadb/parser/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,13 @@ func (p *Parser) infixPrecedence() (int, nodes.BinaryOp, bool) {
case kwCOLLATE:
return precCollate, 0, true

// JSON column-path operators
// JSON column-path operators.
// FIDELITY NOTE (BYT-9135): MariaDB has no -> / ->> operators (MySQL-only; it
// uses JSON_EXTRACT()/JSON_UNQUOTE()), so omni over-accepts them here. The
// grammar fix is one line (drop these two cases), but it ripples into the
// inherited mysql accept-tests (compare_test.go TestParseJsonExtract /
// TestParseJsonUnquoteExtract) and the routine_body_audit "-> / ->>" entries —
// a multi-file cleanup deferred from the FOR-UPDATE-OF prune. Tracked over-accept.
case tokJsonExtract:
return precJsonAccess, nodes.BinOpJsonExtract, true
case tokJsonUnquote:
Expand Down
12 changes: 12 additions & 0 deletions mariadb/parser/oracle_corpus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ var categoryCaseSQLs = []string{
"SELECT 1 MINUS SELECT 2",
"SELECT 1 MINUS SELECT 2 MINUS SELECT 3",
"SELECT * FROM t WHERE id = 1 FOR SHARE",
"SELECT name -> '$.b' FROM t",
"SELECT name ->> '$.b' FROM t",
}

// containerFatalVerbs name statements whose *execution* would kill the shared
Expand Down Expand Up @@ -289,6 +291,16 @@ var subtractiveDivergences = []divergence{
omniSide: true, // omni mirrors MySQL's FOR SHARE; MariaDB rejects it (1064).
reason: "FOR SHARE locking clause: valid in MySQL 8.0 and omni mirrors it; MariaDB has no FOR SHARE (uses LOCK IN SHARE MODE) and rejects at parse (1064). Phase-0 mdbcheck OVER finding.",
},
{
sql: "SELECT name -> '$.b' FROM t",
omniSide: true, // omni mirrors MySQL's -> JSON operator; MariaDB rejects it (1064).
reason: "JSON -> column-path operator: MySQL-only; omni mirrors MySQL and over-accepts. MariaDB has no -> / ->> operators (uses JSON_EXTRACT()/JSON_UNQUOTE()) and rejects at parse (1064). Deferred prune: the one-line grammar fix (drop the tokJsonExtract/tokJsonUnquote arms in expr.go) ripples into inherited mysql accept-tests (TestParseJsonExtract/UnquoteExtract) and the routine_body_audit, beyond the surgical FOR-UPDATE-OF arm.",
},
{
sql: "SELECT name ->> '$.b' FROM t",
omniSide: true, // ->> shares the -> divergence.
reason: "JSON ->> column-path unquote operator: MySQL-only; same divergence as -> (omni over-accepts, MariaDB rejects 1064). Deferred with the -> prune.",
},
}

func TestMariaDBDivergenceInventory(t *testing.T) {
Expand Down
16 changes: 4 additions & 12 deletions mariadb/parser/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -1553,19 +1553,11 @@ func (p *Parser) parseForUpdateClause() (*nodes.ForUpdate, error) {
return nil, &ParseError{Message: "collecting"}
}

// OF table_list
// MariaDB removed FOR UPDATE/SHARE ... OF in 11.4 (it is MySQL-only).
if p.cur.Type == kwOF {
p.advance()
for {
ref, err := p.parseTableRef()
if err != nil {
return nil, err
}
fu.Tables = append(fu.Tables, ref)
if p.cur.Type != ',' {
break
}
p.advance()
return nil, &ParseError{
Message: "FOR UPDATE/SHARE ... OF is not supported in MariaDB",
Position: p.cur.Loc,
}
}

Expand Down
23 changes: 23 additions & 0 deletions mariadb/parser/subtractive_prune_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package parser

import "testing"

// TestForUpdateOfReject pins that omni/mariadb rejects FOR UPDATE/SHARE ... OF,
// which MariaDB removed in 11.4 (the OF object list is MySQL-only). FOR UPDATE /
// FOR SHARE without OF are unaffected (FOR SHARE itself remains a separately
// tracked over-accept — see subtractiveDivergences).
//
// The JSON -> / ->> arrow over-accept is a deferred sibling prune: the grammar
// fix is one line but it ripples into inherited mysql accept-tests and the
// routine_body_audit, so it is deferred from this surgical arm (see the fidelity
// note in expr.go).
func TestForUpdateOfReject(t *testing.T) {
reject := []string{
"SELECT * FROM t FOR UPDATE OF t",
"SELECT * FROM t FOR SHARE OF t",
"SELECT * FROM t FOR UPDATE OF t, t2",
}
for _, sql := range reject {
t.Run(sql, func(t *testing.T) { ParseExpectError(t, sql) })
}
}
20 changes: 20 additions & 0 deletions mariadb/parser/type.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package parser

import (
"strings"

nodes "github.com/bytebase/omni/mariadb/ast"
)

Expand Down Expand Up @@ -385,6 +387,24 @@ func (p *Parser) parseDataType() (*nodes.DataType, error) {
}

default:
// MariaDB-only scalar types that are non-reserved (UUID is also a function
// name, INET4/INET6 can be identifiers), so they are recognised here in type
// position only, by their identifier text, without reserving the words.
if p.cur.Type == tokIDENT {
switch {
case strings.EqualFold(p.cur.Str, "UUID"):
dt.Name = "UUID"
case strings.EqualFold(p.cur.Str, "INET4"):
dt.Name = "INET4"
case strings.EqualFold(p.cur.Str, "INET6"):
dt.Name = "INET6"
}
if dt.Name != "" {
p.advance()
dt.Loc.End = p.pos()
return dt, nil
}
}
if p.cur.Type == tokEOF {
return nil, p.syntaxErrorAtCur()
}
Expand Down
33 changes: 33 additions & 0 deletions mariadb/parser/type_inet_uuid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package parser

import "testing"

// TestUUIDInetTypeAccept covers the MariaDB-only scalar types UUID, INET4 and
// INET6 (BYT-9135). They are non-reserved — UUID is also a function, INET4/INET6
// are usable as identifiers — so they are recognised only in type position.
func TestUUIDInetTypeAccept(t *testing.T) {
accept := []string{
"CREATE TABLE t (a UUID)",
"CREATE TABLE t (a uuid)", // case-insensitive
"CREATE TABLE t (a INET4, b INET6)",
"CREATE TABLE t (id INT PRIMARY KEY, addr INET6 NOT NULL DEFAULT '::1')",
"CREATE TABLE t (u UUID DEFAULT UUID())",
"ALTER TABLE t ADD COLUMN addr INET4",
}
for _, sql := range accept {
t.Run(sql, func(t *testing.T) { ParseAndCheck(t, sql) })
}
}

// TestUUIDInetNonReserved pins that the words stay usable as function names and
// identifiers (the contextual recognition must not reserve them).
func TestUUIDInetNonReserved(t *testing.T) {
accept := []string{
"SELECT UUID()",
"SELECT uuid, inet4, inet6 FROM t",
"CREATE TABLE t (uuid INT, inet4 VARCHAR(10), inet6 TEXT)",
}
for _, sql := range accept {
t.Run(sql, func(t *testing.T) { ParseAndCheck(t, sql) })
}
}
Loading