Skip to content
Open
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
54 changes: 51 additions & 3 deletions docs/operations/create_index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ description: A create index operation creates a new index on a set of columns.

## Structure

The `columns` field supports two formats:

### Array Format (Recommended)
Use this format for multi-column indexes to ensure predictable column ordering:

<YamlJsonTabs>
```yaml
create_index:
table: name of table on which to define the index
name: index name
columns:
column_name:
- name: column_name1
collate: collation name
sort: ASC | DESC
nulls: FIRST | LAST
Expand All @@ -20,6 +25,8 @@ create_index:
params:
- param1=val
- param2=val
- name: column_name2
sort: DESC
predicate: conditional expression for defining a partial index
storage_parameters: comma-separated list of storage parameters
unique: true | false
Expand All @@ -31,7 +38,8 @@ create_index:
"table": "name of table on which to define the index",
"name": "index name",
"columns": [
"column_name": {
{
"name": "column_name1",
"collate": "collation name",
"sort": "ASC | DESC",
"nulls": "FIRST | LAST",
Expand All @@ -42,8 +50,12 @@ create_index:
"param2=val"
]
}
},
{
"name": "column_name2",
"sort": "DESC"
}
]
],
"predicate": "conditional expression for defining a partial index",
"storage_parameters": "comma-separated list of storage parameters",
"unique": true | false,
Expand All @@ -53,9 +65,45 @@ create_index:
```
</YamlJsonTabs>

### Map Format (Legacy, Single-Column Only)
This format is still supported for single-column indexes but is deprecated for multi-column indexes due to non-deterministic ordering:

<YamlJsonTabs>
```yaml
create_index:
table: name of table on which to define the index
name: index name
columns:
column_name:
sort: DESC
method: btree
```
```json
{
"create_index": {
"table": "name of table on which to define the index",
"name": "index name",
"columns": {
"column_name": {
"sort": "DESC"
}
},
"method": "btree"
}
}
```
</YamlJsonTabs>

<Note type="warning">
**Deprecation Notice:** Map format for `columns` is deprecated for multi-column and partial indexes.
It will continue to work for single-column indexes, but using array format is recommended for consistency.
Support for map format in multi-column/partial indexes will be removed on June 17, 2025.
</Note>

* The field `method` can be `btree`, `hash`, `gist`, `spgist`, `gin`, `brin`.
* You can also specify storage parameters for the index in `storage_parameters`.
* To create a unique index set `unique` to `true`.
* For multi-column indexes, always use the array format to ensure predictable column ordering.

## Examples

Expand Down
8 changes: 8 additions & 0 deletions examples/55_create_multicolumn_index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
operations:
- create_index:
name: idx_users_last_first
table: users
columns:
- name: last_name
- name: first_name
sort: ASC
28 changes: 14 additions & 14 deletions pkg/migrations/dbactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,12 @@
name string
method string
unique bool
columns map[string]IndexField
columns []IndexColumn

Check failure on line 262 in pkg/migrations/dbactions.go

View workflow job for this annotation

GitHub Actions / code generation

undefined: IndexColumn
storageParameters string
predicate string
}

