Skip to content

Commit 68fe62b

Browse files
authored
Merge pull request #35 from akito0107/feat-mysql-tableoption
Feat mysql tableoption
2 parents a6dac9b + 20d6f3d commit 68fe62b

8 files changed

Lines changed: 376 additions & 37 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- from https://github.com/isucon/isucon8-qualify/blob/master/db/schema.sql
2+
3+
CREATE TABLE IF NOT EXISTS users (
4+
id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
5+
nickname VARCHAR(128) NOT NULL,
6+
login_name VARCHAR(128) NOT NULL,
7+
pass_hash VARCHAR(128) NOT NULL
8+
) ENGINE=InnoDB DEFAULT CHARSET utf8mb4;

parser.go

Lines changed: 158 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -174,18 +174,23 @@ func (p *Parser) ParseDataType() (sqlast.Type, error) {
174174
if err != nil {
175175
return nil, errors.Errorf("parsePrecision failed: %w", err)
176176
}
177-
return &sqlast.Float{Size: size, From: tok.From, To: tok.To, RParen: r}, nil
177+
unsigned, pos := p.parseMyUnsigned()
178+
return &sqlast.Float{Size: size, From: tok.From, To: tok.To, RParen: r, IsUnsigned: unsigned, Unsigned: pos}, nil
178179
case "REAL":
179-
return &sqlast.Real{From: tok.From, To: tok.To}, nil
180+
unsigned, pos := p.parseMyUnsigned()
181+
return &sqlast.Real{From: tok.From, To: tok.To, IsUnsigned: unsigned, Unsigned: pos}, nil
180182
case "DOUBLE":
181183
p := p.expectKeyword("PRECISION")
182184
return &sqlast.Double{From: tok.From, To: p.To}, nil
183185
case "SMALLINT":
184-
return &sqlast.SmallInt{From: tok.From, To: tok.To}, nil
186+
unsigned, pos := p.parseMyUnsigned()
187+
return &sqlast.SmallInt{From: tok.From, To: tok.To, IsUnsigned: unsigned, Unsigned: pos}, nil
185188
case "INTEGER", "INT":
186-
return &sqlast.Int{From: tok.From, To: tok.To}, nil
189+
unsigned, pos := p.parseMyUnsigned()
190+
return &sqlast.Int{From: tok.From, To: tok.To, IsUnsigned: unsigned, Unsigned: pos}, nil
187191
case "BIGINT":
188-
return &sqlast.BigInt{From: tok.From, To: tok.To}, nil
192+
unsigned, u, _ := p.parseKeyword("UNSIGNED")
193+
return &sqlast.BigInt{From: tok.From, To: tok.To, IsUnsigned: unsigned, Unsigned: u.To}, nil
189194
case "VARCHAR":
190195
p, r, err := p.parseOptionalPrecision()
191196
if err != nil {
@@ -252,11 +257,15 @@ func (p *Parser) ParseDataType() (sqlast.Type, error) {
252257
if r.Kind != sqltoken.RParen {
253258
return nil, errors.Errorf("expected RParen but %s", r)
254259
}
260+
261+
unsigned, pos := p.parseMyUnsigned()
255262
return &sqlast.Decimal{
256-
Precision: precision,
257-
Scale: scale,
258-
Numeric: tok.From,
259-
RParen: r.To,
263+
Precision: precision,
264+
Scale: scale,
265+
Numeric: tok.From,
266+
RParen: r.To,
267+
IsUnsigned: unsigned,
268+
Unsigned: pos,
260269
}, nil
261270

262271
default:
@@ -519,6 +528,7 @@ func (p *Parser) parseCreate() (sqlast.Stmt, error) {
519528
}
520529

521530
func (p *Parser) parseCreateTable(create *sqltoken.Token) (sqlast.Stmt, error) {
531+
notExists, _, _ := p.parseKeywords("IF", "NOT", "EXISTS")
522532
name, err := p.parseObjectName()
523533
if err != nil {
524534
return nil, errors.Errorf("parseObjectName failed: %w", err)
@@ -529,10 +539,17 @@ func (p *Parser) parseCreateTable(create *sqltoken.Token) (sqlast.Stmt, error) {
529539
return nil, errors.Errorf("parseElements failed: %w", err)
530540
}
531541

542+
options, err := p.parseTableOptions()
543+
if err != nil {
544+
return nil, errors.Errorf("parseTableOptions failed: %w", err)
545+
}
546+
532547
return &sqlast.CreateTableStmt{
533-
Create: create.From,
534-
Name: name,
535-
Elements: elements,
548+
NotExists: notExists,
549+
Create: create.From,
550+
Name: name,
551+
Elements: elements,
552+
Options: options,
536553
}, nil
537554
}
538555

@@ -664,7 +681,7 @@ func (p *Parser) parseColumnDef() (*sqlast.ColumnDef, error) {
664681
return nil, errors.Errorf("ParseDataType failed: %w", err)
665682
}
666683

667-
def, specs, err := p.parseColumnDefinition()
684+
def, specs, decorates, err := p.parseColumnDefinition()
668685
if err != nil {
669686
return nil, errors.Errorf("parseColumnDefinition: %w", err)
670687
}
@@ -676,8 +693,9 @@ func (p *Parser) parseColumnDef() (*sqlast.ColumnDef, error) {
676693
To: tok.To,
677694
Value: columnName.String(),
678695
},
679-
DataType: dataType,
680-
Default: def,
696+
MyDataTypeDecoration: decorates,
697+
DataType: dataType,
698+
Default: def,
681699
}, nil
682700
}
683701

@@ -804,9 +822,11 @@ func (p *Parser) parseTableConstraints() (*sqlast.TableConstraint, error) {
804822
}, nil
805823
}
806824

807-
func (p *Parser) parseColumnDefinition() (sqlast.Node, []*sqlast.ColumnConstraint, error) {
825+
// TODO rethink mysql create table AST
826+
func (p *Parser) parseColumnDefinition() (sqlast.Node, []*sqlast.ColumnConstraint, []sqlast.MyDataTypeDecoration, error) {
808827
var specs []*sqlast.ColumnConstraint
809828
var def sqlast.Node
829+
var decorates []sqlast.MyDataTypeDecoration
810830

811831
COLUMN_DEF_LOOP:
812832
for {
@@ -822,22 +842,28 @@ COLUMN_DEF_LOOP:
822842
if ok, _, _ := p.parseKeyword("DEFAULT"); ok {
823843
d, err := p.parseDefaultExpr(0)
824844
if err != nil {
825-
return nil, nil, errors.Errorf("parseDefaultExpr failed: %w", err)
845+
return nil, nil, nil, errors.Errorf("parseDefaultExpr failed: %w", err)
826846
}
827847
def = d
828848
continue
829849
}
830850
case "CONSTRAINT", "NOT", "UNIQUE", "PRIMARY", "REFERENCES", "CHECK":
831851
s, err := p.parseColumnConstraints()
832852
if err != nil {
833-
return nil, nil, errors.Errorf("parseColumnConstraints failed: %w", err)
853+
return nil, nil, nil, errors.Errorf("parseColumnConstraints failed: %w", err)
834854
}
835855
specs = s
856+
case "AUTO_INCREMENT":
857+
p.mustNextToken()
858+
decorates = append(decorates, &sqlast.AutoIncrement{
859+
Auto: t.From,
860+
Increment: t.To,
861+
})
836862
default:
837863
break COLUMN_DEF_LOOP
838864
}
839865
}
840-
return def, specs, nil
866+
return def, specs, decorates, nil
841867
}
842868

843869
func (p *Parser) parseColumnConstraints() ([]*sqlast.ColumnConstraint, error) {
@@ -945,6 +971,111 @@ CONSTRAINT_LOOP:
945971
return constraints, nil
946972
}
947973

974+
func (p *Parser) parseTableOptions() ([]sqlast.TableOption, error) {
975+
var opts []sqlast.TableOption
976+
977+
for {
978+
tok, err := p.peekToken()
979+
if err == EOF {
980+
return opts, nil
981+
} else if err != nil {
982+
return nil, err
983+
}
984+
985+
if tok.Kind == sqltoken.Comma {
986+
tok = p.mustNextToken()
987+
}
988+
if tok.Kind != sqltoken.SQLKeyword {
989+
break
990+
}
991+
opt, err := p.parseTableOption()
992+
if err != nil {
993+
p.Debug()
994+
return nil, errors.Errorf("parseTableOption failed: %w", err)
995+
}
996+
opts = append(opts, opt)
997+
}
998+
999+
return opts, nil
1000+
}
1001+
1002+
func (p *Parser) parseTableOption() (sqlast.TableOption, error) {
1003+
tok, _ := p.peekToken()
1004+
if tok.Kind != sqltoken.SQLKeyword {
1005+
return nil, errors.Errorf("must be SQLKeyword but: %v", tok)
1006+
}
1007+
word, _ := tok.Value.(*sqltoken.SQLWord)
1008+
1009+
p.mustNextToken()
1010+
switch word.Keyword {
1011+
case "ENGINE":
1012+
opt := &sqlast.MyEngine{
1013+
Engine: tok.From,
1014+
}
1015+
t, _ := p.peekToken()
1016+
if t.Kind == sqltoken.Eq {
1017+
opt.Equal = true
1018+
p.mustNextToken()
1019+
t, _ = p.peekToken()
1020+
}
1021+
1022+
if t.Kind != sqltoken.SQLKeyword {
1023+
return nil, errors.Errorf("expected '=' or 'engine_name' but: %v", t)
1024+
}
1025+
name, _ := p.parseIdentifier()
1026+
opt.Name = name
1027+
return opt, nil
1028+
case "DEFAULT":
1029+
opt := &sqlast.MyCharset{
1030+
IsDefault: true,
1031+
Default: tok.From,
1032+
}
1033+
ok, t, err := p.parseKeyword("CHARSET")
1034+
if !ok || err != nil {
1035+
return nil, errors.Errorf("expected CHARSET but: %v", t)
1036+
}
1037+
opt.Charset = t.From
1038+
1039+
t, _ = p.peekToken()
1040+
if t.Kind == sqltoken.Eq {
1041+
opt.Equal = true
1042+
p.mustNextToken()
1043+
t, _ = p.peekToken()
1044+
}
1045+
1046+
if t.Kind != sqltoken.SQLKeyword {
1047+
return nil, errors.Errorf("expected '=' or 'charset_name' but: %v", t)
1048+
}
1049+
1050+
name, _ := p.parseIdentifier()
1051+
1052+
opt.Name = name
1053+
1054+
return opt, nil
1055+
case "CHARSET":
1056+
opt := &sqlast.MyCharset{
1057+
Charset: tok.From,
1058+
}
1059+
t, _ := p.peekToken()
1060+
if t.Kind == sqltoken.Eq {
1061+
opt.Equal = true
1062+
p.mustNextToken()
1063+
t, _ = p.peekToken()
1064+
}
1065+
1066+
if t.Kind != sqltoken.SQLKeyword {
1067+
return nil, errors.Errorf("expected '=' or 'charset_name' but: %v", t)
1068+
}
1069+
1070+
name, _ := p.parseIdentifier()
1071+
opt.Name = name
1072+
1073+
return opt, nil
1074+
default:
1075+
return nil, errors.Errorf("unsupported Table Options: %v", word)
1076+
}
1077+
}
1078+
9481079
func (p *Parser) parseDelete() (sqlast.Stmt, error) {
9491080
ok, d, _ := p.parseKeyword("DELETE")
9501081
if !ok {
@@ -2711,6 +2842,14 @@ func (p *Parser) parseExistsExpression(negatedTok *sqltoken.Token) (sqlast.Node,
27112842
}, nil
27122843
}
27132844

2845+
func (p *Parser) parseMyUnsigned() (bool, sqltoken.Pos) {
2846+
if ok, u, _ := p.parseKeyword("UNSIGNED"); ok {
2847+
return ok, u.To
2848+
}
2849+
2850+
return false, sqltoken.Pos{}
2851+
}
2852+
27142853
func (p *Parser) expectKeyword(expected string) *sqltoken.Token {
27152854
ok, tok, err := p.parseKeyword(expected)
27162855
if err != nil || !ok {

sqlast/ast.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import (
1010
"log"
1111
"strings"
1212

13-
"github.com/akito0107/xsqlparser/sqltoken"
1413
errors "golang.org/x/xerrors"
14+
15+
"github.com/akito0107/xsqlparser/sqltoken"
1516
)
1617

1718
// AST Node interface. All node types implements this interface.
@@ -727,6 +728,10 @@ func commaSeparatedString(list interface{}) string {
727728
for _, l := range s {
728729
strs = append(strs, l.ToSQLString())
729730
}
731+
case []TableOption:
732+
for _, l := range s {
733+
strs = append(strs, l.ToSQLString())
734+
}
730735
default:
731736
log.Fatalf("unexpected type array %+v", list)
732737
}

sqlast/my_data_type_decoration_gen.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sqlast/stmt.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ type CreateTableStmt struct {
244244
Elements []TableElement
245245
Location *string
246246
NotExists bool
247+
Options []TableOption
247248
}
248249

249250
func (c *CreateTableStmt) Pos() sqltoken.Pos {
@@ -259,7 +260,13 @@ func (c *CreateTableStmt) ToSQLString() string {
259260
if c.NotExists {
260261
ifNotExists = "IF NOT EXISTS "
261262
}
262-
return fmt.Sprintf("CREATE TABLE %s%s (%s)", ifNotExists, c.Name.ToSQLString(), commaSeparatedString(c.Elements))
263+
sql := fmt.Sprintf("CREATE TABLE %s%s (%s)", ifNotExists, c.Name.ToSQLString(), commaSeparatedString(c.Elements))
264+
265+
if len(c.Options) != 0 {
266+
sql += commaSeparatedString(c.Options)
267+
}
268+
269+
return sql
263270
}
264271

265272
type Assignment struct {
@@ -397,10 +404,11 @@ func (c *CheckTableConstraint) ToSQLString() string {
397404

398405
type ColumnDef struct {
399406
tableElement
400-
Name *Ident
401-
DataType Type
402-
Default Node
403-
Constraints []*ColumnConstraint
407+
Name *Ident
408+
DataType Type
409+
Default Node
410+
MyDataTypeDecoration []MyDataTypeDecoration // DataType Decoration for MySQL eg. AUTO_INCREMENT currently, only supports AUTO_INCREMENT
411+
Constraints []*ColumnConstraint
404412
}
405413

406414
func (c *ColumnDef) Pos() sqltoken.Pos {
@@ -417,12 +425,36 @@ func (c *ColumnDef) ToSQLString() string {
417425
str += fmt.Sprintf(" DEFAULT %s", c.Default.ToSQLString())
418426
}
419427

428+
for _, m := range c.MyDataTypeDecoration {
429+
str += " " + m.ToSQLString()
430+
}
431+
420432
for _, cons := range c.Constraints {
421433
str += cons.ToSQLString()
422434
}
423435
return str
424436
}
425437

438+
//go:generate genmark -t MyDataTypeDecoration -e Node
439+
440+
type AutoIncrement struct {
441+
myDataTypeDecoration
442+
Auto sqltoken.Pos
443+
Increment sqltoken.Pos
444+
}
445+
446+
func (a *AutoIncrement) ToSQLString() string {
447+
return "AUTO_INCREMENT"
448+
}
449+
450+
func (a *AutoIncrement) Pos() sqltoken.Pos {
451+
return a.Auto
452+
}
453+
454+
func (a *AutoIncrement) End() sqltoken.Pos {
455+
return a.Increment
456+
}
457+
426458
type ColumnConstraint struct {
427459
Name *Ident
428460
Constraint sqltoken.Pos

0 commit comments

Comments
 (0)