ddl_defaults_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. package executor
  2. import (
  3. "errors"
  4. "testing"
  5. "time"
  6. "github.com/danfragoso/pizzasql-next/pkg/pgerror"
  7. "github.com/danfragoso/pizzasql-next/pkg/storage"
  8. )
  9. // testClock lets a test move the clock forward deterministically between
  10. // statements, so per-statement default evaluation is observable without
  11. // depending on wall-clock resolution.
  12. type testClock struct {
  13. now time.Time
  14. }
  15. func (c *testClock) Now() time.Time { return c.now }
  16. func (c *testClock) Advance(d time.Duration) { c.now = c.now.Add(d) }
  17. func newTestClock() *testClock {
  18. return &testClock{now: time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)}
  19. }
  20. // steppingClock advances one second per call, so a test can see whether a
  21. // value was evaluated once per statement or once per row.
  22. type steppingClock struct {
  23. now time.Time
  24. }
  25. func (c *steppingClock) Now() time.Time {
  26. current := c.now
  27. c.now = c.now.Add(time.Second)
  28. return current
  29. }
  30. func TestCreateTableWithCurrentTimestampDefaultAppliesOnInsert(t *testing.T) {
  31. _, schema, table := newTestDB(t)
  32. e := newExec(schema, table)
  33. clock := newTestClock()
  34. e.clock = clock.Now
  35. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  36. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  37. res := execMust(t, e, "SELECT created_at FROM t WHERE id = 1")
  38. if res.RowCount != 1 {
  39. t.Fatalf("row count = %d, want 1", res.RowCount)
  40. }
  41. want := "2026-01-02 03:04:05"
  42. if got := res.Rows[0][0]; got != want {
  43. t.Fatalf("created_at = %v, want %q", got, want)
  44. }
  45. }
  46. func TestCurrentDateAndTimeDefaults(t *testing.T) {
  47. _, schema, table := newTestDB(t)
  48. e := newExec(schema, table)
  49. e.clock = newTestClock().Now
  50. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, d DATE DEFAULT CURRENT_DATE, tm TEXT DEFAULT CURRENT_TIME)")
  51. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  52. res := execMust(t, e, "SELECT d, tm FROM t WHERE id = 1")
  53. if got, want := res.Rows[0][0], "2026-01-02"; got != want {
  54. t.Fatalf("date = %v, want %q", got, want)
  55. }
  56. if got, want := res.Rows[0][1], "03:04:05"; got != want {
  57. t.Fatalf("time = %v, want %q", got, want)
  58. }
  59. }
  60. func TestWhitelistedDatetimeNowDefaults(t *testing.T) {
  61. _, schema, table := newTestDB(t)
  62. e := newExec(schema, table)
  63. e.clock = newTestClock().Now
  64. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, ts TIMESTAMP DEFAULT datetime('now'), d DATE DEFAULT date('now'), tm TEXT DEFAULT time('now'))")
  65. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  66. res := execMust(t, e, "SELECT ts, d, tm FROM t WHERE id = 1")
  67. if got, want := res.Rows[0][0], "2026-01-02 03:04:05"; got != want {
  68. t.Fatalf("ts = %v, want %q", got, want)
  69. }
  70. if got, want := res.Rows[0][1], "2026-01-02"; got != want {
  71. t.Fatalf("d = %v, want %q", got, want)
  72. }
  73. if got, want := res.Rows[0][2], "03:04:05"; got != want {
  74. t.Fatalf("tm = %v, want %q", got, want)
  75. }
  76. }
  77. func TestExplicitNullWinsOverNonConstantDefault(t *testing.T) {
  78. _, schema, table := newTestDB(t)
  79. e := newExec(schema, table)
  80. e.clock = newTestClock().Now
  81. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  82. execMust(t, e, "INSERT INTO t (id, created_at) VALUES (1, NULL)")
  83. res := execMust(t, e, "SELECT created_at FROM t WHERE id = 1")
  84. if got := res.Rows[0][0]; got != nil {
  85. t.Fatalf("created_at = %v, want NULL (an explicit NULL wins)", got)
  86. }
  87. }
  88. func TestUpdateOmittingColumnDoesNotReevaluateDefault(t *testing.T) {
  89. _, schema, table := newTestDB(t)
  90. e := newExec(schema, table)
  91. clock := newTestClock()
  92. e.clock = clock.Now
  93. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  94. execMust(t, e, "INSERT INTO t (id, label) VALUES (1, 'first')")
  95. clock.Advance(time.Hour)
  96. execMust(t, e, "UPDATE t SET label = 'second' WHERE id = 1")
  97. res := execMust(t, e, "SELECT label, created_at FROM t WHERE id = 1")
  98. if got := res.Rows[0][0]; got != "second" {
  99. t.Fatalf("label = %v, want second", got)
  100. }
  101. if got, want := res.Rows[0][1], "2026-01-02 03:04:05"; got != want {
  102. t.Fatalf("created_at = %v, want the INSERT-time %q (UPDATE must not re-evaluate)", got, want)
  103. }
  104. }
  105. func TestInsertDefaultValuesUsesNonConstantDefault(t *testing.T) {
  106. _, schema, table := newTestDB(t)
  107. e := newExec(schema, table)
  108. e.clock = newTestClock().Now
  109. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT DEFAULT 'new', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  110. execMust(t, e, "INSERT INTO t DEFAULT VALUES")
  111. res := execMust(t, e, "SELECT label, created_at FROM t")
  112. if res.RowCount != 1 {
  113. t.Fatalf("row count = %d, want 1", res.RowCount)
  114. }
  115. if got := res.Rows[0][0]; got != "new" {
  116. t.Fatalf("label = %v, want new", got)
  117. }
  118. if got, want := res.Rows[0][1], "2026-01-02 03:04:05"; got != want {
  119. t.Fatalf("created_at = %v, want %q", got, want)
  120. }
  121. }
  122. func TestNonConstantDefaultEvaluatedOncePerStatement(t *testing.T) {
  123. _, schema, table := newTestDB(t)
  124. e := newExec(schema, table)
  125. clock := newTestClock()
  126. e.clock = clock.Now
  127. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  128. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  129. clock.Advance(time.Minute)
  130. execMust(t, e, "INSERT INTO t (id) VALUES (2)")
  131. res := execMust(t, e, "SELECT id, created_at FROM t ORDER BY id")
  132. first := res.Rows[0][1]
  133. second := res.Rows[1][1]
  134. if first == second {
  135. t.Fatalf("two INSERT statements produced the same timestamp %v; each statement must evaluate its own default", first)
  136. }
  137. if compare(first, second) >= 0 {
  138. t.Fatalf("timestamps %v then %v are not ordered", first, second)
  139. }
  140. }
  141. func TestNonConstantDefaultAppliesInsideTransaction(t *testing.T) {
  142. _, schema, table := newTestDB(t)
  143. e := newExec(schema, table)
  144. e.clock = newTestClock().Now
  145. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  146. execMust(t, e, "BEGIN")
  147. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  148. execMust(t, e, "COMMIT")
  149. // Read through a fresh executor so the value is the committed row, not the
  150. // transaction's staged overlay.
  151. e2 := newExec(schema, table)
  152. res := execMust(t, e2, "SELECT created_at FROM t WHERE id = 1")
  153. if got, want := res.Rows[0][0], "2026-01-02 03:04:05"; got != want {
  154. t.Fatalf("created_at = %v, want %q", got, want)
  155. }
  156. }
  157. func TestMultiRowInsertSharesStatementDefault(t *testing.T) {
  158. _, schema, table := newTestDB(t)
  159. e := newExec(schema, table)
  160. e.clock = newTestClock().Now
  161. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  162. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  163. res := execMust(t, e, "SELECT created_at FROM t")
  164. if res.Rows[0][0] != res.Rows[1][0] {
  165. t.Fatalf("multi-row INSERT timestamps = %v and %v, want one evaluation per statement", res.Rows[0][0], res.Rows[1][0])
  166. }
  167. }
  168. func TestAlterTableAddColumnBackfillsNonConstantDefaultPerRow(t *testing.T) {
  169. _, schema, table := newTestDB(t)
  170. e := newExec(schema, table)
  171. // The storage-side evaluator samples the executor clock for every rewritten
  172. // row, so a stepping clock shows per-row evaluation without patching the
  173. // schema manager.
  174. e.clock = (&steppingClock{now: time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)}).Now
  175. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  176. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  177. execMust(t, e, "ALTER TABLE t ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
  178. res := execMust(t, e, "SELECT id, created_at FROM t ORDER BY id")
  179. first, second := res.Rows[0][1], res.Rows[1][1]
  180. if first == second {
  181. t.Fatalf("both rows were stamped %v; the ADD COLUMN rewrite must evaluate each row", first)
  182. }
  183. if compare(first, second) >= 0 {
  184. t.Fatalf("timestamps %v then %v are not ordered", first, second)
  185. }
  186. col, ok := e.schemaColumn(t, "t", "created_at")
  187. if !ok {
  188. t.Fatal("created_at column missing from schema")
  189. }
  190. if col.DefaultExpr == "" {
  191. t.Fatal("ADD COLUMN did not persist the default expression")
  192. }
  193. if col.Default != nil {
  194. t.Fatalf("ADD COLUMN stored an evaluated default %v instead of an expression", col.Default)
  195. }
  196. }
  197. func TestMultipleClockDefaultsInOneRowShareInstant(t *testing.T) {
  198. _, schema, table := newTestDB(t)
  199. e := newExec(schema, table)
  200. // A stepping clock makes per-column sampling visible: the two columns must
  201. // still share the single instant sampled for the statement.
  202. e.clock = (&steppingClock{now: time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)}).Now
  203. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  204. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  205. res := execMust(t, e, "SELECT created_at, updated_at FROM t WHERE id = 1")
  206. if got := res.Rows[0][0]; got != "2026-01-02 03:04:05" {
  207. t.Fatalf("created_at = %v, want the statement instant", got)
  208. }
  209. if res.Rows[0][0] != res.Rows[0][1] {
  210. t.Fatalf("created_at = %v, updated_at = %v; both clock defaults must share one instant", res.Rows[0][0], res.Rows[0][1])
  211. }
  212. }
  213. func TestParenthesizedClockDefaults(t *testing.T) {
  214. _, schema, table := newTestDB(t)
  215. e := newExec(schema, table)
  216. e.clock = newTestClock().Now
  217. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, a TIMESTAMP DEFAULT (CURRENT_TIMESTAMP), b TIMESTAMP DEFAULT (datetime('now')))")
  218. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  219. execMust(t, e, "ALTER TABLE t ADD COLUMN c TIMESTAMP DEFAULT (CURRENT_TIMESTAMP)")
  220. res := execMust(t, e, "SELECT a, b, c FROM t WHERE id = 1")
  221. want := "2026-01-02 03:04:05"
  222. for i, name := range []string{"a", "b", "c"} {
  223. if got := res.Rows[0][i]; got != want {
  224. t.Fatalf("%s = %v, want %q", name, got, want)
  225. }
  226. }
  227. execMust(t, e, "INSERT INTO t (id) VALUES (2)")
  228. res = execMust(t, e, "SELECT a, b, c FROM t WHERE id = 2")
  229. if got := res.Rows[0][2]; got != want {
  230. t.Fatalf("new row c = %v, want %q", got, want)
  231. }
  232. }
  233. func TestAlterTableAddColumnUniqueClockDefaultRejectsDuplicates(t *testing.T) {
  234. _, schema, table := newTestDB(t)
  235. e := newExec(schema, table)
  236. e.clock = newTestClock().Now // one fixed instant: both backfilled rows collide
  237. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  238. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  239. _, err := execSQL(e, "ALTER TABLE t ADD COLUMN tag TEXT DEFAULT CURRENT_TIMESTAMP UNIQUE")
  240. var violation *storage.UniqueViolationError
  241. if !errors.As(err, &violation) {
  242. t.Fatalf("ALTER error = %v, want a UNIQUE violation", err)
  243. }
  244. // The duplicate must abort before the column and index become visible.
  245. stored, gerr := storage.NewSchemaManager(schema.GetPool(), schema.GetDatabaseName()).GetSchema("t")
  246. if gerr != nil {
  247. t.Fatalf("reload schema: %v", gerr)
  248. }
  249. if _, ok := stored.GetColumn("tag"); ok {
  250. t.Fatal("aborted ADD COLUMN published the tag column")
  251. }
  252. if e.schema.IndexExists("uniq_t_tag") {
  253. t.Fatal("aborted ADD COLUMN published the UNIQUE index")
  254. }
  255. }
  256. func TestAlterTableAddColumnUniqueClockDefaultValidatesTheRewrite(t *testing.T) {
  257. _, schema, table := newTestDB(t)
  258. e := newExec(schema, table)
  259. // The first two samples (the rewrite of rows 1 and 2) are distinct; later
  260. // samples collide. A separate prediction scan would have seen the distinct
  261. // values and then published an index over the colliding rewrite values.
  262. base := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
  263. calls := 0
  264. e.clock = func() time.Time {
  265. calls++
  266. if calls == 2 {
  267. return base.Add(time.Second)
  268. }
  269. if calls > 2 {
  270. return base.Add(2 * time.Second)
  271. }
  272. return base
  273. }
  274. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  275. execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
  276. execMust(t, e, "ALTER TABLE t ADD COLUMN tag TEXT DEFAULT CURRENT_TIMESTAMP UNIQUE")
  277. if calls != 2 {
  278. t.Fatalf("clock sampled %d times, want once per rewritten row (2); the UNIQUE check must validate the rewrite itself", calls)
  279. }
  280. res := execMust(t, e, "SELECT id, tag FROM t ORDER BY id")
  281. if res.Rows[0][1] == res.Rows[1][1] {
  282. t.Fatalf("both rows got tag %v; the published index must cover distinct rewrite values", res.Rows[0][1])
  283. }
  284. // The published index enforces uniqueness for later writes.
  285. execMust(t, e, "INSERT INTO t (id) VALUES (3)")
  286. if _, err := execSQL(e, "INSERT INTO t (id, tag) VALUES (4, '"+res.Rows[0][1].(string)+"')"); err == nil {
  287. t.Fatal("expected the published UNIQUE index to reject a duplicate")
  288. }
  289. }
  290. // schemaColumn reads a column from the durable schema through a fresh manager,
  291. // which also proves the expression survives a reload.
  292. func (e *Executor) schemaColumn(t *testing.T, table, column string) (*storage.Column, bool) {
  293. t.Helper()
  294. reloaded := storage.NewSchemaManager(e.schema.GetPool(), e.schema.GetDatabaseName())
  295. schema, err := reloaded.GetSchema(table)
  296. if err != nil {
  297. t.Fatalf("reload schema: %v", err)
  298. }
  299. return schema.GetColumn(column)
  300. }
  301. func TestNonConstantDefaultSurvivesReload(t *testing.T) {
  302. pool, schema, table := newTestDB(t)
  303. e := newExec(schema, table)
  304. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
  305. reloaded := storage.NewSchemaManager(pool, "testdb")
  306. reloadedTable := storage.NewTableManager(pool, reloaded, "testdb")
  307. e2 := newExec(reloaded, reloadedTable)
  308. clock := newTestClock()
  309. e2.clock = clock.Now
  310. col, ok := e2.schemaColumn(t, "t", "created_at")
  311. if !ok {
  312. t.Fatal("created_at column missing after reload")
  313. }
  314. if col.DefaultExpr != "CURRENT_TIMESTAMP" {
  315. t.Fatalf("reloaded DefaultExpr = %q, want CURRENT_TIMESTAMP", col.DefaultExpr)
  316. }
  317. if col.Default != nil {
  318. t.Fatalf("reloaded Default = %v, want nil", col.Default)
  319. }
  320. execMust(t, e2, "INSERT INTO t (id) VALUES (1)")
  321. res := execMust(t, e2, "SELECT created_at FROM t WHERE id = 1")
  322. if got, want := res.Rows[0][0], "2026-01-02 03:04:05"; got != want {
  323. t.Fatalf("created_at after reload = %v, want %q", got, want)
  324. }
  325. }
  326. func TestUnsupportedNonConstantDefaultsAreRejected(t *testing.T) {
  327. _, schema, table := newTestDB(t)
  328. e := newExec(schema, table)
  329. for _, sql := range []string{
  330. "CREATE TABLE t1 (id INTEGER PRIMARY KEY, token TEXT DEFAULT lower('X'))",
  331. "CREATE TABLE t2 (id INTEGER PRIMARY KEY, token TEXT DEFAULT upper('x'))",
  332. "CREATE TABLE t3 (id INTEGER PRIMARY KEY, n INTEGER DEFAULT (2 + 3))",
  333. } {
  334. if _, err := execSQL(e, sql); !errors.Is(err, pgerror.ErrFeatureNotSupported) {
  335. t.Fatalf("%s: error = %v, want ErrFeatureNotSupported", sql, err)
  336. }
  337. }
  338. }
  339. func TestAlterTableRejectsUnsupportedNonConstantDefault(t *testing.T) {
  340. _, schema, table := newTestDB(t)
  341. e := newExec(schema, table)
  342. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  343. if _, err := execSQL(e, "ALTER TABLE t ADD COLUMN token TEXT DEFAULT lower('X')"); !errors.Is(err, pgerror.ErrFeatureNotSupported) {
  344. t.Fatalf("ALTER error = %v, want ErrFeatureNotSupported", err)
  345. }
  346. }
  347. func TestConstantDefaultsStillApplyOnInsert(t *testing.T) {
  348. _, schema, table := newTestDB(t)
  349. e := newExec(schema, table)
  350. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT DEFAULT 'new', score INTEGER DEFAULT -1, flag BOOLEAN DEFAULT false, note TEXT DEFAULT NULL)")
  351. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  352. res := execMust(t, e, "SELECT label, score, flag, note FROM t WHERE id = 1")
  353. if res.RowCount != 1 {
  354. t.Fatalf("row count = %d, want 1", res.RowCount)
  355. }
  356. if got := res.Rows[0][0]; got != "new" {
  357. t.Fatalf("label = %v, want new", got)
  358. }
  359. if got := res.Rows[0][1]; got != int64(-1) {
  360. t.Fatalf("score = %v (%T), want int64(-1)", got, got)
  361. }
  362. if got := res.Rows[0][2]; got != false {
  363. t.Fatalf("flag = %v, want false", got)
  364. }
  365. if got := res.Rows[0][3]; got != nil {
  366. t.Fatalf("note = %v, want NULL", got)
  367. }
  368. }
  369. func TestAlterTableAddColumnBackfillsExistingRows(t *testing.T) {
  370. _, schema, table := newTestDB(t)
  371. e := newExec(schema, table)
  372. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  373. execMust(t, e, "INSERT INTO t (id) VALUES (1)")
  374. execMust(t, e, "ALTER TABLE t ADD COLUMN status TEXT DEFAULT 'pending'")
  375. res := execMust(t, e, "SELECT id, status FROM t WHERE id = 1")
  376. if res.RowCount != 1 {
  377. t.Fatalf("row count = %d, want 1", res.RowCount)
  378. }
  379. if got := res.Rows[0][1]; got != "pending" {
  380. t.Fatalf("existing row status = %v, want pending (ADD COLUMN backfills)", got)
  381. }
  382. execMust(t, e, "INSERT INTO t (id) VALUES (2)")
  383. res = execMust(t, e, "SELECT status FROM t WHERE id = 2")
  384. if got := res.Rows[0][0]; got != "pending" {
  385. t.Fatalf("new row status = %v, want pending", got)
  386. }
  387. }