orez 0.2.25 → 0.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/dist/cf-do/watermark.d.ts +21 -0
  2. package/dist/cf-do/watermark.d.ts.map +1 -0
  3. package/dist/cf-do/watermark.js +93 -0
  4. package/dist/cf-do/watermark.js.map +1 -0
  5. package/dist/cf-do/worker.d.ts +48 -22
  6. package/dist/cf-do/worker.d.ts.map +1 -1
  7. package/dist/cf-do/worker.js +650 -269
  8. package/dist/cf-do/worker.js.map +1 -1
  9. package/dist/config.js +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/do-sql-tracking.d.ts +6 -0
  12. package/dist/do-sql-tracking.d.ts.map +1 -0
  13. package/dist/do-sql-tracking.js +14 -0
  14. package/dist/do-sql-tracking.js.map +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +28 -14
  17. package/dist/index.js.map +1 -1
  18. package/dist/pg-proxy-browser.js +6 -6
  19. package/dist/pg-proxy-browser.js.map +1 -1
  20. package/dist/pg-proxy-do-backend.d.ts +98 -17
  21. package/dist/pg-proxy-do-backend.d.ts.map +1 -1
  22. package/dist/pg-proxy-do-backend.js +6075 -454
  23. package/dist/pg-proxy-do-backend.js.map +1 -1
  24. package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
  25. package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
  26. package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
  27. package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
  28. package/dist/pg-sqlite-compiler/index.d.ts +12 -0
  29. package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
  30. package/dist/pg-sqlite-compiler/index.js +59 -0
  31. package/dist/pg-sqlite-compiler/index.js.map +1 -0
  32. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
  33. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
  34. package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
  35. package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
  36. package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
  37. package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
  38. package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
  39. package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
  40. package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
  41. package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
  42. package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
  43. package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
  44. package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
  45. package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
  46. package/dist/pg-sqlite-compiler/passes/index.js +39 -0
  47. package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
  48. package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
  49. package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
  50. package/dist/pg-sqlite-compiler/passes/types.js +103 -0
  51. package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
  52. package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
  53. package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
  54. package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
  55. package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
  56. package/dist/pg-sqlite-compiler/types.d.ts +55 -0
  57. package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
  58. package/dist/pg-sqlite-compiler/types.js +2 -0
  59. package/dist/pg-sqlite-compiler/types.js.map +1 -0
  60. package/dist/replication/change-tracker.d.ts.map +1 -1
  61. package/dist/replication/change-tracker.js +18 -1
  62. package/dist/replication/change-tracker.js.map +1 -1
  63. package/dist/replication/handler.d.ts.map +1 -1
  64. package/dist/replication/handler.js +7 -2
  65. package/dist/replication/handler.js.map +1 -1
  66. package/dist/replication/pgoutput-encoder.d.ts.map +1 -1
  67. package/dist/replication/pgoutput-encoder.js +72 -30
  68. package/dist/replication/pgoutput-encoder.js.map +1 -1
  69. package/dist/worker/browser-build-config.d.ts.map +1 -1
  70. package/dist/worker/browser-build-config.js +2 -1
  71. package/dist/worker/browser-build-config.js.map +1 -1
  72. package/dist/worker/cf-patches.d.ts +5 -2
  73. package/dist/worker/cf-patches.d.ts.map +1 -1
  74. package/dist/worker/cf-patches.js +238 -4
  75. package/dist/worker/cf-patches.js.map +1 -1
  76. package/dist/worker/shims/node-stub.d.ts +35 -0
  77. package/dist/worker/shims/node-stub.d.ts.map +1 -1
  78. package/dist/worker/shims/node-stub.js +53 -1
  79. package/dist/worker/shims/node-stub.js.map +1 -1
  80. package/dist/worker/shims/oxfmt.d.ts +4 -0
  81. package/dist/worker/shims/oxfmt.d.ts.map +1 -0
  82. package/dist/worker/shims/oxfmt.js +4 -0
  83. package/dist/worker/shims/oxfmt.js.map +1 -0
  84. package/dist/worker/shims/postgres-socket.js +1 -1
  85. package/dist/worker/shims/postgres-socket.js.map +1 -1
  86. package/dist/worker/shims/sqlite.d.ts +1 -0
  87. package/dist/worker/shims/sqlite.d.ts.map +1 -1
  88. package/dist/worker/shims/sqlite.js +229 -9
  89. package/dist/worker/shims/sqlite.js.map +1 -1
  90. package/dist/worker/shims/ws.d.ts.map +1 -1
  91. package/dist/worker/shims/ws.js +45 -0
  92. package/dist/worker/shims/ws.js.map +1 -1
  93. package/dist/worker/shims/zero-process-env.d.ts +2 -0
  94. package/dist/worker/shims/zero-process-env.d.ts.map +1 -0
  95. package/dist/worker/shims/zero-process-env.js +9 -0
  96. package/dist/worker/shims/zero-process-env.js.map +1 -0
  97. package/dist/worker/zero-cache-embed-cf.d.ts +29 -12
  98. package/dist/worker/zero-cache-embed-cf.d.ts.map +1 -1
  99. package/dist/worker/zero-cache-embed-cf.js +83 -14
  100. package/dist/worker/zero-cache-embed-cf.js.map +1 -1
  101. package/package.json +11 -2
  102. package/src/cf-do/.wrangler/cache/cf.json +1 -0
  103. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  104. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  105. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  106. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
  107. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  108. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  109. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  110. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +11 -0
  111. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +134 -0
  112. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +11 -0
  113. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +134 -0
  114. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +1059 -0
  115. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +8 -0
  116. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +1059 -0
  117. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +8 -0
  118. package/src/cf-do/ARCHITECTURE.md +93 -0
  119. package/src/cf-do/CHAT_E2E.md +213 -0
  120. package/src/cf-do/watermark.test.ts +103 -0
  121. package/src/cf-do/watermark.ts +118 -0
  122. package/src/cf-do/worker.ts +1041 -0
  123. package/src/cf-do/wrangler.toml +11 -0
  124. package/src/cli.test.ts +3 -1
  125. package/src/config.ts +1 -1
  126. package/src/do-sql-tracking.test.ts +19 -0
  127. package/src/do-sql-tracking.ts +19 -0
  128. package/src/index.ts +29 -14
  129. package/src/pg-proxy-browser.ts +6 -6
  130. package/src/pg-proxy-do-backend.test.ts +3890 -0
  131. package/src/pg-proxy-do-backend.ts +6833 -482
  132. package/src/pg-sqlite-compiler/README.md +53 -0
  133. package/src/pg-sqlite-compiler/catalog/seed.ts +524 -0
  134. package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +307 -0
  135. package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +377 -0
  136. package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +12 -0
  137. package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +447 -0
  138. package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +32 -0
  139. package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +397 -0
  140. package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +337 -0
  141. package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +337 -0
  142. package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +537 -0
  143. package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +1837 -0
  144. package/src/pg-sqlite-compiler/index.ts +73 -0
  145. package/src/pg-sqlite-compiler/integration.test.ts +136 -0
  146. package/src/pg-sqlite-compiler/passes/ast-utils.ts +113 -0
  147. package/src/pg-sqlite-compiler/passes/catalog.ts +65 -0
  148. package/src/pg-sqlite-compiler/passes/datetime.ts +74 -0
  149. package/src/pg-sqlite-compiler/passes/index.ts +49 -0
  150. package/src/pg-sqlite-compiler/passes/types.ts +156 -0
  151. package/src/pg-sqlite-compiler/smoke.test.ts +69 -0
  152. package/src/pg-sqlite-compiler/test/catalog.test.ts +171 -0
  153. package/src/pg-sqlite-compiler/test/corpus.test.ts +161 -0
  154. package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +102 -0
  155. package/src/pg-sqlite-compiler/test/oracle.ts +237 -0
  156. package/src/pg-sqlite-compiler/test/types.test.ts +109 -0
  157. package/src/pg-sqlite-compiler/types.ts +63 -0
  158. package/src/replication/change-tracker.ts +16 -1
  159. package/src/replication/handler.test.ts +35 -0
  160. package/src/replication/handler.ts +7 -2
  161. package/src/replication/pgoutput-encoder.test.ts +71 -2
  162. package/src/replication/pgoutput-encoder.ts +65 -30
  163. package/src/worker/browser-build-config.test.ts +12 -0
  164. package/src/worker/browser-build-config.ts +2 -1
  165. package/src/worker/cf-patches.ts +274 -4
  166. package/src/worker/shims/node-stub.ts +53 -1
  167. package/src/worker/shims/oxfmt.ts +3 -0
  168. package/src/worker/shims/postgres-socket.ts +1 -1
  169. package/src/worker/shims/sqlite.test.ts +145 -0
  170. package/src/worker/shims/sqlite.ts +256 -9
  171. package/src/worker/shims/ws.ts +45 -0
  172. package/src/worker/shims/zero-process-env.ts +11 -0
  173. package/src/worker/zero-cache-embed-cf.ts +114 -18
  174. package/src/query-rewrites.test.ts +0 -30
  175. package/src/query-rewrites.ts +0 -152
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Smoke test for the compiler scaffold.
3
+ *
4
+ * Validates the pipeline (parse → passes → emit) works end-to-end on a few
5
+ * representative cases. Real coverage comes from the snapshot + oracle tests.
6
+ */
7
+ import { describe, expect, it } from 'vitest'
8
+
9
+ import { compile } from './index.js'
10
+
11
+ describe('pg-sqlite-compiler smoke', () => {
12
+ it('passes through trivial select', () => {
13
+ const { sql, warnings } = compile('SELECT 1 AS ok')
14
+ expect(warnings).toEqual([])
15
+ expect(sql).toMatch(/SELECT\s+1\s+AS\s+ok/i)
16
+ })
17
+
18
+ it('rewrites NOW() to CURRENT_TIMESTAMP', () => {
19
+ const { sql } = compile('SELECT NOW() AS t')
20
+ expect(sql).toMatch(/CURRENT_TIMESTAMP/i)
21
+ expect(sql).not.toMatch(/NOW\s*\(/i)
22
+ })
23
+
24
+ it('rewrites NOW() inside CREATE TABLE DEFAULT', () => {
25
+ const { sql } = compile(
26
+ 'CREATE TABLE event (id text PRIMARY KEY, "createdAt" timestamp DEFAULT NOW() NOT NULL)'
27
+ )
28
+ expect(sql).toMatch(/CURRENT_TIMESTAMP/i)
29
+ expect(sql).not.toMatch(/NOW\s*\(/i)
30
+ })
31
+
32
+ it('passes CURRENT_TIMESTAMP keyword through unchanged', () => {
33
+ // CURRENT_TIMESTAMP is a SQL bareword, not a function call — should already
34
+ // round-trip cleanly without our pass touching it.
35
+ const { sql } = compile('SELECT CURRENT_TIMESTAMP AS t')
36
+ expect(sql).toMatch(/CURRENT_TIMESTAMP/i)
37
+ })
38
+
39
+ it('rewrites pg_catalog.now() to CURRENT_TIMESTAMP', () => {
40
+ const { sql } = compile('SELECT pg_catalog.now() AS t')
41
+ expect(sql).toMatch(/CURRENT_TIMESTAMP/i)
42
+ expect(sql).not.toMatch(/pg_catalog/i)
43
+ })
44
+
45
+ it('rewrites NOW() in an UPDATE SET clause', () => {
46
+ const { sql } = compile('UPDATE event SET "updatedAt" = NOW() WHERE id = $1')
47
+ expect(sql).toMatch(/CURRENT_TIMESTAMP/i)
48
+ expect(sql).not.toMatch(/now\s*\(/i)
49
+ })
50
+
51
+ it('rewrites CURRENT_DATE used as DEFAULT', () => {
52
+ const { sql } = compile(
53
+ 'CREATE TABLE log (id text PRIMARY KEY, "day" date DEFAULT CURRENT_DATE NOT NULL)'
54
+ )
55
+ expect(sql).toMatch(/CURRENT_DATE/i)
56
+ })
57
+
58
+ it('preserves multi-statement input', () => {
59
+ const { sql } = compile('SELECT 1; SELECT 2')
60
+ expect(sql).toMatch(/SELECT\s+1/i)
61
+ expect(sql).toMatch(/SELECT\s+2/i)
62
+ })
63
+
64
+ it('preserves quoted identifiers', () => {
65
+ const { sql } = compile('SELECT id, "createdAt" FROM public.message')
66
+ expect(sql).toMatch(/"createdAt"/)
67
+ expect(sql).toMatch(/public\.message/i)
68
+ })
69
+ })
@@ -0,0 +1,171 @@
1
+ import Database from '@rocicorp/zero-sqlite3'
2
+ /**
3
+ * Catalog pass tests.
4
+ *
5
+ * Validates two things in tandem:
6
+ * 1. The pass rewrites catalog refs to _orez_catalog__* (snapshot-style).
7
+ * 2. The seed creates matching tables that the rewritten query can
8
+ * actually execute against (semantic equivalence with a real catalog).
9
+ */
10
+ import { describe, expect, it } from 'vitest'
11
+
12
+ import { buildCatalogTables } from '../catalog/seed.js'
13
+ import { compile } from '../index.js'
14
+
15
+ function rewriteParams(sql: string): string {
16
+ return sql.replace(/\$\d+/g, '?')
17
+ }
18
+
19
+ function freshDb(): { db: any; setup: (s: string[]) => void } {
20
+ const db = new Database(':memory:')
21
+ return {
22
+ db,
23
+ setup: (statements: string[]) => {
24
+ for (const s of statements) {
25
+ const { sql } = compile(s)
26
+ db.exec(sql)
27
+ }
28
+ },
29
+ }
30
+ }
31
+
32
+ describe('catalog pass — rewrite', () => {
33
+ it('pg_catalog.pg_class → _orez_catalog__pg_class', () => {
34
+ const { sql } = compile('SELECT relname FROM pg_catalog.pg_class WHERE relkind = $1')
35
+ expect(sql).toMatch(/_orez_catalog__pg_class/)
36
+ expect(sql).not.toMatch(/pg_catalog\./)
37
+ })
38
+
39
+ it('bare pg_class (no schema) is NOT rewritten — would hijack user tables', () => {
40
+ // Apps may legitimately have a table called `pg_user`/`pg_views`/etc.
41
+ // Catalog clients always qualify (`pg_catalog.pg_class`), so the bare
42
+ // form is treated as a user table.
43
+ const { sql } = compile('SELECT relname FROM pg_class WHERE relkind = $1')
44
+ expect(sql).not.toMatch(/_orez_catalog/)
45
+ expect(sql).toMatch(/FROM pg_class/i)
46
+ })
47
+
48
+ it('information_schema.columns → _orez_catalog__information_schema_columns', () => {
49
+ const { sql } = compile(
50
+ "SELECT column_name FROM information_schema.columns WHERE table_name = 't'"
51
+ )
52
+ expect(sql).toMatch(/_orez_catalog__information_schema_columns/)
53
+ expect(sql).not.toMatch(/information_schema\./)
54
+ })
55
+
56
+ it('user table refs (e.g. message) are NOT rewritten', () => {
57
+ const { sql } = compile('SELECT id FROM message WHERE id = $1')
58
+ expect(sql).not.toMatch(/_orez_catalog/)
59
+ expect(sql).toMatch(/FROM message/i)
60
+ })
61
+
62
+ it('information_schema.tables → flat name', () => {
63
+ const { sql } = compile(
64
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"
65
+ )
66
+ expect(sql).toMatch(/_orez_catalog__information_schema_tables/)
67
+ })
68
+
69
+ it('schema-qualified pg_publication → flat name', () => {
70
+ const { sql } = compile(
71
+ 'SELECT pubname FROM pg_catalog.pg_publication WHERE pubname IN ($1)'
72
+ )
73
+ expect(sql).toMatch(/_orez_catalog__pg_publication/)
74
+ })
75
+
76
+ it('INSERT INTO pg_user (bare) is NOT rewritten — user-owned table', () => {
77
+ // Regression guard: earlier iterations rewrote bare PG catalog names,
78
+ // which silently redirected user DML at synthetic catalog tables.
79
+ const { sql } = compile('INSERT INTO pg_user (id) VALUES ($1)')
80
+ expect(sql).not.toMatch(/_orez_catalog/)
81
+ })
82
+ })
83
+
84
+ describe('catalog seed + pass roundtrip — executable', () => {
85
+ it('pg_class returns the user-table rows after seeding', () => {
86
+ const { db, setup } = freshDb()
87
+ setup([
88
+ 'CREATE TABLE message (id text PRIMARY KEY, content text)',
89
+ 'CREATE TABLE event (id text PRIMARY KEY, ts timestamp)',
90
+ ])
91
+ buildCatalogTables(db)
92
+ const { sql } = compile(`SELECT relname FROM pg_catalog.pg_class WHERE relkind = 'r'`)
93
+ const rows = db.prepare(rewriteParams(sql)).all() as { relname: string }[]
94
+ const names = rows.map((r) => r.relname).sort()
95
+ expect(names).toContain('message')
96
+ expect(names).toContain('event')
97
+ db.close()
98
+ })
99
+
100
+ it('pg_attribute returns column info per table', () => {
101
+ const { db, setup } = freshDb()
102
+ setup(['CREATE TABLE message (id text PRIMARY KEY, content text NOT NULL)'])
103
+ buildCatalogTables(db)
104
+ const { sql } = compile(`
105
+ SELECT a.attname, a.attnotnull
106
+ FROM pg_catalog.pg_attribute a
107
+ JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
108
+ WHERE c.relname = 'message'
109
+ ORDER BY a.attnum
110
+ `)
111
+ const rows = db.prepare(rewriteParams(sql)).all() as {
112
+ attname: string
113
+ attnotnull: number
114
+ }[]
115
+ expect(rows.map((r) => r.attname)).toEqual(['id', 'content'])
116
+ // `content text NOT NULL` is reported NOT NULL; SQLite's PRAGMA table_info
117
+ // reports `id text PRIMARY KEY` as NOT NULL only when explicitly written
118
+ // that way (historical SQLite quirk), so we only check `content` here.
119
+ expect(rows[1].attnotnull).toBe(1)
120
+ db.close()
121
+ })
122
+
123
+ it('information_schema.columns returns column metadata', () => {
124
+ const { db, setup } = freshDb()
125
+ setup(['CREATE TABLE event (id text PRIMARY KEY, "createdAt" timestamp)'])
126
+ buildCatalogTables(db)
127
+ const { sql } = compile(
128
+ "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'event' ORDER BY ordinal_position"
129
+ )
130
+ const rows = db.prepare(rewriteParams(sql)).all() as {
131
+ column_name: string
132
+ data_type: string
133
+ }[]
134
+ expect(rows.map((r) => r.column_name)).toEqual(['id', 'createdAt'])
135
+ db.close()
136
+ })
137
+
138
+ it('pg_namespace contains public + pg_catalog', () => {
139
+ const { db, setup } = freshDb()
140
+ setup([])
141
+ buildCatalogTables(db)
142
+ const { sql } = compile('SELECT nspname FROM pg_catalog.pg_namespace ORDER BY oid')
143
+ const rows = db.prepare(rewriteParams(sql)).all() as { nspname: string }[]
144
+ expect(rows.map((r) => r.nspname)).toEqual([
145
+ 'pg_catalog',
146
+ 'information_schema',
147
+ 'public',
148
+ ])
149
+ db.close()
150
+ })
151
+
152
+ it('pg_publication is populated when ZERO_APP_PUBLICATIONS-style names supplied', () => {
153
+ const { db, setup } = freshDb()
154
+ setup(['CREATE TABLE message (id text PRIMARY KEY)'])
155
+ buildCatalogTables(db, { publications: ['orez_zero_public'] })
156
+ const { sql } = compile('SELECT pubname FROM pg_catalog.pg_publication')
157
+ const rows = db.prepare(rewriteParams(sql)).all() as { pubname: string }[]
158
+ expect(rows.map((r) => r.pubname)).toEqual(['orez_zero_public'])
159
+
160
+ const { sql: sql2 } = compile(
161
+ "SELECT pubname, tablename FROM pg_catalog.pg_publication_tables WHERE pubname = 'orez_zero_public'"
162
+ )
163
+ const ptRows = db.prepare(rewriteParams(sql2)).all() as {
164
+ pubname: string
165
+ tablename: string
166
+ }[]
167
+ expect(ptRows.length).toBeGreaterThanOrEqual(1)
168
+ expect(ptRows[0].tablename).toBe('message')
169
+ db.close()
170
+ })
171
+ })
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Corpus tests — run every vendored pgsqlite fixture through the compiler.
3
+ *
4
+ * Asserts the compiler doesn't throw for fixtures in each bucket, against a
5
+ * per-bucket **minimum-passing-count ratchet** (`BASELINE_OK`). Pass rates
6
+ * are already 94–100% — a flat 50% floor would silently mask real
7
+ * regressions. The ratchet is in absolute counts (not percentages) so adding
8
+ * new fixtures to a bucket can't make the threshold easier to clear.
9
+ *
10
+ * Bumping a baseline: improve coverage, run the test, copy the new count
11
+ * from the summary table into `BASELINE_OK`. Never lower a baseline without
12
+ * understanding *why* — that's the regression alarm.
13
+ *
14
+ * Specific buckets have their own oracle tests (datetime.oracle.test.ts,
15
+ * etc.) that check translation correctness in detail.
16
+ */
17
+ import { readFileSync } from 'node:fs'
18
+ import { resolve } from 'node:path'
19
+
20
+ import { describe, expect, it } from 'vitest'
21
+
22
+ import { compile } from '../index.js'
23
+
24
+ const FIXTURES_DIR = resolve(import.meta.dirname, '..', 'fixtures', 'pgsqlite')
25
+
26
+ interface BucketFixture {
27
+ source: string
28
+ bucket: string
29
+ count: number
30
+ cases: { name: string; sql: string; source: string }[]
31
+ }
32
+
33
+ function loadBucket(bucket: string): BucketFixture {
34
+ return JSON.parse(readFileSync(resolve(FIXTURES_DIR, `${bucket}.json`), 'utf-8'))
35
+ }
36
+
37
+ const BUCKETS = [
38
+ 'datetime',
39
+ 'array',
40
+ 'cast',
41
+ 'json',
42
+ 'catalog',
43
+ 'enum',
44
+ 'create-table',
45
+ 'insert',
46
+ 'arithmetic',
47
+ 'misc',
48
+ ]
49
+
50
+ /**
51
+ * Minimum non-throwing fixture count per bucket. The corpus today passes at
52
+ * 99.2% overall; these baselines lock that in. Bump (never lower) when
53
+ * coverage improves.
54
+ */
55
+ const BASELINE_OK: Record<string, number> = {
56
+ arithmetic: 60,
57
+ array: 74,
58
+ cast: 1,
59
+ catalog: 87,
60
+ 'create-table': 5,
61
+ datetime: 74,
62
+ enum: 66,
63
+ insert: 65,
64
+ json: 106,
65
+ misc: 365,
66
+ }
67
+
68
+ interface BucketResult {
69
+ bucket: string
70
+ total: number
71
+ parseOk: number
72
+ compileOk: number
73
+ // examples of failures (first 5) — useful when diagnosing regressions
74
+ failures: { name: string; sql: string; error: string }[]
75
+ }
76
+
77
+ const results: BucketResult[] = []
78
+
79
+ describe('pgsqlite corpus survival', () => {
80
+ for (const bucket of BUCKETS) {
81
+ describe(bucket, () => {
82
+ let fixture: BucketFixture
83
+ try {
84
+ fixture = loadBucket(bucket)
85
+ } catch {
86
+ it.skip(`fixture missing for ${bucket}`, () => {})
87
+ return
88
+ }
89
+
90
+ it(`compiles ${fixture.cases.length} cases without throwing`, () => {
91
+ const result: BucketResult = {
92
+ bucket,
93
+ total: fixture.cases.length,
94
+ parseOk: 0,
95
+ compileOk: 0,
96
+ failures: [],
97
+ }
98
+ for (const c of fixture.cases) {
99
+ try {
100
+ const { sql, warnings } = compile(c.sql)
101
+ if (sql) {
102
+ result.compileOk++
103
+ result.parseOk++
104
+ } else if (warnings.length === 0) {
105
+ result.compileOk++
106
+ result.parseOk++
107
+ }
108
+ } catch (err: any) {
109
+ if (result.failures.length < 5) {
110
+ result.failures.push({
111
+ name: c.name,
112
+ sql: c.sql.slice(0, 200),
113
+ error: (err.message || String(err)).slice(0, 200),
114
+ })
115
+ }
116
+ }
117
+ }
118
+ results.push(result)
119
+ const baseline = BASELINE_OK[bucket] ?? 0
120
+ if (result.compileOk < baseline) {
121
+ const sample = result.failures
122
+ .slice(0, 3)
123
+ .map((f) => ` - ${f.name}: ${f.error}`)
124
+ .join('\n')
125
+ throw new Error(
126
+ `corpus regression in bucket "${bucket}": got ${result.compileOk}/${result.total} passing, baseline is ${baseline}.\n` +
127
+ `If this is intentional (e.g. fixture set changed), update BASELINE_OK.\n` +
128
+ `Sample failures:\n${sample}`
129
+ )
130
+ }
131
+ // also require coverage doesn't collapse against the fixture set
132
+ expect(result.compileOk).toBeGreaterThanOrEqual(baseline)
133
+ })
134
+ })
135
+ }
136
+ })
137
+
138
+ // emit a summary table after all tests run
139
+ import { afterAll } from 'vitest'
140
+ afterAll(() => {
141
+ if (results.length === 0) return
142
+ console.log('\n========== CORPUS COMPILER SURVIVAL ==========')
143
+ console.log('bucket total ok pct example-failures')
144
+ console.log('--------------------------------------------------')
145
+ let totalAll = 0
146
+ let okAll = 0
147
+ for (const r of results.sort((a, b) => a.bucket.localeCompare(b.bucket))) {
148
+ const pct = ((r.compileOk / Math.max(1, r.total)) * 100).toFixed(1).padStart(5)
149
+ const ex = r.failures[0] ? `${r.failures[0].error.slice(0, 50)}` : ''
150
+ console.log(
151
+ `${r.bucket.padEnd(14)} ${String(r.total).padStart(5)} ${String(r.compileOk).padStart(5)} ${pct}% ${ex}`
152
+ )
153
+ totalAll += r.total
154
+ okAll += r.compileOk
155
+ }
156
+ console.log('--------------------------------------------------')
157
+ console.log(
158
+ `${'TOTAL'.padEnd(14)} ${String(totalAll).padStart(5)} ${String(okAll).padStart(5)} ${((okAll / Math.max(1, totalAll)) * 100).toFixed(1).padStart(5)}%`
159
+ )
160
+ console.log('==============================================')
161
+ })
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Oracle tests for the datetime pass.
3
+ *
4
+ * For each PG query: run it against pgsqlite (oracle) AND through our
5
+ * compile() + bun:sqlite. Compare result shapes. We don't compare exact
6
+ * timestamps (CURRENT_TIMESTAMP returns "now" — non-deterministic) — just
7
+ * that both sides return a row of the same shape with a parseable timestamp.
8
+ */
9
+ import { describe, expect, it } from 'vitest'
10
+
11
+ import { compile } from '../index.js'
12
+ import {
13
+ ORACLE_AVAILABLE,
14
+ connectOracle,
15
+ runOracleAndCompiler,
16
+ startPgsqliteServer,
17
+ } from './oracle.js'
18
+
19
+ const describeOracle = ORACLE_AVAILABLE ? describe : describe.skip
20
+
21
+ describeOracle('datetime pass against pgsqlite oracle', () => {
22
+ it('NOW() in SELECT — both sides return a timestamp', async () => {
23
+ const { oracle, ours } = await runOracleAndCompiler([], 'SELECT NOW() AS t')
24
+ expect(oracle).toHaveLength(1)
25
+ expect(ours).toHaveLength(1)
26
+ // both should expose 't' with something Date-parseable
27
+ const ot = String((oracle[0] as any).t)
28
+ const ut = String((ours[0] as any).t)
29
+ expect(Number.isNaN(Date.parse(ot))).toBe(false)
30
+ expect(Number.isNaN(Date.parse(ut))).toBe(false)
31
+ }, 30_000)
32
+
33
+ /**
34
+ * pgsqlite has a known bug here: it rewrites `DEFAULT NOW()` →
35
+ * `DEFAULT datetime('now')`, which isn't valid in a SQLite CREATE TABLE
36
+ * DEFAULT clause (only a small expression grammar is permitted). Our
37
+ * compiler rewrites to `DEFAULT CURRENT_TIMESTAMP` which IS valid.
38
+ *
39
+ * So this test validates *our* side standalone; if pgsqlite ever fixes
40
+ * this bug, we can promote it to a full oracle test.
41
+ */
42
+ it('NOW() in CREATE TABLE DEFAULT — ours works (pgsqlite has a known bug here)', async () => {
43
+ // @ts-expect-error — test-only sqlite driver
44
+ const { default: Database } = await import('@rocicorp/zero-sqlite3')
45
+ const db = new Database(':memory:')
46
+ const { sql: c1 } = compile(
47
+ 'CREATE TABLE event (id text PRIMARY KEY, ts timestamp DEFAULT NOW() NOT NULL)'
48
+ )
49
+ db.exec(c1)
50
+ db.prepare('INSERT INTO event (id) VALUES (?)').run('e1')
51
+ const ours = db.prepare('SELECT id, ts FROM event WHERE id = ?').all('e1') as any[]
52
+ db.close()
53
+ expect(ours).toHaveLength(1)
54
+ expect(ours[0].id).toBe('e1')
55
+ expect(String(ours[0].ts)).toMatch(/^\d{4}-\d{2}-\d{2}/)
56
+ })
57
+
58
+ it('CURRENT_DATE in DEFAULT — both sides accept', async () => {
59
+ const server = await startPgsqliteServer()
60
+ try {
61
+ const conn = await connectOracle(server)
62
+ try {
63
+ await conn.exec(
64
+ 'CREATE TABLE log (id text PRIMARY KEY, day date DEFAULT CURRENT_DATE NOT NULL)'
65
+ )
66
+ await conn.exec('INSERT INTO log (id) VALUES ($1)', ['l1'])
67
+ const oracle = (await conn.exec('SELECT id, day FROM log WHERE id = $1', [
68
+ 'l1',
69
+ ])) as any[]
70
+
71
+ // @ts-expect-error — test-only sqlite driver
72
+ const { default: Database } = await import('@rocicorp/zero-sqlite3')
73
+ const db = new Database(':memory:')
74
+ const { sql: c1 } = compile(
75
+ 'CREATE TABLE log (id text PRIMARY KEY, day date DEFAULT CURRENT_DATE NOT NULL)'
76
+ )
77
+ db.exec(c1)
78
+ db.prepare('INSERT INTO log (id) VALUES (?)').run('l1')
79
+ const ours = db.prepare('SELECT id, day FROM log WHERE id = ?').all('l1') as any[]
80
+ db.close()
81
+
82
+ expect(oracle).toHaveLength(1)
83
+ expect(ours).toHaveLength(1)
84
+ // postgres.js parses DATE values into JS Date objects; verify both
85
+ // sides produced a Date-equivalent (today). Don't compare strings —
86
+ // pgsqlite returns ISO, sqlite returns YYYY-MM-DD, both valid.
87
+ const oracleDate = new Date(String(oracle[0].day))
88
+ const ourDate = new Date(String(ours[0].day))
89
+ expect(Number.isNaN(oracleDate.getTime())).toBe(false)
90
+ expect(Number.isNaN(ourDate.getTime())).toBe(false)
91
+ // Within ~24h of each other (allowing for TZ).
92
+ expect(Math.abs(oracleDate.getTime() - ourDate.getTime())).toBeLessThan(
93
+ 86_400_000
94
+ )
95
+ } finally {
96
+ await conn.end()
97
+ }
98
+ } finally {
99
+ await server.stop()
100
+ }
101
+ }, 30_000)
102
+ })