Skip to content

Commit

Permalink
Support AUTOINCREMENT directive in SQLite (#936)
Browse files Browse the repository at this point in the history
* refactor: delegate autoincrement to dialects

* feat: support AUTOINCREMENT PKs in SQLite

* chore: use succinct syntax

* refactor: push the check for PK to the caller

#929
  • Loading branch information
bevzzz authored Jan 10, 2024
1 parent 4785291 commit 8b5c3a3
Show file tree
Hide file tree
Showing 13 changed files with 60 additions and 25 deletions.
4 changes: 4 additions & 0 deletions dialect/mssqldialect/dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ func (d *Dialect) DefaultVarcharLen() int {
return 255
}

func (d *Dialect) AppendSequence(b []byte, _ *schema.Table, _ *schema.Field) []byte {
return append(b, " IDENTITY"...)
}

func sqlType(field *schema.Field) string {
switch field.DiscoveredSQLType {
case sqltype.Timestamp:
Expand Down
4 changes: 4 additions & 0 deletions dialect/mysqldialect/dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ func (d *Dialect) DefaultVarcharLen() int {
return 255
}

func (d *Dialect) AppendSequence(b []byte, _ *schema.Table, _ *schema.Field) []byte {
return append(b, " AUTO_INCREMENT"...)
}

func sqlType(field *schema.Field) string {
if field.DiscoveredSQLType == sqltype.Timestamp {
return datetimeType
Expand Down
4 changes: 4 additions & 0 deletions dialect/pgdialect/dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,7 @@ func (d *Dialect) AppendUint32(b []byte, n uint32) []byte {
func (d *Dialect) AppendUint64(b []byte, n uint64) []byte {
return strconv.AppendInt(b, int64(n), 10)
}

func (d *Dialect) AppendSequence(b []byte, _ *schema.Table, _ *schema.Field) []byte {
return append(b, " GENERATED BY DEFAULT AS IDENTITY"...)
}
20 changes: 20 additions & 0 deletions dialect/sqlitedialect/dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func New() *Dialect {
feature.InsertOnConflict |
feature.TableNotExists |
feature.SelectExists |
feature.AutoIncrement |
feature.CompositeIn
return d
}
Expand Down Expand Up @@ -91,6 +92,25 @@ func (d *Dialect) DefaultVarcharLen() int {
return 0
}

// AppendSequence adds AUTOINCREMENT keyword to the column definition. As per [documentation],
// AUTOINCREMENT is only valid for INTEGER PRIMARY KEY, and this method will be a noop for other columns.
//
// Because this is a valid construct:
// CREATE TABLE ("id" INTEGER PRIMARY KEY AUTOINCREMENT);
// and this is not:
// CREATE TABLE ("id" INTEGER AUTOINCREMENT, PRIMARY KEY ("id"));
// AppendSequence adds a primary key constraint as a *side-effect*. Callers should expect it to avoid building invalid SQL.
// SQLite also [does not support] AUTOINCREMENT column in composite primary keys.
//
// [documentation]: https://www.sqlite.org/autoinc.html
// [does not support]: https://stackoverflow.com/a/6793274/14726116
func (d *Dialect) AppendSequence(b []byte, table *schema.Table, field *schema.Field) []byte {
if field.IsPK && len(table.PKs) == 1 && field.CreateTableSQLType == sqltype.Integer {
b = append(b, " PRIMARY KEY AUTOINCREMENT"...)
}
return b
}

func fieldSQLType(field *schema.Field) string {
switch field.DiscoveredSQLType {
case sqltype.SmallInt, sqltype.BigInt:
Expand Down
2 changes: 1 addition & 1 deletion internal/dbtest/testdata/snapshots/TestQuery-sqlite-102
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE TABLE "models" ("id" INTEGER NOT NULL, "str" VARCHAR, PRIMARY KEY ("id")) PARTITION BY HASH (id)
CREATE TABLE "models" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "str" VARCHAR) PARTITION BY HASH (id)
2 changes: 1 addition & 1 deletion internal/dbtest/testdata/snapshots/TestQuery-sqlite-103
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE TABLE "models" ("id" INTEGER NOT NULL, "str" VARCHAR, PRIMARY KEY ("id")) TABLESPACE "fasttablespace"
CREATE TABLE "models" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "str" VARCHAR) TABLESPACE "fasttablespace"
2 changes: 1 addition & 1 deletion internal/dbtest/testdata/snapshots/TestQuery-sqlite-151
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE TABLE "users" ("id" INTEGER NOT NULL, PRIMARY KEY ("id"))
CREATE TABLE "users" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)
2 changes: 1 addition & 1 deletion internal/dbtest/testdata/snapshots/TestQuery-sqlite-32
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE TABLE "models" ("id" INTEGER NOT NULL, "str" VARCHAR, PRIMARY KEY ("id"))
CREATE TABLE "models" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "str" VARCHAR)
2 changes: 1 addition & 1 deletion internal/dbtest/testdata/snapshots/TestQuery-sqlite-33
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE TABLE "models" ("id" INTEGER NOT NULL, "struct" VARCHAR, "map" VARCHAR, "slice" VARCHAR, "array" VARCHAR, PRIMARY KEY ("id"))
CREATE TABLE "models" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "struct" VARCHAR, "map" VARCHAR, "slice" VARCHAR, "array" VARCHAR)
2 changes: 1 addition & 1 deletion internal/dbtest/testdata/snapshots/TestQuery-sqlite-57
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE TABLE "models" ("id" INTEGER NOT NULL, "str" VARCHAR, PRIMARY KEY ("id"), FOREIGN KEY ("profile_id") REFERENCES "profiles" ("id"))
CREATE TABLE "models" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "str" VARCHAR, FOREIGN KEY ("profile_id") REFERENCES "profiles" ("id"))
2 changes: 1 addition & 1 deletion internal/dbtest/testdata/snapshots/TestQuery-sqlite-71
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE TABLE "models" ("id" INTEGER NOT NULL, PRIMARY KEY ("id"))
CREATE TABLE "models" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)
31 changes: 13 additions & 18 deletions query_table_create.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bun

import (
"bytes"
"context"
"database/sql"
"fmt"
Expand Down Expand Up @@ -146,7 +147,7 @@ func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []by
b = append(b, "TEMP "...)
}
b = append(b, "TABLE "...)
if q.ifNotExists && fmter.Dialect().Features().Has(feature.TableNotExists) {
if q.ifNotExists && fmter.HasFeature(feature.TableNotExists) {
b = append(b, "IF NOT EXISTS "...)
}
b, err = q.appendFirstTable(fmter, b)
Expand All @@ -167,19 +168,12 @@ func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []by
if field.NotNull {
b = append(b, " NOT NULL"...)
}
if field.AutoIncrement {
switch {
case fmter.Dialect().Features().Has(feature.AutoIncrement):
b = append(b, " AUTO_INCREMENT"...)
case fmter.Dialect().Features().Has(feature.Identity):
b = append(b, " IDENTITY"...)
}
}
if field.Identity {
if fmter.Dialect().Features().Has(feature.GeneratedIdentity) {
b = append(b, " GENERATED BY DEFAULT AS IDENTITY"...)
}

if (field.Identity && fmter.HasFeature(feature.GeneratedIdentity)) ||
(field.AutoIncrement && (fmter.HasFeature(feature.AutoIncrement) || fmter.HasFeature(feature.Identity))) {
b = q.db.dialect.AppendSequence(b, q.table, field)
}

if field.SQLDefault != "" {
b = append(b, " DEFAULT "...)
b = append(b, field.SQLDefault...)
Expand All @@ -199,7 +193,12 @@ func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []by
}
}

b = q.appendPKConstraint(b, q.table.PKs)
// In SQLite AUTOINCREMENT is only valid for INTEGER PRIMARY KEY columns, so it might be that
// a primary key constraint has already been created in dialect.AppendSequence() call above.
// See sqldialect.Dialect.AppendSequence() for more details.
if len(q.table.PKs) > 0 && !bytes.Contains(b, []byte("PRIMARY KEY")) {
b = q.appendPKConstraint(b, q.table.PKs)
}
b = q.appendUniqueConstraints(fmter, b)

if q.fksFromRel {
Expand Down Expand Up @@ -330,10 +329,6 @@ func (q *CreateTableQuery) appendFKConstraints(
}

func (q *CreateTableQuery) appendPKConstraint(b []byte, pks []*schema.Field) []byte {
if len(pks) == 0 {
return b
}

b = append(b, ", PRIMARY KEY ("...)
b = appendColumns(b, "", pks)
b = append(b, ")"...)
Expand Down
8 changes: 8 additions & 0 deletions schema/dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ type Dialect interface {
AppendJSON(b, jsonb []byte) []byte
AppendBool(b []byte, v bool) []byte

// AppendSequence adds the appropriate instruction for the driver to create a sequence
// from which (autoincremented) values for the column will be generated.
AppendSequence(b []byte, t *Table, f *Field) []byte

// DefaultVarcharLen should be returned for dialects in which specifying VARCHAR length
// is mandatory in queries that modify the schema (CREATE TABLE / ADD COLUMN, etc).
// Dialects that do not have such requirement may return 0, which should be interpreted so by the caller.
Expand Down Expand Up @@ -177,3 +181,7 @@ func (d *nopDialect) IdentQuote() byte {
func (d *nopDialect) DefaultVarcharLen() int {
return 0
}

func (d *nopDialect) AppendSequence(b []byte, _ *Table, _ *Field) []byte {
return b
}

0 comments on commit 8b5c3a3

Please sign in to comment.