2
0

alter_table_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package executor
  2. import (
  3. "errors"
  4. "sync"
  5. "testing"
  6. "github.com/danfragoso/pizzasql-next/pkg/storage"
  7. )
  8. // ADD COLUMN with a constant DEFAULT materializes the value into rows that
  9. // already exist, so the column reads the default everywhere.
  10. func TestAlterTableAddColumnBackfillsPopulatedTable(t *testing.T) {
  11. _, schema, table := newTestDB(t)
  12. e := newExec(schema, table)
  13. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  14. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  15. execMust(t, e, "ALTER TABLE t ADD COLUMN note TEXT DEFAULT 'later'")
  16. res := execMust(t, e, "SELECT id, note FROM t ORDER BY id")
  17. if res.RowCount != 2 {
  18. t.Fatalf("row count = %d, want 2", res.RowCount)
  19. }
  20. for _, row := range res.Rows {
  21. if row[1] != "later" {
  22. t.Fatalf("existing row %v note = %v, want later", row[0], row[1])
  23. }
  24. }
  25. execMust(t, e, "INSERT INTO t (id) VALUES (3)")
  26. res = execMust(t, e, "SELECT note FROM t WHERE id = 3")
  27. if got := res.Rows[0][0]; got != "later" {
  28. t.Fatalf("new row note = %v, want later", got)
  29. }
  30. }
  31. // A DEFAULT declared on an empty table has no rows to rewrite; later INSERTs
  32. // that omit the column take it, and an explicit NULL still wins.
  33. func TestAlterTableAddColumnDefaultOnEmptyTable(t *testing.T) {
  34. _, schema, table := newTestDB(t)
  35. e := newExec(schema, table)
  36. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  37. execMust(t, e, "ALTER TABLE t ADD COLUMN note TEXT DEFAULT 'later'")
  38. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  39. execMust(t, e, "INSERT INTO t (id, note) VALUES (2, NULL)")
  40. res := execMust(t, e, "SELECT id, note FROM t ORDER BY id")
  41. if len(res.Rows) != 2 || res.Rows[0][1] != "later" || res.Rows[1][1] != nil {
  42. t.Fatalf("rows = %v, want [[1 later] [2 NULL]]", res.Rows)
  43. }
  44. }
  45. // ADD COLUMN without a DEFAULT leaves existing rows NULL.
  46. func TestAlterTableAddColumnWithoutDefaultLeavesExistingRowsNull(t *testing.T) {
  47. _, schema, table := newTestDB(t)
  48. e := newExec(schema, table)
  49. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  50. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  51. execMust(t, e, "ALTER TABLE t ADD COLUMN note TEXT")
  52. res := execMust(t, e, "SELECT id, note FROM t ORDER BY id")
  53. if res.RowCount != 2 {
  54. t.Fatalf("row count = %d, want 2", res.RowCount)
  55. }
  56. for _, row := range res.Rows {
  57. if row[1] != nil {
  58. t.Fatalf("existing row %v note = %v, want NULL", row[0], row[1])
  59. }
  60. }
  61. }
  62. // A NOT NULL column with a constant DEFAULT backfills existing rows, so the
  63. // constraint holds for rows that predate the statement.
  64. func TestAlterTableAddColumnNotNullWithDefaultBackfills(t *testing.T) {
  65. _, schema, table := newTestDB(t)
  66. e := newExec(schema, table)
  67. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  68. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  69. execMust(t, e, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'")
  70. fresh := newExec(schema, table)
  71. res := execMust(t, fresh, "SELECT status FROM t ORDER BY id")
  72. if res.RowCount != 2 {
  73. t.Fatalf("row count = %d, want 2", res.RowCount)
  74. }
  75. for _, row := range res.Rows {
  76. if row[0] != "pending" {
  77. t.Fatalf("existing status = %v, want pending", row[0])
  78. }
  79. }
  80. execMust(t, e, "INSERT INTO t (id) VALUES (3)")
  81. res = execMust(t, e, "SELECT status FROM t WHERE id = 3")
  82. if got := res.Rows[0][0]; got != "pending" {
  83. t.Fatalf("new row status = %v, want pending", got)
  84. }
  85. }
  86. // A NOT NULL column without a DEFAULT has nothing to write into existing rows
  87. // and must not report the table as migrated.
  88. func TestAlterTableAddColumnNotNullWithoutDefaultRejectsPopulatedTable(t *testing.T) {
  89. _, schema, table := newTestDB(t)
  90. e := newExec(schema, table)
  91. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  92. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  93. _, err := execSQL(e, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL")
  94. var violation *storage.NotNullViolationError
  95. if !errors.As(err, &violation) {
  96. t.Fatalf("error = %v, want NotNullViolationError", err)
  97. }
  98. s, err := schema.GetSchema("t")
  99. if err != nil {
  100. t.Fatalf("get schema: %v", err)
  101. }
  102. if _, exists := s.GetColumn("status"); exists {
  103. t.Fatal("rejected ADD COLUMN left the column in the schema")
  104. }
  105. if rows := execMust(t, e, "SELECT id FROM t").Rows; len(rows) != 1 {
  106. t.Fatalf("rows = %v, want the existing row untouched", rows)
  107. }
  108. }
  109. // The same statement is legal on an empty table; the constraint is then
  110. // enforced on the INSERTs that follow.
  111. func TestAlterTableAddColumnNotNullWithoutDefaultOnEmptyTable(t *testing.T) {
  112. _, schema, table := newTestDB(t)
  113. e := newExec(schema, table)
  114. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  115. execMust(t, e, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL")
  116. _, err := execSQL(e, "INSERT INTO t (id) VALUES (1)")
  117. var violation *storage.NotNullViolationError
  118. if !errors.As(err, &violation) {
  119. t.Fatalf("insert without status error = %v, want NotNullViolationError", err)
  120. }
  121. execMust(t, e, "INSERT INTO t (id, status) VALUES (1, 'active')")
  122. res := execMust(t, e, "SELECT status FROM t WHERE id = 1")
  123. if got := res.Rows[0][0]; got != "active" {
  124. t.Fatalf("status = %v, want active", got)
  125. }
  126. }
  127. // Backfilling one constant into several rows would duplicate the value, so the
  128. // UNIQUE constraint must reject the ALTER instead of creating a silently
  129. // non-unique column.
  130. func TestAlterTableAddColumnUniqueWithDefaultRejectsPopulatedTable(t *testing.T) {
  131. _, schema, table := newTestDB(t)
  132. e := newExec(schema, table)
  133. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  134. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  135. _, err := execSQL(e, "ALTER TABLE t ADD COLUMN code TEXT DEFAULT 'same' UNIQUE")
  136. var violation *storage.UniqueViolationError
  137. if !errors.As(err, &violation) {
  138. t.Fatalf("error = %v, want UniqueViolationError", err)
  139. }
  140. s, err := schema.GetSchema("t")
  141. if err != nil {
  142. t.Fatalf("get schema: %v", err)
  143. }
  144. if _, exists := s.GetColumn("code"); exists {
  145. t.Fatal("rejected ADD COLUMN left the column in the schema")
  146. }
  147. indexes, err := schema.ListTableIndexes("t")
  148. if err != nil {
  149. t.Fatalf("list indexes: %v", err)
  150. }
  151. if len(indexes) != 0 {
  152. t.Fatalf("indexes = %v, want none", indexes)
  153. }
  154. }
  155. // A single-row table can take a UNIQUE column with a default, and the
  156. // constraint is enforced for every later INSERT that omits the column. The
  157. // column and the index that validates it must be visible together.
  158. func TestAlterTableAddColumnUniqueWithDefaultEnforcesLaterInserts(t *testing.T) {
  159. _, schema, table := newTestDB(t)
  160. e := newExec(schema, table)
  161. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  162. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  163. execMust(t, e, "ALTER TABLE t ADD COLUMN code TEXT DEFAULT 'same' UNIQUE")
  164. s, err := schema.GetSchema("t")
  165. if err != nil {
  166. t.Fatalf("get schema: %v", err)
  167. }
  168. if _, exists := s.GetColumn("code"); !exists {
  169. t.Fatal("ADD COLUMN ... UNIQUE published no column")
  170. }
  171. indexes, err := schema.ListTableIndexes("t")
  172. if err != nil {
  173. t.Fatalf("list indexes: %v", err)
  174. }
  175. if len(indexes) != 1 {
  176. t.Fatalf("indexes = %v, want the one unique index for column code", indexes)
  177. }
  178. if !indexes[0].Unique || len(indexes[0].Columns) != 1 || indexes[0].Columns[0].Name != "code" {
  179. t.Fatalf("index = %+v, want a unique index over code", indexes[0])
  180. }
  181. _, err = execSQL(e, "INSERT INTO t (id) VALUES (2)")
  182. var violation *storage.UniqueViolationError
  183. if !errors.As(err, &violation) {
  184. t.Fatalf("insert with duplicated default error = %v, want UniqueViolationError", err)
  185. }
  186. execMust(t, e, "INSERT INTO t (id, code) VALUES (2, 'other')")
  187. res := execMust(t, e, "SELECT code FROM t ORDER BY id")
  188. if len(res.Rows) != 2 || res.Rows[0][0] != "same" || res.Rows[1][0] != "other" {
  189. t.Fatalf("codes = %v, want [same other]", res.Rows)
  190. }
  191. }
  192. // A transaction can stage a row image before a concurrent ADD COLUMN publishes
  193. // a required column. The commit must re-validate that row against the schema now
  194. // in effect instead of persisting a durable NOT NULL violation.
  195. func TestAlterTableAddColumnRejectsRowStagedBeforeDDL(t *testing.T) {
  196. _, schema, table := newTestDB(t)
  197. writer := newExec(schema, table)
  198. ddl := newExec(schema, table)
  199. execMust(t, writer, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  200. execMust(t, writer, "BEGIN")
  201. execMust(t, writer, "INSERT INTO t (id) VALUES (1)")
  202. // The table is durably empty, so the ALTER accepts the required column.
  203. execMust(t, ddl, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL")
  204. _, err := execSQL(writer, "COMMIT")
  205. var violation *storage.NotNullViolationError
  206. if !errors.As(err, &violation) {
  207. t.Fatalf("commit error = %v, want NotNullViolationError", err)
  208. }
  209. if got := execMust(t, ddl, "SELECT id FROM t").RowCount; got != 0 {
  210. t.Fatalf("rows after rejected commit = %d, want 0", got)
  211. }
  212. }
  213. // The same race is harmless when the new column has a default: the commit
  214. // materializes it into the staged row, exactly as the backfill did for rows that
  215. // were already durable.
  216. func TestAlterTableAddColumnDefaultsRowStagedBeforeDDL(t *testing.T) {
  217. _, schema, table := newTestDB(t)
  218. writer := newExec(schema, table)
  219. ddl := newExec(schema, table)
  220. execMust(t, writer, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  221. execMust(t, writer, "BEGIN")
  222. execMust(t, writer, "INSERT INTO t (id) VALUES (1)")
  223. execMust(t, ddl, "ALTER TABLE t ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'")
  224. execMust(t, writer, "COMMIT")
  225. res := execMust(t, ddl, "SELECT id, status FROM t")
  226. if len(res.Rows) != 1 || res.Rows[0][1] != "pending" {
  227. t.Fatalf("rows = %v, want [[1 pending]]", res.Rows)
  228. }
  229. }
  230. // IF NOT EXISTS stays idempotent on a populated table: the second statement is
  231. // a no-op even when it declares a different default.
  232. func TestAlterTableAddColumnIfNotExistsKeepsBackfilledValues(t *testing.T) {
  233. _, schema, table := newTestDB(t)
  234. e := newExec(schema, table)
  235. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  236. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  237. execMust(t, e, "ALTER TABLE t ADD COLUMN IF NOT EXISTS note TEXT DEFAULT 'first'")
  238. execMust(t, e, "ALTER TABLE t ADD COLUMN IF NOT EXISTS note TEXT DEFAULT 'second'")
  239. res := execMust(t, e, "SELECT note FROM t WHERE id = 1")
  240. if got := res.Rows[0][0]; got != "first" {
  241. t.Fatalf("note = %v, want the first default to stand", got)
  242. }
  243. s, err := schema.GetSchema("t")
  244. if err != nil {
  245. t.Fatalf("get schema: %v", err)
  246. }
  247. count := 0
  248. for _, col := range s.Columns {
  249. if col.Name == "note" {
  250. count++
  251. }
  252. }
  253. if count != 1 {
  254. t.Fatalf("column note appears %d times, want 1", count)
  255. }
  256. }
  257. func TestAlterTableAddColumnIfNotExistsIsIdempotent(t *testing.T) {
  258. _, schema, table := newTestDB(t)
  259. e := newExec(schema, table)
  260. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  261. execMust(t, e, "ALTER TABLE t ADD COLUMN IF NOT EXISTS c TEXT")
  262. execMust(t, e, "ALTER TABLE t ADD COLUMN IF NOT EXISTS c TEXT")
  263. s, err := schema.GetSchema("t")
  264. if err != nil {
  265. t.Fatalf("get schema: %v", err)
  266. }
  267. count := 0
  268. for _, col := range s.Columns {
  269. if col.Name == "c" {
  270. count++
  271. }
  272. }
  273. if count != 1 {
  274. t.Fatalf("column c appears %d times, want 1", count)
  275. }
  276. }
  277. // Concurrent IF NOT EXISTS ADD COLUMN on one engine instance must not duplicate
  278. // metadata; the schema manager serializes the check-and-append.
  279. func TestAlterTableAddColumnIfNotExistsConcurrent(t *testing.T) {
  280. _, schema, table := newTestDB(t)
  281. e := newExec(schema, table)
  282. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  283. var wg sync.WaitGroup
  284. for i := 0; i < 8; i++ {
  285. wg.Add(1)
  286. go func() {
  287. defer wg.Done()
  288. session := newExec(schema, table)
  289. if _, err := execSQL(session, "ALTER TABLE t ADD COLUMN IF NOT EXISTS c TEXT"); err != nil {
  290. t.Errorf("add column: %v", err)
  291. }
  292. }()
  293. }
  294. wg.Wait()
  295. s, err := schema.GetSchema("t")
  296. if err != nil {
  297. t.Fatalf("get schema: %v", err)
  298. }
  299. count := 0
  300. for _, col := range s.Columns {
  301. if col.Name == "c" {
  302. count++
  303. }
  304. }
  305. if count != 1 {
  306. t.Fatalf("column c appears %d times, want 1", count)
  307. }
  308. }
  309. // DROP COLUMN rewrites every remaining row so the removed value does not stay
  310. // in durable storage.
  311. func TestAlterTableDropColumnRewritesPopulatedRows(t *testing.T) {
  312. _, schema, table := newTestDB(t)
  313. e := newExec(schema, table)
  314. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, keep TEXT, dropme TEXT)")
  315. execMust(t, e, "INSERT INTO t (id, keep, dropme) VALUES (1, 'a', 'secret'), (2, 'b', 'secret')")
  316. execMust(t, e, "ALTER TABLE t DROP COLUMN dropme")
  317. res := execMust(t, e, "SELECT * FROM t ORDER BY id")
  318. if len(res.Columns) != 2 || res.Columns[0] != "id" || res.Columns[1] != "keep" {
  319. t.Fatalf("columns after drop = %v, want [id keep]", res.Columns)
  320. }
  321. if res.RowCount != 2 || res.Rows[0][1] != "a" || res.Rows[1][1] != "b" {
  322. t.Fatalf("rows after drop = %v", res.Rows)
  323. }
  324. if _, err := execSQL(e, "SELECT dropme FROM t"); err == nil {
  325. t.Fatal("expected dropped column to be unknown")
  326. }
  327. }
  328. // RENAME COLUMN rewrites row keys but deliberately leaves index metadata
  329. // unchanged. Documented guidance: drop and recreate indexes after a rename.
  330. func TestAlterTableRenameColumnLeavesIndexMetadataStale(t *testing.T) {
  331. _, schema, table := newTestDB(t)
  332. e := newExec(schema, table)
  333. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT)")
  334. execMust(t, e, "INSERT INTO t (id, a) VALUES (1, 'x')")
  335. execMust(t, e, "CREATE INDEX idx_a ON t(a)")
  336. execMust(t, e, "ALTER TABLE t RENAME COLUMN a TO aa")
  337. res := execMust(t, e, "SELECT aa FROM t WHERE id = 1")
  338. if got := res.Rows[0][0]; got != "x" {
  339. t.Fatalf("renamed value = %v, want x", got)
  340. }
  341. names, err := schema.ListIndexes()
  342. if err != nil {
  343. t.Fatalf("list indexes: %v", err)
  344. }
  345. found := false
  346. for _, name := range names {
  347. if name == "idx_a" {
  348. found = true
  349. }
  350. }
  351. if !found {
  352. t.Fatalf("indexes = %v, want idx_a to still exist (rename does not rewrite index metadata)", names)
  353. }
  354. }