alter_table_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. package executor
  2. import (
  3. "errors"
  4. "strings"
  5. "sync"
  6. "testing"
  7. "github.com/danfragoso/pizzasql-next/pkg/storage"
  8. )
  9. // ADD COLUMN with a constant DEFAULT materializes the value into rows that
  10. // already exist, so the column reads the default everywhere.
  11. func TestAlterTableAddColumnBackfillsPopulatedTable(t *testing.T) {
  12. _, schema, table := newTestDB(t)
  13. e := newExec(schema, table)
  14. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  15. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  16. execMust(t, e, "ALTER TABLE t ADD COLUMN note TEXT DEFAULT 'later'")
  17. res := execMust(t, e, "SELECT id, note FROM t ORDER BY id")
  18. if res.RowCount != 2 {
  19. t.Fatalf("row count = %d, want 2", res.RowCount)
  20. }
  21. for _, row := range res.Rows {
  22. if row[1] != "later" {
  23. t.Fatalf("existing row %v note = %v, want later", row[0], row[1])
  24. }
  25. }
  26. execMust(t, e, "INSERT INTO t (id) VALUES (3)")
  27. res = execMust(t, e, "SELECT note FROM t WHERE id = 3")
  28. if got := res.Rows[0][0]; got != "later" {
  29. t.Fatalf("new row note = %v, want later", got)
  30. }
  31. }
  32. // A DEFAULT declared on an empty table has no rows to rewrite; later INSERTs
  33. // that omit the column take it, and an explicit NULL still wins.
  34. func TestAlterTableAddColumnDefaultOnEmptyTable(t *testing.T) {
  35. _, schema, table := newTestDB(t)
  36. e := newExec(schema, table)
  37. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  38. execMust(t, e, "ALTER TABLE t ADD COLUMN note TEXT DEFAULT 'later'")
  39. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  40. execMust(t, e, "INSERT INTO t (id, note) VALUES (2, NULL)")
  41. res := execMust(t, e, "SELECT id, note FROM t ORDER BY id")
  42. if len(res.Rows) != 2 || res.Rows[0][1] != "later" || res.Rows[1][1] != nil {
  43. t.Fatalf("rows = %v, want [[1 later] [2 NULL]]", res.Rows)
  44. }
  45. }
  46. // ADD COLUMN without a DEFAULT leaves existing rows NULL.
  47. func TestAlterTableAddColumnWithoutDefaultLeavesExistingRowsNull(t *testing.T) {
  48. _, schema, table := newTestDB(t)
  49. e := newExec(schema, table)
  50. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  51. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  52. execMust(t, e, "ALTER TABLE t ADD COLUMN note TEXT")
  53. res := execMust(t, e, "SELECT id, note FROM t ORDER BY id")
  54. if res.RowCount != 2 {
  55. t.Fatalf("row count = %d, want 2", res.RowCount)
  56. }
  57. for _, row := range res.Rows {
  58. if row[1] != nil {
  59. t.Fatalf("existing row %v note = %v, want NULL", row[0], row[1])
  60. }
  61. }
  62. }
  63. // A NOT NULL column with a constant DEFAULT backfills existing rows, so the
  64. // constraint holds for rows that predate the statement.
  65. func TestAlterTableAddColumnNotNullWithDefaultBackfills(t *testing.T) {
  66. _, schema, table := newTestDB(t)
  67. e := newExec(schema, table)
  68. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  69. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  70. execMust(t, e, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'")
  71. fresh := newExec(schema, table)
  72. res := execMust(t, fresh, "SELECT status FROM t ORDER BY id")
  73. if res.RowCount != 2 {
  74. t.Fatalf("row count = %d, want 2", res.RowCount)
  75. }
  76. for _, row := range res.Rows {
  77. if row[0] != "pending" {
  78. t.Fatalf("existing status = %v, want pending", row[0])
  79. }
  80. }
  81. execMust(t, e, "INSERT INTO t (id) VALUES (3)")
  82. res = execMust(t, e, "SELECT status FROM t WHERE id = 3")
  83. if got := res.Rows[0][0]; got != "pending" {
  84. t.Fatalf("new row status = %v, want pending", got)
  85. }
  86. }
  87. // A NOT NULL column without a DEFAULT has nothing to write into existing rows
  88. // and must not report the table as migrated.
  89. func TestAlterTableAddColumnNotNullWithoutDefaultRejectsPopulatedTable(t *testing.T) {
  90. _, schema, table := newTestDB(t)
  91. e := newExec(schema, table)
  92. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  93. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  94. _, err := execSQL(e, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL")
  95. var violation *storage.NotNullViolationError
  96. if !errors.As(err, &violation) {
  97. t.Fatalf("error = %v, want NotNullViolationError", err)
  98. }
  99. s, err := schema.GetSchema("t")
  100. if err != nil {
  101. t.Fatalf("get schema: %v", err)
  102. }
  103. if _, exists := s.GetColumn("status"); exists {
  104. t.Fatal("rejected ADD COLUMN left the column in the schema")
  105. }
  106. if rows := execMust(t, e, "SELECT id FROM t").Rows; len(rows) != 1 {
  107. t.Fatalf("rows = %v, want the existing row untouched", rows)
  108. }
  109. }
  110. // The same statement is legal on an empty table; the constraint is then
  111. // enforced on the INSERTs that follow.
  112. func TestAlterTableAddColumnNotNullWithoutDefaultOnEmptyTable(t *testing.T) {
  113. _, schema, table := newTestDB(t)
  114. e := newExec(schema, table)
  115. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  116. execMust(t, e, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL")
  117. _, err := execSQL(e, "INSERT INTO t (id) VALUES (1)")
  118. var violation *storage.NotNullViolationError
  119. if !errors.As(err, &violation) {
  120. t.Fatalf("insert without status error = %v, want NotNullViolationError", err)
  121. }
  122. execMust(t, e, "INSERT INTO t (id, status) VALUES (1, 'active')")
  123. res := execMust(t, e, "SELECT status FROM t WHERE id = 1")
  124. if got := res.Rows[0][0]; got != "active" {
  125. t.Fatalf("status = %v, want active", got)
  126. }
  127. }
  128. // Backfilling one constant into several rows would duplicate the value, so the
  129. // UNIQUE constraint must reject the ALTER instead of creating a silently
  130. // non-unique column.
  131. func TestAlterTableAddColumnUniqueWithDefaultRejectsPopulatedTable(t *testing.T) {
  132. _, schema, table := newTestDB(t)
  133. e := newExec(schema, table)
  134. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  135. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  136. _, err := execSQL(e, "ALTER TABLE t ADD COLUMN code TEXT DEFAULT 'same' UNIQUE")
  137. var violation *storage.UniqueViolationError
  138. if !errors.As(err, &violation) {
  139. t.Fatalf("error = %v, want UniqueViolationError", err)
  140. }
  141. s, err := schema.GetSchema("t")
  142. if err != nil {
  143. t.Fatalf("get schema: %v", err)
  144. }
  145. if _, exists := s.GetColumn("code"); exists {
  146. t.Fatal("rejected ADD COLUMN left the column in the schema")
  147. }
  148. indexes, err := schema.ListTableIndexes("t")
  149. if err != nil {
  150. t.Fatalf("list indexes: %v", err)
  151. }
  152. if len(indexes) != 0 {
  153. t.Fatalf("indexes = %v, want none", indexes)
  154. }
  155. }
  156. // A single-row table can take a UNIQUE column with a default, and the
  157. // constraint is enforced for every later INSERT that omits the column. The
  158. // column and the index that validates it must be visible together.
  159. func TestAlterTableAddColumnUniqueWithDefaultEnforcesLaterInserts(t *testing.T) {
  160. _, schema, table := newTestDB(t)
  161. e := newExec(schema, table)
  162. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  163. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  164. execMust(t, e, "ALTER TABLE t ADD COLUMN code TEXT DEFAULT 'same' UNIQUE")
  165. s, err := schema.GetSchema("t")
  166. if err != nil {
  167. t.Fatalf("get schema: %v", err)
  168. }
  169. if _, exists := s.GetColumn("code"); !exists {
  170. t.Fatal("ADD COLUMN ... UNIQUE published no column")
  171. }
  172. indexes, err := schema.ListTableIndexes("t")
  173. if err != nil {
  174. t.Fatalf("list indexes: %v", err)
  175. }
  176. if len(indexes) != 1 {
  177. t.Fatalf("indexes = %v, want the one unique index for column code", indexes)
  178. }
  179. if !indexes[0].Unique || len(indexes[0].Columns) != 1 || indexes[0].Columns[0].Name != "code" {
  180. t.Fatalf("index = %+v, want a unique index over code", indexes[0])
  181. }
  182. _, err = execSQL(e, "INSERT INTO t (id) VALUES (2)")
  183. var violation *storage.UniqueViolationError
  184. if !errors.As(err, &violation) {
  185. t.Fatalf("insert with duplicated default error = %v, want UniqueViolationError", err)
  186. }
  187. execMust(t, e, "INSERT INTO t (id, code) VALUES (2, 'other')")
  188. res := execMust(t, e, "SELECT code FROM t ORDER BY id")
  189. if len(res.Rows) != 2 || res.Rows[0][0] != "same" || res.Rows[1][0] != "other" {
  190. t.Fatalf("codes = %v, want [same other]", res.Rows)
  191. }
  192. }
  193. // A transaction can stage a row image before a concurrent ADD COLUMN publishes
  194. // a required column. The commit must re-validate that row against the schema now
  195. // in effect instead of persisting a durable NOT NULL violation.
  196. func TestAlterTableAddColumnRejectsRowStagedBeforeDDL(t *testing.T) {
  197. _, schema, table := newTestDB(t)
  198. writer := newExec(schema, table)
  199. ddl := newExec(schema, table)
  200. execMust(t, writer, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  201. execMust(t, writer, "BEGIN")
  202. execMust(t, writer, "INSERT INTO t (id) VALUES (1)")
  203. // The table is durably empty, so the ALTER accepts the required column.
  204. execMust(t, ddl, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL")
  205. _, err := execSQL(writer, "COMMIT")
  206. var violation *storage.NotNullViolationError
  207. if !errors.As(err, &violation) {
  208. t.Fatalf("commit error = %v, want NotNullViolationError", err)
  209. }
  210. if got := execMust(t, ddl, "SELECT id FROM t").RowCount; got != 0 {
  211. t.Fatalf("rows after rejected commit = %d, want 0", got)
  212. }
  213. }
  214. // The same race is harmless when the new column has a default: the commit
  215. // materializes it into the staged row, exactly as the backfill did for rows that
  216. // were already durable.
  217. func TestAlterTableAddColumnDefaultsRowStagedBeforeDDL(t *testing.T) {
  218. _, schema, table := newTestDB(t)
  219. writer := newExec(schema, table)
  220. ddl := newExec(schema, table)
  221. execMust(t, writer, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  222. execMust(t, writer, "BEGIN")
  223. execMust(t, writer, "INSERT INTO t (id) VALUES (1)")
  224. execMust(t, ddl, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'")
  225. execMust(t, writer, "COMMIT")
  226. res := execMust(t, ddl, "SELECT id, status FROM t")
  227. if len(res.Rows) != 1 || res.Rows[0][1] != "pending" {
  228. t.Fatalf("rows = %v, want [[1 pending]]", res.Rows)
  229. }
  230. }
  231. // IF NOT EXISTS stays idempotent on a populated table: the second statement is
  232. // a no-op even when it declares a different default.
  233. func TestAlterTableAddColumnIfNotExistsKeepsBackfilledValues(t *testing.T) {
  234. _, schema, table := newTestDB(t)
  235. e := newExec(schema, table)
  236. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  237. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  238. execMust(t, e, "ALTER TABLE t ADD COLUMN IF NOT EXISTS note TEXT DEFAULT 'first'")
  239. execMust(t, e, "ALTER TABLE t ADD COLUMN IF NOT EXISTS note TEXT DEFAULT 'second'")
  240. res := execMust(t, e, "SELECT note FROM t WHERE id = 1")
  241. if got := res.Rows[0][0]; got != "first" {
  242. t.Fatalf("note = %v, want the first default to stand", got)
  243. }
  244. s, err := schema.GetSchema("t")
  245. if err != nil {
  246. t.Fatalf("get schema: %v", err)
  247. }
  248. count := 0
  249. for _, col := range s.Columns {
  250. if col.Name == "note" {
  251. count++
  252. }
  253. }
  254. if count != 1 {
  255. t.Fatalf("column note appears %d times, want 1", count)
  256. }
  257. }
  258. func TestAlterTableAddColumnIfNotExistsIsIdempotent(t *testing.T) {
  259. _, schema, table := newTestDB(t)
  260. e := newExec(schema, table)
  261. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  262. execMust(t, e, "ALTER TABLE t ADD COLUMN IF NOT EXISTS c TEXT")
  263. execMust(t, e, "ALTER TABLE t ADD COLUMN IF NOT EXISTS c TEXT")
  264. s, err := schema.GetSchema("t")
  265. if err != nil {
  266. t.Fatalf("get schema: %v", err)
  267. }
  268. count := 0
  269. for _, col := range s.Columns {
  270. if col.Name == "c" {
  271. count++
  272. }
  273. }
  274. if count != 1 {
  275. t.Fatalf("column c appears %d times, want 1", count)
  276. }
  277. }
  278. // Concurrent IF NOT EXISTS ADD COLUMN on one engine instance must not duplicate
  279. // metadata; the schema manager serializes the check-and-append.
  280. func TestAlterTableAddColumnIfNotExistsConcurrent(t *testing.T) {
  281. _, schema, table := newTestDB(t)
  282. e := newExec(schema, table)
  283. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  284. var wg sync.WaitGroup
  285. for i := 0; i < 8; i++ {
  286. wg.Add(1)
  287. go func() {
  288. defer wg.Done()
  289. session := newExec(schema, table)
  290. if _, err := execSQL(session, "ALTER TABLE t ADD COLUMN IF NOT EXISTS c TEXT"); err != nil {
  291. t.Errorf("add column: %v", err)
  292. }
  293. }()
  294. }
  295. wg.Wait()
  296. s, err := schema.GetSchema("t")
  297. if err != nil {
  298. t.Fatalf("get schema: %v", err)
  299. }
  300. count := 0
  301. for _, col := range s.Columns {
  302. if col.Name == "c" {
  303. count++
  304. }
  305. }
  306. if count != 1 {
  307. t.Fatalf("column c appears %d times, want 1", count)
  308. }
  309. }
  310. // DROP COLUMN rewrites every remaining row so the removed value does not stay
  311. // in durable storage.
  312. func TestAlterTableDropColumnRewritesPopulatedRows(t *testing.T) {
  313. _, schema, table := newTestDB(t)
  314. e := newExec(schema, table)
  315. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, keep TEXT, dropme TEXT)")
  316. execMust(t, e, "INSERT INTO t (id, keep, dropme) VALUES (1, 'a', 'secret'), (2, 'b', 'secret')")
  317. execMust(t, e, "ALTER TABLE t DROP COLUMN dropme")
  318. res := execMust(t, e, "SELECT * FROM t ORDER BY id")
  319. if len(res.Columns) != 2 || res.Columns[0] != "id" || res.Columns[1] != "keep" {
  320. t.Fatalf("columns after drop = %v, want [id keep]", res.Columns)
  321. }
  322. if res.RowCount != 2 || res.Rows[0][1] != "a" || res.Rows[1][1] != "b" {
  323. t.Fatalf("rows after drop = %v", res.Rows)
  324. }
  325. if _, err := execSQL(e, "SELECT dropme FROM t"); err == nil {
  326. t.Fatal("expected dropped column to be unknown")
  327. }
  328. }
  329. // RENAME COLUMN rewrites the durable index definitions along with the schema
  330. // and rows, so single- and multi-column indexes name the new column while
  331. // keeping their original index names.
  332. func TestAlterTableRenameColumnUpdatesIndexMetadata(t *testing.T) {
  333. pool, schema, table := newTestDB(t)
  334. e := newExec(schema, table)
  335. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT)")
  336. execMust(t, e, "INSERT INTO t (id, a, b) VALUES (1, 'x', 'y')")
  337. execMust(t, e, "CREATE INDEX idx_a ON t(a)")
  338. execMust(t, e, "CREATE INDEX idx_ab ON t(a, b)")
  339. execMust(t, e, "ALTER TABLE t RENAME COLUMN a TO aa")
  340. single, err := schema.GetIndex("idx_a")
  341. if err != nil {
  342. t.Fatalf("get idx_a: %v", err)
  343. }
  344. if len(single.Columns) != 1 || single.Columns[0].Name != "aa" {
  345. t.Fatalf("idx_a columns = %#v, want [aa]", single.Columns)
  346. }
  347. composite, err := schema.GetIndex("idx_ab")
  348. if err != nil {
  349. t.Fatalf("get idx_ab: %v", err)
  350. }
  351. if len(composite.Columns) != 2 || composite.Columns[0].Name != "aa" || composite.Columns[1].Name != "b" {
  352. t.Fatalf("idx_ab columns = %#v, want [aa b]", composite.Columns)
  353. }
  354. // index_info reports the renamed column and resolves its column id.
  355. res := execMust(t, e, "PRAGMA index_info(idx_a)")
  356. if len(res.Rows) != 1 || res.Rows[0][1] != int64(1) || res.Rows[0][2] != "aa" {
  357. t.Fatalf("index_info(idx_a) = %v, want [[0 1 aa]]", res.Rows)
  358. }
  359. // sqlite_master renders the recreated index over the new column name.
  360. res = execMust(t, e, "SELECT sql FROM sqlite_master WHERE type='index' AND name='idx_ab'")
  361. if len(res.Rows) != 1 || !strings.Contains(res.Rows[0][0].(string), "(`aa`, `b`)") {
  362. t.Fatalf("recreated idx_ab SQL = %v, want a definition over aa, b", res.Rows)
  363. }
  364. // The definition is durable, not just cached on this SchemaManager.
  365. reloaded := storage.NewSchemaManager(pool, "testdb")
  366. composite, err = reloaded.GetIndex("idx_ab")
  367. if err != nil {
  368. t.Fatalf("reload idx_ab: %v", err)
  369. }
  370. if len(composite.Columns) != 2 || composite.Columns[0].Name != "aa" || composite.Columns[1].Name != "b" {
  371. t.Fatalf("reloaded idx_ab columns = %#v, want [aa b]", composite.Columns)
  372. }
  373. // Index-backed equality lookup still returns the row after the rename.
  374. res = execMust(t, e, "SELECT id, aa FROM t WHERE aa = 'x'")
  375. if res.RowCount != 1 || res.Rows[0][0] != int64(1) || res.Rows[0][1] != "x" {
  376. t.Fatalf("indexed lookup after rename = %v, want [[1 x]]", res.Rows)
  377. }
  378. }
  379. // A UNIQUE index keeps enforcing uniqueness after its column is renamed.
  380. func TestAlterTableRenameColumnKeepsUniqueIndexEnforcement(t *testing.T) {
  381. _, schema, table := newTestDB(t)
  382. e := newExec(schema, table)
  383. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT, tag TEXT)")
  384. execMust(t, e, "CREATE UNIQUE INDEX uq_email ON t(email)")
  385. execMust(t, e, "INSERT INTO t (id, email, tag) VALUES (1, 'a', 'first')")
  386. if _, err := execSQL(e, "INSERT INTO t (id, email, tag) VALUES (2, 'a', 'dup')"); err == nil || !strings.Contains(err.Error(), "UNIQUE constraint failed") {
  387. t.Fatalf("pre-rename duplicate error = %v, want UNIQUE constraint failure", err)
  388. }
  389. execMust(t, e, "ALTER TABLE t RENAME COLUMN email TO mail")
  390. if _, err := execSQL(e, "INSERT INTO t (id, mail, tag) VALUES (3, 'a', 'dup')"); err == nil || !strings.Contains(err.Error(), "UNIQUE constraint failed") {
  391. t.Fatalf("post-rename duplicate error = %v, want UNIQUE constraint failure", err)
  392. }
  393. execMust(t, e, "INSERT INTO t (id, mail, tag) VALUES (4, 'b', 'ok')")
  394. res := execMust(t, e, "SELECT id, mail, tag FROM t ORDER BY id")
  395. if res.RowCount != 2 || res.Rows[0][1] != "a" || res.Rows[1][1] != "b" {
  396. t.Fatalf("rows after rename = %v, want two distinct emails", res.Rows)
  397. }
  398. res = execMust(t, e, "PRAGMA index_list(t)")
  399. if len(res.Rows) != 1 || res.Rows[0][1] != "uq_email" || res.Rows[0][2] != int64(1) {
  400. t.Fatalf("index_list(t) = %v, want the unique uq_email index", res.Rows)
  401. }
  402. }
  403. // ON CONFLICT resolves against the renamed column and rejects the old name.
  404. func TestAlterTableRenameColumnResolvesOnConflictTarget(t *testing.T) {
  405. _, schema, table := newTestDB(t)
  406. e := newExec(schema, table)
  407. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT, tag TEXT)")
  408. execMust(t, e, "CREATE UNIQUE INDEX uq_email ON t(email)")
  409. execMust(t, e, "INSERT INTO t (id, email, tag) VALUES (1, 'a', 'old')")
  410. execMust(t, e, "ALTER TABLE t RENAME COLUMN email TO mail")
  411. execMust(t, e, "INSERT INTO t (id, mail, tag) VALUES (2, 'a', 'new') ON CONFLICT (mail) DO UPDATE SET tag = excluded.tag")
  412. res := execMust(t, e, "SELECT id, mail, tag FROM t")
  413. if res.RowCount != 1 || res.Rows[0][0] != int64(1) || res.Rows[0][2] != "new" {
  414. t.Fatalf("upsert on renamed column = %v, want the existing row updated", res.Rows)
  415. }
  416. if _, err := execSQL(e, "INSERT INTO t (id, mail, tag) VALUES (3, 'a', 'x') ON CONFLICT (email) DO UPDATE SET tag = excluded.tag"); err == nil || !strings.Contains(err.Error(), "column not found: email") {
  417. t.Fatalf("upsert on old column error = %v, want column not found: email", err)
  418. }
  419. }
  420. // Renaming a column back restores the original index metadata, and renaming to
  421. // an existing column name still fails without touching that metadata.
  422. func TestAlterTableRenameColumnCycleRestoresIndexMetadata(t *testing.T) {
  423. _, schema, table := newTestDB(t)
  424. e := newExec(schema, table)
  425. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT)")
  426. execMust(t, e, "CREATE UNIQUE INDEX uq_a ON t(a)")
  427. execMust(t, e, "INSERT INTO t (id, a) VALUES (1, 'x')")
  428. execMust(t, e, "ALTER TABLE t RENAME COLUMN a TO b")
  429. execMust(t, e, "ALTER TABLE t RENAME COLUMN b TO a")
  430. idx, err := schema.GetIndex("uq_a")
  431. if err != nil {
  432. t.Fatalf("get uq_a: %v", err)
  433. }
  434. if len(idx.Columns) != 1 || idx.Columns[0].Name != "a" {
  435. t.Fatalf("uq_a columns after cycle = %#v, want [a]", idx.Columns)
  436. }
  437. if _, err := execSQL(e, "INSERT INTO t (id, a) VALUES (2, 'x')"); err == nil || !strings.Contains(err.Error(), "UNIQUE constraint failed") {
  438. t.Fatalf("duplicate error after rename cycle = %v, want UNIQUE constraint failure", err)
  439. }
  440. if _, err := execSQL(e, "ALTER TABLE t RENAME COLUMN a TO id"); err == nil || !strings.Contains(err.Error(), "column already exists: id") {
  441. t.Fatalf("rename to existing column error = %v, want column already exists: id", err)
  442. }
  443. idx, err = schema.GetIndex("uq_a")
  444. if err != nil {
  445. t.Fatalf("get uq_a after rejected rename: %v", err)
  446. }
  447. if len(idx.Columns) != 1 || idx.Columns[0].Name != "a" {
  448. t.Fatalf("uq_a columns after rejected rename = %#v, want [a]", idx.Columns)
  449. }
  450. }
  451. // A column referenced by an expression index cannot be renamed: the engine
  452. // stores expression text verbatim, so the rename is rejected instead of leaving
  453. // a UNIQUE expression index silently unbound from the column it enforces.
  454. func TestAlterTableRenameColumnRejectsExpressionIndexDependency(t *testing.T) {
  455. _, schema, table := newTestDB(t)
  456. e := newExec(schema, table)
  457. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT, tag TEXT)")
  458. execMust(t, e, "CREATE UNIQUE INDEX uq_lower_email ON t(lower(email))")
  459. execMust(t, e, "INSERT INTO t (id, email, tag) VALUES (1, 'a@x', 'keep')")
  460. _, err := execSQL(e, "ALTER TABLE t RENAME COLUMN email TO mail")
  461. if err == nil {
  462. t.Fatal("rename of a column used by an expression index must be rejected")
  463. }
  464. for _, want := range []string{"uq_lower_email", "lower(email)"} {
  465. if !strings.Contains(err.Error(), want) {
  466. t.Fatalf("rename error = %v, want it to name %q", err, want)
  467. }
  468. }
  469. // Nothing was renamed, and the UNIQUE expression index still enforces.
  470. sch, err := schema.GetSchema("t")
  471. if err != nil {
  472. t.Fatalf("get schema: %v", err)
  473. }
  474. if _, ok := sch.GetColumn("email"); !ok {
  475. t.Fatal("rejected rename removed the email column")
  476. }
  477. if _, ok := sch.GetColumn("mail"); ok {
  478. t.Fatal("rejected rename added the mail column")
  479. }
  480. if _, err := execSQL(e, "INSERT INTO t (id, email, tag) VALUES (2, 'a@x', 'dup')"); err == nil || !strings.Contains(err.Error(), "UNIQUE constraint failed") {
  481. t.Fatalf("duplicate on expression index after rejected rename = %v, want UNIQUE constraint failure", err)
  482. }
  483. // A column the expression does not reference can still be renamed.
  484. execMust(t, e, "ALTER TABLE t RENAME COLUMN tag TO label")
  485. idx, err := schema.GetIndex("uq_lower_email")
  486. if err != nil {
  487. t.Fatalf("get uq_lower_email: %v", err)
  488. }
  489. if len(idx.Columns) != 1 || idx.Columns[0].Expression != "lower(email)" {
  490. t.Fatalf("uq_lower_email columns = %#v, want the untouched lower(email) expression", idx.Columns)
  491. }
  492. if res := execMust(t, e, "SELECT label FROM t WHERE id = 1"); res.RowCount != 1 || res.Rows[0][0] != "keep" {
  493. t.Fatalf("renamed label = %v, want keep", res.Rows)
  494. }
  495. }
  496. // Conflict targets that name a hidden rowid alias are not table columns but are
  497. // still valid targets, including after a rename.
  498. func TestAlterTableRenameColumnKeepsRowIDConflictTarget(t *testing.T) {
  499. _, schema, table := newTestDB(t)
  500. e := newExec(schema, table)
  501. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT)")
  502. execMust(t, e, "INSERT INTO t (id, a) VALUES (1, 'x')")
  503. execMust(t, e, "ALTER TABLE t RENAME COLUMN a TO b")
  504. execMust(t, e, "INSERT INTO t (id, b) VALUES (2, 'y') ON CONFLICT (rowid) DO NOTHING")
  505. execMust(t, e, "INSERT INTO t (id, b) VALUES (3, 'z') ON CONFLICT (oid) DO NOTHING")
  506. execMust(t, e, "INSERT INTO t (id, b) VALUES (4, 'w') ON CONFLICT (_rowid_) DO NOTHING")
  507. if res := execMust(t, e, "SELECT count(*) FROM t"); res.Rows[0][0] != int64(4) {
  508. t.Fatalf("row count = %v, want 4", res.Rows[0][0])
  509. }
  510. }