mirror of
https://github.com/ent/ent.git
synced 2026-05-22 09:31:45 +03:00
dialect/sql/schema: index builders per dialect
Summary: Pull Request resolved: https://github.com/facebookincubator/ent/pull/118 Reviewed By: alexsn Differential Revision: D18092371 fbshipit-source-id: 02b85724b1e00d10c930112b9e2c8d07c8727216
This commit is contained in:
committed by
Facebook Github Bot
parent
c414cd9a82
commit
88bfbc38df
@@ -39,7 +39,7 @@ func WithGlobalUniqueID(b bool) MigrateOption {
|
||||
// Defaults to false.
|
||||
func WithDropColumn(b bool) MigrateOption {
|
||||
return func(m *Migrate) {
|
||||
m.dropColumn = b
|
||||
m.dropColumns = b
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func WithDropColumn(b bool) MigrateOption {
|
||||
// Defaults to false.
|
||||
func WithDropIndex(b bool) MigrateOption {
|
||||
return func(m *Migrate) {
|
||||
m.dropIndex = b
|
||||
m.dropIndexes = b
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ func WithDropIndex(b bool) MigrateOption {
|
||||
type Migrate struct {
|
||||
sqlDialect
|
||||
universalID bool // global unique ids.
|
||||
dropColumn bool // drop deleted columns.
|
||||
dropIndex bool // drop deleted indexes.
|
||||
dropColumns bool // drop deleted columns.
|
||||
dropIndexes bool // drop deleted indexes.
|
||||
typeRanges []string // types order by their range.
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func (m *Migrate) create(ctx context.Context, tx dialect.Tx, tables ...*Table) e
|
||||
}
|
||||
// indexes.
|
||||
for _, idx := range t.Indexes {
|
||||
query, args := idx.Builder(t.Name).Query()
|
||||
query, args := m.addIndex(idx, t.Name).Query()
|
||||
if err := tx.Exec(ctx, query, args, new(sql.Result)); err != nil {
|
||||
return fmt.Errorf("create index %q: %v", idx.Name, err)
|
||||
}
|
||||
@@ -183,9 +183,9 @@ func (m *Migrate) apply(ctx context.Context, tx dialect.Tx, table string, change
|
||||
// constraints should be dropped before dropping columns, because if a column
|
||||
// is a part of multi-column constraints (like, unique index), ALTER TABLE
|
||||
// might fail if the intermediate state violates the constraints.
|
||||
if m.dropIndex {
|
||||
if m.dropIndexes {
|
||||
for _, idx := range change.index.drop {
|
||||
query, args := idx.DropBuilder(table).Query()
|
||||
query, args := m.dropIndex(idx, table).Query()
|
||||
if err := tx.Exec(ctx, query, args, new(sql.Result)); err != nil {
|
||||
return fmt.Errorf("drop index of table %q: %v", table, err)
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (m *Migrate) apply(ctx context.Context, tx dialect.Tx, table string, change
|
||||
for _, c := range change.column.modify {
|
||||
b.ModifyColumns(m.alterColumn(c)...)
|
||||
}
|
||||
if m.dropColumn {
|
||||
if m.dropColumns {
|
||||
for _, c := range change.column.drop {
|
||||
b.DropColumn(sql.Dialect(m.Dialect()).Column(c.Name))
|
||||
}
|
||||
@@ -211,7 +211,7 @@ func (m *Migrate) apply(ctx context.Context, tx dialect.Tx, table string, change
|
||||
}
|
||||
}
|
||||
for _, idx := range change.index.add {
|
||||
query, args := idx.Builder(table).Query()
|
||||
query, args := m.addIndex(idx, table).Query()
|
||||
if err := tx.Exec(ctx, query, args, new(sql.Result)); err != nil {
|
||||
return fmt.Errorf("create index %q: %v", table, err)
|
||||
}
|
||||
@@ -414,4 +414,6 @@ type sqlDialect interface {
|
||||
tBuilder(*Table) *sql.TableBuilder
|
||||
addColumn(*Column) *sql.ColumnBuilder
|
||||
alterColumn(*Column) []*sql.ColumnBuilder
|
||||
addIndex(*Index, string) *sql.IndexBuilder
|
||||
dropIndex(*Index, string) *sql.DropIndexBuilder
|
||||
}
|
||||
|
||||
@@ -111,3 +111,13 @@ func (d *MySQL) addColumn(c *Column) *sql.ColumnBuilder { return c.MySQL(d.versi
|
||||
func (d *MySQL) alterColumn(c *Column) []*sql.ColumnBuilder {
|
||||
return []*sql.ColumnBuilder{c.MySQL(d.version)}
|
||||
}
|
||||
|
||||
// addIndex returns the querying for adding an index to MySQL.
|
||||
func (d *MySQL) addIndex(i *Index, table string) *sql.IndexBuilder {
|
||||
return i.Builder(table)
|
||||
}
|
||||
|
||||
// addIndex returns the querying for dropping an index in MySQL.
|
||||
func (d *MySQL) dropIndex(i *Index, table string) *sql.DropIndexBuilder {
|
||||
return i.DropBuilder(table)
|
||||
}
|
||||
|
||||
@@ -115,6 +115,8 @@ func (d *Postgres) table(ctx context.Context, tx dialect.Tx, name string) (*Tabl
|
||||
}
|
||||
c.Key = UniqueKey
|
||||
c.Unique = true
|
||||
c.indexes.append(idx)
|
||||
fallthrough
|
||||
default:
|
||||
t.AddIndex(idx.Name, idx.Unique, idx.columns)
|
||||
}
|
||||
@@ -162,6 +164,9 @@ func (d *Postgres) indexes(ctx context.Context, tx dialect.Tx, table string) (In
|
||||
if err := rows.Scan(&name, &column, &primary, &unique); err != nil {
|
||||
return nil, fmt.Errorf("scanning index description: %v", err)
|
||||
}
|
||||
// If the index is prefixed with the table, it's probably was
|
||||
// added by this driver (and not entc) and it should be trimmed.
|
||||
name = strings.TrimPrefix(name, table+"_")
|
||||
idx, ok := names[name]
|
||||
if !ok {
|
||||
idx = &Index{Name: name, Unique: unique, primary: primary}
|
||||
@@ -298,3 +303,25 @@ func (d *Postgres) alterColumn(c *Column) (ops []*sql.ColumnBuilder) {
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
// addIndex returns the querying for adding an index to PostgreSQL.
|
||||
func (d *Postgres) addIndex(i *Index, table string) *sql.IndexBuilder {
|
||||
// Since index name should be unique in pg_class for schema,
|
||||
// we prefix it with the table name and remove on read.
|
||||
name := fmt.Sprintf("%s_%s", table, i.Name)
|
||||
idx := sql.Dialect(dialect.Postgres).
|
||||
CreateIndex(name).Table(table)
|
||||
if i.Unique {
|
||||
idx.Unique()
|
||||
}
|
||||
for _, c := range i.Columns {
|
||||
idx.Column(c.Name)
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
// dropIndex returns a DropIndexBuilder for the given table index.
|
||||
func (d *Postgres) dropIndex(i *Index, table string) *sql.DropIndexBuilder {
|
||||
name := fmt.Sprintf("%s_%s", table, i.Name)
|
||||
return sql.Dialect(dialect.Postgres).DropIndex(name)
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ func TestPostgres_Create(t *testing.T) {
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint(20)", "NO", "NULL").
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("name", "character", "YES", "NULL").
|
||||
AddRow("doc", "jsonb", "YES", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
@@ -229,7 +229,7 @@ func TestPostgres_Create(t *testing.T) {
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint(20)", "NO", "NULL").
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("name", "character", "YES", "NULL").
|
||||
AddRow("doc", "jsonb", "YES", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
@@ -265,7 +265,7 @@ func TestPostgres_Create(t *testing.T) {
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint(20)", "NO", "NULL").
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("name", "character", "YES", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique"}).
|
||||
@@ -300,7 +300,7 @@ func TestPostgres_Create(t *testing.T) {
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint(20)", "NO", "NULL").
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("name", "character", "YES", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique"}).
|
||||
@@ -335,7 +335,7 @@ func TestPostgres_Create(t *testing.T) {
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint(20)", "NO", "NULL").
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("name", "character", "YES", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique"}).
|
||||
@@ -369,7 +369,7 @@ func TestPostgres_Create(t *testing.T) {
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint(20)", "NO", "NULL").
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("name", "character", "YES", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique"}).
|
||||
@@ -403,7 +403,7 @@ func TestPostgres_Create(t *testing.T) {
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint(20)", "NO", "NULL").
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("name", "character", "NO", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique"}).
|
||||
@@ -413,6 +413,109 @@ func TestPostgres_Create(t *testing.T) {
|
||||
mock.ExpectCommit()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apply uniqueness on column",
|
||||
tables: []*Table{
|
||||
{
|
||||
Name: "users",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: field.TypeInt, Increment: true},
|
||||
{Name: "age", Type: field.TypeInt, Unique: true},
|
||||
},
|
||||
PrimaryKey: []*Column{
|
||||
{Name: "id", Type: field.TypeInt, Increment: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
before: func(mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(escape("SHOW server_version_num")).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"server_version_num"}).AddRow("120000"))
|
||||
mock.ExpectQuery(escape(`SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("age", "bigint", "NO", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique"}).
|
||||
AddRow("users_pkey", "id", "t", "t"))
|
||||
mock.ExpectExec(escape(`CREATE UNIQUE INDEX "users_age" ON "users"("age")`)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectCommit()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove uniqueness from column without option",
|
||||
tables: []*Table{
|
||||
{
|
||||
Name: "users",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: field.TypeInt, Increment: true},
|
||||
{Name: "age", Type: field.TypeInt},
|
||||
},
|
||||
PrimaryKey: []*Column{
|
||||
{Name: "id", Type: field.TypeInt, Increment: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
before: func(mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(escape("SHOW server_version_num")).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"server_version_num"}).AddRow("120000"))
|
||||
mock.ExpectQuery(escape(`SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("age", "bigint", "NO", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique"}).
|
||||
AddRow("users_pkey", "id", "t", "t").
|
||||
AddRow("users_age_key", "age", "f", "t"))
|
||||
mock.ExpectCommit()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove uniqueness from column with option",
|
||||
tables: []*Table{
|
||||
{
|
||||
Name: "users",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: field.TypeInt, Increment: true},
|
||||
{Name: "age", Type: field.TypeInt},
|
||||
},
|
||||
PrimaryKey: []*Column{
|
||||
{Name: "id", Type: field.TypeInt, Increment: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
options: []MigrateOption{WithDropIndex(true)},
|
||||
before: func(mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(escape("SHOW server_version_num")).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"server_version_num"}).AddRow("120000"))
|
||||
mock.ExpectQuery(escape(`SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default" FROM INFORMATION_SCHEMA.COLUMNS WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)).
|
||||
WithArgs("users").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default"}).
|
||||
AddRow("id", "bigint", "NO", "NULL").
|
||||
AddRow("age", "bigint", "NO", "NULL"))
|
||||
mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "users"))).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique"}).
|
||||
AddRow("users_pkey", "id", "t", "t").
|
||||
AddRow("users_age_key", "age", "f", "t"))
|
||||
mock.ExpectExec(escape(`DROP INDEX "users_age_key"`)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectCommit()
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -163,8 +163,14 @@ func (t *Table) index(name string) (*Index, bool) {
|
||||
if idx.Name == name {
|
||||
return idx, true
|
||||
}
|
||||
// Same as below, there are cases where the index name
|
||||
// is unknown (created automatically on column constraint).
|
||||
if len(idx.Columns) == 1 && idx.Columns[0].Name == name {
|
||||
return idx, true
|
||||
}
|
||||
}
|
||||
// if it is an "implicit index" (unique constraint on table creation).
|
||||
// If it is an "implicit index" (unique constraint on
|
||||
// table creation) and it didn't load on table scanning.
|
||||
if c, ok := t.column(name); ok && c.Unique {
|
||||
return &Index{Name: name, Unique: c.Unique, Columns: []*Column{c}, columns: []string{c.Name}}, true
|
||||
}
|
||||
|
||||
@@ -61,11 +61,19 @@ func (d *SQLite) setRange(ctx context.Context, tx dialect.Tx, name string, value
|
||||
return tx.Exec(ctx, query, args, new(sql.Result))
|
||||
}
|
||||
|
||||
// fkExist returns always true to disable foreign-keys creation after the table was created.
|
||||
func (d *SQLite) fkExist(context.Context, dialect.Tx, string) (bool, error) { return true, nil }
|
||||
func (d *SQLite) table(context.Context, dialect.Tx, string) (*Table, error) { return nil, nil }
|
||||
|
||||
func (*SQLite) cType(c *Column) string { return c.SQLiteType() }
|
||||
func (*SQLite) tBuilder(t *Table) *sql.TableBuilder { return t.SQLite() }
|
||||
func (*SQLite) addColumn(c *Column) *sql.ColumnBuilder { return c.SQLite() }
|
||||
func (*SQLite) alterColumn(c *Column) []*sql.ColumnBuilder { return []*sql.ColumnBuilder{c.SQLite()} }
|
||||
|
||||
// fkExist returns always tru to disable foreign-keys creation after the table was created.
|
||||
func (d *SQLite) fkExist(context.Context, dialect.Tx, string) (bool, error) { return true, nil }
|
||||
func (d *SQLite) table(context.Context, dialect.Tx, string) (*Table, error) { return nil, nil }
|
||||
func (d *SQLite) addIndex(i *Index, table string) *sql.IndexBuilder {
|
||||
return i.Builder(table)
|
||||
}
|
||||
|
||||
func (d *SQLite) dropIndex(i *Index, _ string) *sql.DropIndexBuilder {
|
||||
return i.DropBuilder("")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user