Skip to content

Commit 533cfb9

Browse files
authored
feat: Support CockroachDB Hash-sharded Indexes (#97)
2 parents c9a2b0e + 9be6b6a commit 533cfb9

File tree

11 files changed

+295
-45
lines changed

11 files changed

+295
-45
lines changed

.github/workflows/go-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ jobs:
161161
direnv exec . make test
162162
fi
163163
- uses: codecov/codecov-action@v4 # ref. https://github.com/codecov/codecov-action#example-workflowyml-with-codecov-action
164-
if: env.COVER
164+
if: ${{ env.COVER == 'true' }}
165165
with:
166166
token: ${{ secrets.CODECOV_TOKEN }}
167167
files: ${{ env.WORKDIR }}/coverage.txt

pkg/ddl/cockroachdb/ddl.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,32 @@ func (s *DataType) StringForDiff() string {
148148

149149
return str
150150
}
151+
152+
type Using struct {
153+
Value *Expr
154+
With *With
155+
}
156+
157+
func (u *Using) String() string {
158+
if u == nil {
159+
return ""
160+
}
161+
162+
str := "USING " + u.Value.String()
163+
if u.With != nil {
164+
str += " " + u.With.String()
165+
}
166+
return str
167+
}
168+
169+
type With struct {
170+
Value *Expr
171+
}
172+
173+
func (w *With) String() string {
174+
if w == nil {
175+
return ""
176+
}
177+
178+
return "WITH " + w.Value.String()
179+
}

pkg/ddl/cockroachdb/ddl_index_create.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ import (
1313
var _ Stmt = (*CreateIndexStmt)(nil)
1414

1515
type CreateIndexStmt struct {
16-
Comment string
17-
Unique bool
18-
IfNotExists bool
19-
Name *Ident
20-
TableName *ObjectName
21-
Using []*Ident
22-
Columns []*ColumnIdent
16+
Comment string
17+
Unique bool
18+
IfNotExists bool
19+
Name *Ident
20+
TableName *ObjectName
21+
UsingPreColumns *Using
22+
Columns []*ColumnIdent
23+
UsingPostColumns *Using
2324
}
2425

2526
func (s *CreateIndexStmt) GetNameForDiff() string {
@@ -45,11 +46,14 @@ func (s *CreateIndexStmt) String() string {
4546
str += "IF NOT EXISTS "
4647
}
4748
str += s.Name.String() + " ON " + s.TableName.String()
48-
if len(s.Using) > 0 {
49-
str += " USING "
50-
str += stringz.JoinStringers(" ", s.Using...)
49+
if s.UsingPreColumns != nil {
50+
str += " " + s.UsingPreColumns.String()
5151
}
52-
str += " (" + stringz.JoinStringers(", ", s.Columns...) + ");\n"
52+
str += " (" + stringz.JoinStringers(", ", s.Columns...) + ")"
53+
if s.UsingPostColumns != nil {
54+
str += " " + s.UsingPostColumns.String()
55+
}
56+
str += ";\n"
5357
return str
5458
}
5559

@@ -60,15 +64,21 @@ func (s *CreateIndexStmt) StringForDiff() string {
6064
}
6165
str += "INDEX "
6266
str += s.Name.StringForDiff() + " ON " + s.TableName.StringForDiff()
63-
// TODO: add USING
67+
if s.UsingPreColumns != nil {
68+
str += " " + s.UsingPreColumns.String()
69+
}
6470
str += " ("
6571
for i, c := range s.Columns {
6672
if i > 0 {
6773
str += ", "
6874
}
6975
str += c.StringForDiff()
7076
}
71-
str += ");\n"
77+
str += ")"
78+
if s.UsingPostColumns != nil {
79+
str += " " + s.UsingPostColumns.String()
80+
}
81+
str += ";\n"
7282
return str
7383
}
7484

pkg/ddl/cockroachdb/ddl_index_create_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ func TestCreateIndexStmt_String(t *testing.T) {
2727
t.Parallel()
2828

2929
stmt := &CreateIndexStmt{
30-
Comment: "test comment content",
31-
IfNotExists: true,
32-
Name: &Ident{Name: "test", QuotationMark: `"`, Raw: `"test"`},
33-
TableName: &ObjectName{Name: &Ident{Name: "users", QuotationMark: `"`, Raw: `"users"`}},
34-
Using: []*Ident{{Name: "btree", QuotationMark: ``, Raw: `btree`}},
30+
Comment: "test comment content",
31+
IfNotExists: true,
32+
Name: &Ident{Name: "test", QuotationMark: `"`, Raw: `"test"`},
33+
TableName: &ObjectName{Name: &Ident{Name: "users", QuotationMark: `"`, Raw: `"users"`}},
34+
UsingPreColumns: &Using{Value: &Expr{Idents: []*Ident{{Name: "btree", QuotationMark: ``, Raw: `btree`}}}},
3535
Columns: []*ColumnIdent{
3636
{
3737
Ident: &Ident{Name: "id", QuotationMark: `"`, Raw: `"id"`},

pkg/ddl/cockroachdb/ddl_table.go

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ func (c *ForeignKeyConstraint) StringForDiff() string {
139139

140140
// IndexConstraint represents a UNIQUE constraint. //diff:ignore-line-postgres-cockroach.
141141
type IndexConstraint struct { //diff:ignore-line-postgres-cockroach
142-
Name *Ident
143-
Unique bool //diff:ignore-line-postgres-cockroach
144-
Columns []*ColumnIdent
142+
Name *Ident
143+
Unique bool //diff:ignore-line-postgres-cockroach
144+
UsingPreColumns *Using
145+
Columns []*ColumnIdent
146+
UsingPostColumns *Using
145147
}
146148

147149
var _ Constraint = (*IndexConstraint)(nil) //diff:ignore-line-postgres-cockroach
@@ -157,7 +159,13 @@ func (c *IndexConstraint) String() string { //diff:ignore-line-postgres-cockroac
157159
if c.Name != nil { //diff:ignore-line-postgres-cockroach
158160
str += "INDEX " + c.Name.String() + " " //diff:ignore-line-postgres-cockroach
159161
}
162+
if c.UsingPreColumns != nil {
163+
str += " " + c.UsingPreColumns.String()
164+
}
160165
str += "(" + stringz.JoinStringers(", ", c.Columns...) + ")"
166+
if c.UsingPostColumns != nil {
167+
str += " " + c.UsingPostColumns.String()
168+
}
161169
return str
162170
}
163171

@@ -169,6 +177,9 @@ func (c *IndexConstraint) StringForDiff() string { //diff:ignore-line-postgres-c
169177
if c.Name != nil {
170178
str += "INDEX " + c.Name.StringForDiff() + " " //diff:ignore-line-postgres-cockroach
171179
}
180+
if c.UsingPreColumns != nil {
181+
str += " " + c.UsingPreColumns.String()
182+
}
172183
str += "("
173184
for i, v := range c.Columns {
174185
if i != 0 {
@@ -177,6 +188,9 @@ func (c *IndexConstraint) StringForDiff() string { //diff:ignore-line-postgres-c
177188
str += v.StringForDiff()
178189
}
179190
str += ")"
191+
if c.UsingPostColumns != nil {
192+
str += " " + c.UsingPostColumns.String()
193+
}
180194
return str
181195
}
182196

@@ -260,10 +274,12 @@ func (t *ObjectName) StringForDiff() string {
260274
}
261275

262276
type Column struct {
263-
Name *Ident
264-
DataType *DataType
265-
Default *Default
266-
NotNull bool
277+
Name *Ident
278+
DataType *DataType
279+
Default *Default
280+
NotNull bool
281+
NotVisible bool
282+
As *As //diff:ignore-line-postgres-cockroach
267283
}
268284

269285
type Default struct {
@@ -339,15 +355,69 @@ func (d *Default) StringForDiff() string {
339355
return ""
340356
}
341357

358+
type As struct {
359+
Value *Expr
360+
Type TokenType
361+
}
362+
363+
func (d *As) GoString() string { return internal.GoString(*d) }
364+
365+
func (d *As) String() string {
366+
var str string
367+
if d == nil {
368+
return ""
369+
}
370+
371+
if d.Value != nil {
372+
str += "AS " + d.Value.String()
373+
if d.Type != "" {
374+
str += " " + d.Type.String()
375+
}
376+
return str
377+
}
378+
379+
return ""
380+
}
381+
382+
func (d *As) StringForDiff() string {
383+
if d == nil {
384+
return ""
385+
}
386+
387+
if e := d.Value; e != nil {
388+
str := "AS "
389+
for i, v := range d.Value.Idents {
390+
if i != 0 {
391+
str += " "
392+
}
393+
str += v.StringForDiff()
394+
}
395+
396+
if d.Type != "" {
397+
str += " " + d.Type.String()
398+
}
399+
400+
return str
401+
}
402+
403+
return ""
404+
}
405+
342406
func (c *Column) String() string {
343407
str := c.Name.String() + " " +
344408
c.DataType.String()
409+
if c.NotVisible {
410+
str += " NOT VISIBLE"
411+
}
345412
if c.NotNull { //diff:ignore-line-postgres-cockroach
346413
str += " NOT NULL" //diff:ignore-line-postgres-cockroach
347414
} //diff:ignore-line-postgres-cockroach
348415
if s := c.Default.String(); s != "" { //diff:ignore-line-postgres-cockroach
349416
str += " " + s //diff:ignore-line-postgres-cockroach
350417
}
418+
if c.As != nil {
419+
str += " " + c.As.String()
420+
}
351421
return str
352422
}
353423

pkg/ddl/cockroachdb/diff_create_table.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/kunitsucom/util.go/exp/diff/simplediff"
77

88
apperr "github.com/kunitsucom/ddlctl/pkg/apperr"
9+
"github.com/kunitsucom/ddlctl/pkg/logs"
910

1011
"github.com/kunitsucom/ddlctl/pkg/ddl"
1112
)
@@ -109,13 +110,16 @@ func DiffCreateTable(before, after *CreateTableStmt, opts ...DiffCreateTableOpti
109110
result.Stmts = append( //diff:ignore-line-postgres-cockroach
110111
result.Stmts, //diff:ignore-line-postgres-cockroach
111112
&DropIndexStmt{ //diff:ignore-line-postgres-cockroach
112-
Name: beforeConstraint.GetName(), //diff:ignore-line-postgres-cockroach
113+
Comment: simplediff.Diff(beforeConstraint.String(), afterConstraint.String()).String(), //diff:ignore-line-postgres-cockroach
114+
Name: beforeConstraint.GetName(), //diff:ignore-line-postgres-cockroach
113115
}, //diff:ignore-line-postgres-cockroach
114116
&CreateIndexStmt{ //diff:ignore-line-postgres-cockroach
115-
Unique: ac.Unique, //diff:ignore-line-postgres-cockroach
116-
Name: ac.GetName(), //diff:ignore-line-postgres-cockroach
117-
TableName: after.Name, //diff:ignore-line-postgres-cockroach
118-
Columns: ac.Columns, //diff:ignore-line-postgres-cockroach
117+
Unique: ac.Unique, //diff:ignore-line-postgres-cockroach
118+
Name: ac.GetName(), //diff:ignore-line-postgres-cockroach
119+
TableName: after.Name, //diff:ignore-line-postgres-cockroach
120+
UsingPreColumns: ac.UsingPreColumns, //diff:ignore-line-postgres-cockroach
121+
Columns: ac.Columns, //diff:ignore-line-postgres-cockroach
122+
UsingPostColumns: ac.UsingPostColumns, //diff:ignore-line-postgres-cockroach
119123
}, //diff:ignore-line-postgres-cockroach
120124
) //diff:ignore-line-postgres-cockroach
121125
default: //diff:ignore-line-postgres-cockroach
@@ -150,11 +154,13 @@ func DiffCreateTable(before, after *CreateTableStmt, opts ...DiffCreateTableOpti
150154
case *IndexConstraint: //diff:ignore-line-postgres-cockroach
151155
// CREATE INDEX index_name ON table_name (column_name); //diff:ignore-line-postgres-cockroach
152156
result.Stmts = append(result.Stmts, &CreateIndexStmt{ //diff:ignore-line-postgres-cockroach
153-
Comment: simplediff.Diff("", ac.StringForDiff()).String(), //diff:ignore-line-postgres-cockroach
154-
Unique: ac.Unique, //diff:ignore-line-postgres-cockroach
155-
Name: ac.GetName(), //diff:ignore-line-postgres-cockroach
156-
TableName: after.Name, //diff:ignore-line-postgres-cockroach
157-
Columns: ac.Columns, //diff:ignore-line-postgres-cockroach
157+
Comment: simplediff.Diff("", ac.StringForDiff()).String(), //diff:ignore-line-postgres-cockroach
158+
Unique: ac.Unique, //diff:ignore-line-postgres-cockroach
159+
Name: ac.GetName(), //diff:ignore-line-postgres-cockroach
160+
TableName: after.Name, //diff:ignore-line-postgres-cockroach
161+
UsingPreColumns: ac.UsingPreColumns, //diff:ignore-line-postgres-cockroach
162+
Columns: ac.Columns, //diff:ignore-line-postgres-cockroach
163+
UsingPostColumns: ac.UsingPostColumns, //diff:ignore-line-postgres-cockroach
158164
}) //diff:ignore-line-postgres-cockroach
159165
default: //diff:ignore-line-postgres-cockroach
160166
// ALTER TABLE table_name ADD CONSTRAINT constraint_name constraint;
@@ -181,6 +187,11 @@ func (config *DiffCreateTableConfig) diffCreateTableColumn(ddls *DDL, before, af
181187
for _, beforeColumn := range before.Columns {
182188
afterColumn := findColumnByName(beforeColumn.Name.Name, after.Columns)
183189
if afterColumn == nil {
190+
if beforeColumn.NotVisible && beforeColumn.As != nil && beforeColumn.As.Type == TOKEN_VIRTUAL {
191+
// ref. https://www.cockroachlabs.com/docs/v24.2/hash-sharded-indexes
192+
logs.Debug.Printf("🪲: If the column is a NOT VISIBLE VIRTUAL column, it may be a Hash-sharded Index. SKIP. ref. https://www.cockroachlabs.com/docs/v24.2/hash-sharded-indexes")
193+
continue
194+
}
184195
// ALTER TABLE table_name DROP COLUMN column_name;
185196
ddls.Stmts = append(ddls.Stmts, &AlterTableStmt{
186197
Comment: simplediff.Diff(beforeColumn.String(), "").String(),

pkg/ddl/cockroachdb/diff_create_table_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,9 @@ ALTER TABLE "users" ADD CONSTRAINT users_group_id_fkey FOREIGN KEY (group_id, na
371371
afterDDL, err := NewParser(NewLexer(after)).Parse()
372372
require.NoError(t, err)
373373

374-
expectedStr := `DROP INDEX users_unique_name;
374+
expectedStr := `-- -UNIQUE INDEX users_unique_name ("name")
375+
-- +UNIQUE INDEX users_unique_name ("id" ASC, name ASC)
376+
DROP INDEX users_unique_name;
375377
CREATE UNIQUE INDEX users_unique_name ON "users" ("id" ASC, name ASC);
376378
`
377379

pkg/ddl/cockroachdb/diff_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,9 @@ ALTER TABLE public.users ADD COLUMN updated_at TIMESTAMP WITH TIME ZONE NOT NULL
360360
`)).Parse()
361361
require.NoError(t, err)
362362

363-
expected := `DROP INDEX users_idx_by_username;
363+
expected := `-- -INDEX users_idx_by_username (username DESC)
364+
-- +INDEX users_idx_by_username (username ASC)
365+
DROP INDEX users_idx_by_username;
364366
CREATE INDEX users_idx_by_username ON public.users (username ASC);
365367
`
366368
actual, err := Diff(before, after)

pkg/ddl/cockroachdb/lexar.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,15 @@ const (
105105
// COLUMN.
106106
TOKEN_DEFAULT TokenType = "DEFAULT"
107107
TOKEN_NOT TokenType = "NOT"
108+
TOKEN_VISIBLE TokenType = "VISIBLE"
109+
TOKEN_AS TokenType = "AS"
108110
TOKEN_ASC TokenType = "ASC"
109111
TOKEN_DESC TokenType = "DESC"
110112
TOKEN_CASCADE TokenType = "CASCADE"
111113
TOKEN_NO TokenType = "NO"
112114
TOKEN_ACTION TokenType = "ACTION"
115+
TOKEN_STORED TokenType = "STORED"
116+
TOKEN_VIRTUAL TokenType = "VIRTUAL"
113117

114118
// CONSTRAINT.
115119
TOKEN_CONSTRAINT TokenType = "CONSTRAINT"
@@ -228,6 +232,10 @@ func lookupIdent(ident string) TokenType {
228232
return TOKEN_DEFAULT
229233
case "NOT":
230234
return TOKEN_NOT
235+
case "VISIBLE":
236+
return TOKEN_VISIBLE
237+
case "AS":
238+
return TOKEN_AS
231239
case "ASC":
232240
return TOKEN_ASC
233241
case "DESC":
@@ -238,6 +246,10 @@ func lookupIdent(ident string) TokenType {
238246
return TOKEN_NO
239247
case "ACTION":
240248
return TOKEN_ACTION
249+
case "STORED":
250+
return TOKEN_STORED
251+
case "VIRTUAL":
252+
return TOKEN_VIRTUAL
241253
case "CONSTRAINT":
242254
return TOKEN_CONSTRAINT
243255
case "PRIMARY":

0 commit comments

Comments
 (0)