func NewCreateIndexConcurrentlyAction(conn db.DB, table, name, method string, unique bool, columns map[string]IndexField, storageParameters, predicate string) *createIndexConcurrentlyAction {
func NewCreateIndexConcurrentlyAction(conn db.DB, table, name, method string, unique bool, columns []IndexColumn, storageParameters, predicate string) *createIndexConcurrentlyAction {

Check failure on line 267 in pkg/migrations/dbactions.go

View workflow job for this annotation

GitHub Actions / code generation

undefined: IndexColumn
return &createIndexConcurrentlyAction{
conn: conn,
id: fmt.Sprintf("create_index_concurrently_%s_%s", table, name),
Expand Down Expand Up @@ -294,26 +294,26 @@
}

colSQLs := make([]string, 0, len(a.columns))
for columnName, settings := range a.columns {
colSQL := pq.QuoteIdentifier(columnName)
for _, col := range a.columns {
colSQL := pq.QuoteIdentifier(col.Name)
// deparse collations
if settings.Collate != "" {
colSQL += " COLLATE " + settings.Collate
if col.Collate != "" {
colSQL += " COLLATE " + col.Collate
}
// deparse operator classes and their parameters
if settings.Opclass != nil {
colSQL += " " + settings.Opclass.Name
if len(settings.Opclass.Params) > 0 {
colSQL += " " + strings.Join(settings.Opclass.Params, ", ")
if col.Opclass != nil {
colSQL += " " + col.Opclass.Name
if len(col.Opclass.Params) > 0 {
colSQL += " " + strings.Join(col.Opclass.Params, ", ")
}
}
// deparse sort order of the index column
if settings.Sort != "" {
colSQL += " " + string(settings.Sort)
if col.Sort != "" {
colSQL += " " + string(col.Sort)
}
// deparse nulls order of the index column
if settings.Nulls != nil {
colSQL += " " + string(*settings.Nulls)
if col.Nulls != nil {
colSQL += " " + string(*col.Nulls)
}
colSQLs = append(colSQLs, colSQL)
}
Expand Down
31 changes: 24 additions & 7 deletions pkg/migrations/op_create_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,27 @@
func (o *OpCreateIndex) Start(ctx context.Context, l Logger, conn db.DB, s *schema.Schema) (*StartResult, error) {
l.LogOperationStart(o)

// Check for deprecation warnings/errors
if err := o.checkDeprecation(l); err != nil {
return nil, err
}

table := s.GetTable(o.Table)
if table == nil {
return nil, TableDoesNotExistError{Name: o.Table}
}

cols := make(map[string]IndexField, len(o.Columns))
for name, settings := range map[string]IndexField(o.Columns) {
physicalName := table.PhysicalColumnNamesFor(name)
cols[physicalName[0]] = settings
// Build ordered columns array with physical column names
cols := make([]IndexColumn, 0, len(o.Columns))

Check failure on line 34 in pkg/migrations/op_create_index.go

View workflow job for this annotation

GitHub Actions / code generation

invalid argument: o.Columns (variable of type interface{}) for built-in len

Check failure on line 34 in pkg/migrations/op_create_index.go

View workflow job for this annotation

GitHub Actions / code generation

undefined: IndexColumn
for _, col := range o.Columns {

Check failure on line 35 in pkg/migrations/op_create_index.go

View workflow job for this annotation

GitHub Actions / code generation

cannot range over o.Columns (variable of type interface{})
physicalName := table.PhysicalColumnNamesFor(col.Name)
cols = append(cols, IndexColumn{

Check failure on line 37 in pkg/migrations/op_create_index.go

View workflow job for this annotation

GitHub Actions / code generation

undefined: IndexColumn
Name: physicalName[0],
Collate: col.Collate,
Nulls: col.Nulls,
Opclass: col.Opclass,
Sort: col.Sort,
})
}

dbActions := []DBAction{
Expand Down Expand Up @@ -70,14 +82,19 @@
return err
}

if len(o.Columns) == 0 {

Check failure on line 85 in pkg/migrations/op_create_index.go

View workflow job for this annotation

GitHub Actions / code generation

invalid argument: o.Columns (variable of type interface{}) for built-in len
return FieldRequiredError{Name: "columns"}
}

table := s.GetTable(o.Table)
if table == nil {
return TableDoesNotExistError{Name: o.Table}
}

for column := range map[string]IndexField(o.Columns) {
if table.GetColumn(column) == nil {
return ColumnDoesNotExistError{Table: o.Table, Name: column}
// Validate column existence
for _, col := range o.Columns {

Check failure on line 95 in pkg/migrations/op_create_index.go

View workflow job for this annotation

GitHub Actions / code generation

cannot range over o.Columns (variable of type interface{})
if table.GetColumn(col.Name) == nil {
return ColumnDoesNotExistError{Table: o.Table, Name: col.Name}
}
}

Expand Down
Loading
Loading