Skip to content

Commit 3e5da06

Browse files
committed
chore(embedded/sql): Support PRIMARY KEY constraint on individual columns
Signed-off-by: Stefano Scafiti <[email protected]>
1 parent cdd00cb commit 3e5da06

File tree

7 files changed

+567
-395
lines changed

7 files changed

+567
-395
lines changed

embedded/sql/catalog.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ type Catalog struct {
4242
maxTableID uint32 // The maxTableID variable is used to assign unique ids to new tables as they are created.
4343
}
4444

45+
type Constraint interface{}
46+
47+
type PrimaryKeyConstraint []string
48+
4549
type CheckConstraint struct {
4650
id uint32
4751
name string

embedded/sql/engine.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ var (
5151
ErrInvalidCheckConstraint = errors.New("invalid check constraint")
5252
ErrCheckConstraintViolation = errors.New("check constraint violation")
5353
ErrReservedWord = errors.New("reserved word")
54+
ErrNoPrimaryKey = errors.New("no primary key specified")
5455
ErrPKCanNotBeNull = errors.New("primary key can not be null")
5556
ErrPKCanNotBeUpdated = errors.New("primary key can not be updated")
57+
ErrMultiplePrimaryKeys = errors.New("multiple primary keys are not allowed")
5658
ErrNotNullableColumnCannotBeNull = errors.New("not nullable column can not be null")
5759
ErrNewColumnMustBeNullable = errors.New("new column must be nullable")
5860
ErrIndexAlreadyExists = errors.New("index already exists")

embedded/sql/engine_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ func TestCreateTable(t *testing.T) {
126126
engine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))
127127
require.NoError(t, err)
128128

129+
_, _, err = engine.Exec(context.Background(), nil, "CREATE TABLE table1 (id INTEGER, name VARCHAR)", nil)
130+
require.ErrorIs(t, err, ErrNoPrimaryKey)
131+
132+
_, _, err = engine.Exec(context.Background(), nil, "CREATE TABLE table1 (id INTEGER PRIMARY KEY, name VARCHAR PRIMARY KEY)", nil)
133+
require.ErrorIs(t, err, ErrMultiplePrimaryKeys)
134+
135+
_, _, err = engine.Exec(context.Background(), nil, "CREATE TABLE table1 (id INTEGER PRIMARY KEY, name VARCHAR, PRIMARY KEY (id, name))", nil)
136+
require.ErrorIs(t, err, ErrMultiplePrimaryKeys)
137+
129138
_, _, err = engine.Exec(context.Background(), nil, "CREATE TABLE table1 (name VARCHAR, PRIMARY KEY id)", nil)
130139
require.ErrorIs(t, err, ErrColumnDoesNotExist)
131140

@@ -135,6 +144,9 @@ func TestCreateTable(t *testing.T) {
135144
_, _, err = engine.Exec(context.Background(), nil, "CREATE TABLE table1 (name VARCHAR[30], PRIMARY KEY name)", nil)
136145
require.NoError(t, err)
137146

147+
_, _, err = engine.Exec(context.Background(), nil, "CREATE TABLE table10 (name VARCHAR[30] PRIMARY KEY)", nil)
148+
require.NoError(t, err)
149+
138150
_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf("CREATE TABLE table2 (name VARCHAR[%d], PRIMARY KEY name)", MaxKeyLen+1), nil)
139151
require.ErrorIs(t, err, ErrLimitedKeyType)
140152

embedded/sql/parser_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ func TestCreateTableStmt(t *testing.T) {
291291
{
292292
input: "CREATE TABLE table1()",
293293
expectedOutput: []SQLStmt{&CreateTableStmt{table: "table1"}},
294-
expectedError: errors.New("syntax error: unexpected ')', expecting IDENTIFIER at position 21"),
294+
expectedError: errors.New("syntax error: unexpected ')', expecting CONSTRAINT or PRIMARY or CHECK or IDENTIFIER at position 21"),
295295
},
296296
{
297297
input: "CREATE TABLE table1(id INTEGER, balance FLOAT, CONSTRAINT non_negative_balance CHECK (balance >= 0), PRIMARY KEY id)",
@@ -312,10 +312,26 @@ func TestCreateTableStmt(t *testing.T) {
312312
},
313313
},
314314
},
315-
pkColNames: []string{"id"},
315+
pkColNames: PrimaryKeyConstraint{"id"},
316316
}},
317317
expectedError: nil,
318318
},
319+
{
320+
input: "CREATE TABLE table1(id INTEGER PRIMARY KEY)",
321+
expectedOutput: []SQLStmt{
322+
&CreateTableStmt{
323+
table: "table1",
324+
colsSpec: []*ColSpec{
325+
{
326+
colName: "id",
327+
colType: IntegerType,
328+
primaryKey: true,
329+
notNull: true,
330+
},
331+
},
332+
},
333+
},
334+
},
319335
{
320336
input: "DROP TABLE table1",
321337
expectedOutput: []SQLStmt{

embedded/sql/sql_grammar.y

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ func setResult(l yyLexer, stmts []SQLStmt) {
2828
stmts []SQLStmt
2929
stmt SQLStmt
3030
datasource DataSource
31-
colsSpec []*ColSpec
3231
colSpec *ColSpec
3332
cols []*ColSelector
3433
rows []*RowSpec
@@ -57,7 +56,7 @@ func setResult(l yyLexer, stmts []SQLStmt) {
5756
joins []*JoinSpec
5857
join *JoinSpec
5958
joinType JoinType
60-
checks []CheckConstraint
59+
check CheckConstraint
6160
exp ValueExp
6261
binExp ValueExp
6362
err error
@@ -73,6 +72,8 @@ func setResult(l yyLexer, stmts []SQLStmt) {
7372
sqlPrivilege SQLPrivilege
7473
sqlPrivileges []SQLPrivilege
7574
whenThenClauses []whenThenClause
75+
tableElem TableElem
76+
tableElems []TableElem
7677
}
7778

7879
%token CREATE DROP USE DATABASE USER WITH PASSWORD READ READWRITE ADMIN SNAPSHOT HISTORY SINCE AFTER BEFORE UNTIL TX OF TIMESTAMP
@@ -120,7 +121,6 @@ func setResult(l yyLexer, stmts []SQLStmt) {
120121

121122
%type <stmts> sql sqlstmts
122123
%type <stmt> sqlstmt ddlstmt dmlstmt dqlstmt select_stmt
123-
%type <colsSpec> colsSpec
124124
%type <colSpec> colSpec
125125
%type <ids> ids one_or_more_ids opt_ids
126126
%type <cols> cols
@@ -141,7 +141,9 @@ func setResult(l yyLexer, stmts []SQLStmt) {
141141
%type <joins> opt_joins joins
142142
%type <join> join
143143
%type <joinType> opt_join_type
144-
%type <checks> opt_checks
144+
%type <check> check
145+
%type <tableElem> tableElem
146+
%type <tableElems> tableElems
145147
%type <exp> exp opt_exp opt_where opt_having boundexp opt_else
146148
%type <binExp> binExp
147149
%type <cols> opt_groupby
@@ -152,7 +154,7 @@ func setResult(l yyLexer, stmts []SQLStmt) {
152154
%type <ordexps> ordexps opt_orderby
153155
%type <opt_ord> opt_ord
154156
%type <ids> opt_indexon
155-
%type <boolean> opt_if_not_exists opt_auto_increment opt_not_null opt_not
157+
%type <boolean> opt_if_not_exists opt_auto_increment opt_not_null opt_not opt_primary_key
156158
%type <update> update
157159
%type <updates> updates
158160
%type <onConflict> opt_on_conflict
@@ -227,9 +229,34 @@ ddlstmt:
227229
$$ = &UseSnapshotStmt{period: $3}
228230
}
229231
|
230-
CREATE TABLE opt_if_not_exists IDENTIFIER '(' colsSpec ',' opt_checks PRIMARY KEY one_or_more_ids ')'
232+
CREATE TABLE opt_if_not_exists IDENTIFIER '(' tableElems ')'
231233
{
232-
$$ = &CreateTableStmt{ifNotExists: $3, table: $4, colsSpec: $6, checks: $8, pkColNames: $11}
234+
colsSpecs := make([]*ColSpec, 0, 5)
235+
var checks []CheckConstraint
236+
237+
var pk PrimaryKeyConstraint
238+
239+
for _, e := range $6 {
240+
switch c := e.(type) {
241+
case *ColSpec:
242+
colsSpecs = append(colsSpecs, c)
243+
case PrimaryKeyConstraint:
244+
pk = c
245+
case CheckConstraint:
246+
if checks == nil {
247+
checks = make([]CheckConstraint, 0, 5)
248+
}
249+
checks = append(checks, c)
250+
}
251+
}
252+
253+
$$ = &CreateTableStmt{
254+
ifNotExists: $3,
255+
table: $4,
256+
colsSpec: colsSpecs,
257+
pkColNames: pk,
258+
checks: checks,
259+
}
233260
}
234261
|
235262
DROP TABLE IDENTIFIER
@@ -587,22 +614,50 @@ fnCall:
587614
$$ = &FnCall{fn: $1, params: $3}
588615
}
589616

590-
colsSpec:
591-
colSpec
617+
tableElems:
618+
tableElem
592619
{
593-
$$ = []*ColSpec{$1}
620+
$$ = []TableElem{$1}
594621
}
595622
|
596-
colsSpec ',' colSpec
623+
tableElems ',' tableElem
597624
{
598625
$$ = append($1, $3)
599626
}
600627

628+
tableElem:
629+
colSpec
630+
{
631+
$$ = $1
632+
}
633+
|
634+
check
635+
{
636+
$$ = $1
637+
}
638+
|
639+
PRIMARY KEY one_or_more_ids
640+
{
641+
$$ = PrimaryKeyConstraint($3)
642+
}
643+
;
644+
601645
colSpec:
602-
IDENTIFIER TYPE opt_max_len opt_not_null opt_auto_increment
646+
IDENTIFIER TYPE opt_max_len opt_not_null opt_auto_increment opt_primary_key
647+
{
648+
$$ = &ColSpec{colName: $1, colType: $2, maxLen: int($3), notNull: $4 || $6, autoIncrement: $5, primaryKey: $6}
649+
}
650+
651+
opt_primary_key:
652+
{
653+
$$ = false
654+
}
655+
|
656+
PRIMARY KEY
603657
{
604-
$$ = &ColSpec{colName: $1, colType: $2, maxLen: int($3), notNull: $4, autoIncrement: $5}
658+
$$ = true
605659
}
660+
;
606661

607662
opt_max_len:
608663
{
@@ -1063,19 +1118,15 @@ opt_as:
10631118
$$ = $2
10641119
}
10651120

1066-
opt_checks:
1067-
{
1068-
$$ = nil
1069-
}
1070-
|
1071-
CHECK exp ',' opt_checks
1121+
check:
1122+
CHECK exp
10721123
{
1073-
$$ = append([]CheckConstraint{{exp: $2}}, $4...)
1124+
$$ = CheckConstraint{exp: $2}
10741125
}
10751126
|
1076-
CONSTRAINT IDENTIFIER CHECK exp ',' opt_checks
1127+
CONSTRAINT IDENTIFIER CHECK exp
10771128
{
1078-
$$ = append([]CheckConstraint{{name: $2, exp: $4}}, $6...)
1129+
$$ = CheckConstraint{name: $2, exp: $4}
10791130
}
10801131

10811132
opt_exp:

0 commit comments

Comments
 (0)