-
Notifications
You must be signed in to change notification settings - Fork 13
/
alter.go
149 lines (127 loc) · 5.25 KB
/
alter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package sqlite3
import (
"context"
"fmt"
"strings"
"github.com/neilotoole/sq/drivers/sqlite3/internal/sqlparser"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/core/kind"
"github.com/neilotoole/sq/libsq/core/lg"
"github.com/neilotoole/sq/libsq/core/lg/lga"
"github.com/neilotoole/sq/libsq/core/sqlz"
"github.com/neilotoole/sq/libsq/core/stringz"
)
// AlterTableRename implements driver.SQLDriver.
func (d *driveri) AlterTableRename(ctx context.Context, db sqlz.DB, tbl, newName string) error {
q := fmt.Sprintf(`ALTER TABLE %q RENAME TO %q`, tbl, newName)
_, err := db.ExecContext(ctx, q)
return errz.Wrapf(errw(err), "alter table: failed to rename table {%s} to {%s}", tbl, newName)
}
// AlterTableRenameColumn implements driver.SQLDriver.
func (d *driveri) AlterTableRenameColumn(ctx context.Context, db sqlz.DB, tbl, col, newName string) error {
q := fmt.Sprintf("ALTER TABLE %q RENAME COLUMN %q TO %q", tbl, col, newName)
_, err := db.ExecContext(ctx, q)
return errz.Wrapf(errw(err), "alter table: failed to rename column {%s.%s} to {%s}", tbl, col, newName)
}
// AlterTableAddColumn implements driver.SQLDriver.
func (d *driveri) AlterTableAddColumn(ctx context.Context, db sqlz.DB, tbl, col string, knd kind.Kind) error {
q := fmt.Sprintf("ALTER TABLE %q ADD COLUMN %q ", tbl, col) + DBTypeForKind(knd)
_, err := db.ExecContext(ctx, q)
if err != nil {
return errz.Wrapf(errw(err), "alter table: failed to add column {%s} to table {%s}", col, tbl)
}
return nil
}
// AlterTableColumnKinds implements driver.SQLDriver. Note that SQLite doesn't
// really support altering column types, so this is a hacky implementation.
// It's not guaranteed that indices, constraints, etc. will be preserved. See:
//
// - https://www.sqlite.org/lang_altertable.html
// - https://www.sqlite.org/faq.html#q11
// - https://www.sqlitetutorial.net/sqlite-alter-table/
//
// Note that colNames and kinds must be the same length.
func (d *driveri) AlterTableColumnKinds(ctx context.Context, db sqlz.DB,
tblName string, colNames []string, kinds []kind.Kind,
) (err error) {
if len(colNames) != len(kinds) {
return errz.New("sqlite3: alter table: mismatched count of columns and kinds")
}
// It's recommended to disable foreign keys before this alter procedure.
if restorePragmaFK, fkErr := pragmaDisableForeignKeys(ctx, db); fkErr != nil {
return fkErr
} else if restorePragmaFK != nil {
defer restorePragmaFK()
}
q := "SELECT sql FROM sqlite_master WHERE type='table' AND name=?"
var ogDDL string
if err = db.QueryRowContext(ctx, q, tblName).Scan(&ogDDL); err != nil {
return errz.Wrapf(err, "sqlite3: alter table: failed to read original DDL")
}
allColDefs, err := sqlparser.ExtractCreateTableStmtColDefs(ogDDL)
if err != nil {
return errz.Wrapf(err, "sqlite3: alter table: failed to extract column definitions from DDL")
}
var colDefs []*sqlparser.ColDef
for i, colName := range colNames {
for _, cd := range allColDefs {
if cd.Name == colName {
colDefs = append(colDefs, cd)
break
}
}
if len(colDefs) != i+1 {
return errz.Errorf("sqlite3: alter table: column {%s} not found in table DDL", colName)
}
}
nuDDL := ogDDL
for i, colDef := range colDefs {
wantType := DBTypeForKind(kinds[i])
wantColDefText := strings.Replace(colDef.Raw, colDef.RawType, wantType, 1)
nuDDL = strings.Replace(nuDDL, colDef.Raw, wantColDefText, 1)
}
nuTblName := "tmp_tbl_alter_" + stringz.Uniq32()
nuDDL = strings.Replace(nuDDL, tblName, nuTblName, 1)
if _, err = db.ExecContext(ctx, nuDDL); err != nil {
return errz.Wrapf(err, "sqlite3: alter table: failed to create temporary table")
}
copyStmt := fmt.Sprintf(
"INSERT INTO %s SELECT * FROM %s",
stringz.DoubleQuote(nuTblName),
stringz.DoubleQuote(tblName),
)
if _, err = db.ExecContext(ctx, copyStmt); err != nil {
return errz.Wrapf(err, "sqlite3: alter table: failed to copy data to temporary table")
}
// Drop old table
if _, err = db.ExecContext(ctx, "DROP TABLE "+stringz.DoubleQuote(tblName)); err != nil {
return errz.Wrapf(err, "sqlite3: alter table: failed to drop original table")
}
// Rename new table to old table name
if _, err = db.ExecContext(ctx, fmt.Sprintf(
"ALTER TABLE %s RENAME TO %s",
stringz.DoubleQuote(nuTblName),
stringz.DoubleQuote(tblName),
)); err != nil {
return errz.Wrapf(err, "sqlite3: alter table: failed to rename temporary table")
}
return nil
}
// pragmaDisableForeignKeys disables foreign keys, returning a function that
// restores the original state of the foreign_keys pragma. If an error occurs,
// the returned restore function will be nil.
func pragmaDisableForeignKeys(ctx context.Context, db sqlz.DB) (restore func(), err error) {
pragmaFkExisting, err := readPragma(ctx, db, "foreign_keys")
if err != nil {
return nil, errz.Wrapf(err, "sqlite3: alter table: failed to read foreign_keys pragma")
}
if _, err = db.ExecContext(ctx, "PRAGMA foreign_keys=off"); err != nil {
return nil, errz.Wrapf(err, "sqlite3: alter table: failed to disable foreign_keys pragma")
}
return func() {
_, restoreErr := db.ExecContext(ctx, fmt.Sprintf("PRAGMA foreign_keys=%v", pragmaFkExisting))
if restoreErr != nil {
lg.FromContext(ctx).Error("sqlite3: alter table: failed to restore foreign_keys pragma", lga.Err, restoreErr)
}
}, nil
}