Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support AUTOINCREMENT directive in SQLite #936

Merged
merged 4 commits into from
Jan 10, 2024
Merged
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
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
bevzzz marked this conversation as resolved.
Show resolved Hide resolved
}

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 @@ -157,7 +158,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 @@ -178,19 +179,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 @@ -210,7 +204,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)
b, err = q.appendFKConstraints(fmter, b)
if err != nil {
Expand Down Expand Up @@ -309,10 +308,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
}
Loading