@oxy-hq/sdk 2.13.0 → 2.16.0
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.
- package/README.md +67 -0
- package/dist/{function-context-D8eyZuw_.d.cts → function-context-BNpL5bFb.d.cts} +223 -20
- package/dist/function-context-BNpL5bFb.d.cts.map +1 -0
- package/dist/{function-context-D8eyZuw_.d.mts → function-context-BNpL5bFb.d.mts} +223 -20
- package/dist/function-context-BNpL5bFb.d.mts.map +1 -0
- package/dist/index.cjs +19 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +17 -15
- package/dist/index.mjs.map +1 -1
- package/dist/ops.d.cts +1 -1
- package/dist/ops.d.mts +1 -1
- package/dist/{react-D-Sf973d.d.mts → react-CljeXJuw.d.cts} +56 -7
- package/dist/react-CljeXJuw.d.cts.map +1 -0
- package/dist/{react-D-Sf973d.d.cts → react-CljeXJuw.d.mts} +56 -7
- package/dist/react-CljeXJuw.d.mts.map +1 -0
- package/dist/{react-CkAQg9wB.cjs → react-Dvkv2deI.cjs} +46 -55
- package/dist/react-Dvkv2deI.cjs.map +1 -0
- package/dist/{react-Cq3xULOr.mjs → react-OW1t_J0M.mjs} +39 -22
- package/dist/react-OW1t_J0M.mjs.map +1 -0
- package/dist/rolldown-runtime-KC0qvQup.cjs +34 -0
- package/dist/shell.cjs +4 -3
- package/dist/shell.cjs.map +1 -1
- package/dist/shell.d.cts +2 -2
- package/dist/shell.d.mts +2 -2
- package/dist/shell.mjs +1 -1
- package/dist/testing.cjs +4431 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +656 -0
- package/dist/testing.d.cts.map +1 -0
- package/dist/testing.d.mts +656 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +4395 -0
- package/dist/testing.mjs.map +1 -0
- package/package.json +22 -9
- package/dist/function-context-D8eyZuw_.d.cts.map +0 -1
- package/dist/function-context-D8eyZuw_.d.mts.map +0 -1
- package/dist/react-CkAQg9wB.cjs.map +0 -1
- package/dist/react-Cq3xULOr.mjs.map +0 -1
- package/dist/react-D-Sf973d.d.cts.map +0 -1
- package/dist/react-D-Sf973d.d.mts.map +0 -1
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,4431 @@
|
|
|
1
|
+
// @oxy/sdk - TypeScript SDK for Oxy data platform
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
3
|
+
const require_rolldown_runtime = require('./rolldown-runtime-KC0qvQup.cjs');
|
|
4
|
+
let node_crypto = require("node:crypto");
|
|
5
|
+
node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
|
|
6
|
+
|
|
7
|
+
//#region src/testing/host-contract.ts
|
|
8
|
+
/**
|
|
9
|
+
* The host's words, gates, op names and rules, as `@oxy-hq/sdk/testing`
|
|
10
|
+
* enforces them on an app's unit tests.
|
|
11
|
+
*
|
|
12
|
+
* ONE SOURCE OF TRUTH, AND IT IS NOT THIS FILE. Every string below is quoted
|
|
13
|
+
* from the host's source — `host.rs`, `host/destinations.rs`,
|
|
14
|
+
* `host/airhouse_ops.rs`, `upsert_support.rs`, `tx.rs`, `runtime.rs` under
|
|
15
|
+
* `crates/app/src/server/api/custom_apps_functions/`, and the connector's
|
|
16
|
+
* `transaction.rs` / `postgres_tx/convert.rs` — never paraphrased.
|
|
17
|
+
* `{placeholder}`s stand for what the host formats in (`{database}`,
|
|
18
|
+
* `{dialect}`, …) and use the host's own names.
|
|
19
|
+
*
|
|
20
|
+
* HELD TO THE HOST BY A RUST TEST. `sdk_testing_drift.rs`, beside `host.rs`,
|
|
21
|
+
* `include_str!`s this file and fails when a `refusal:` here is no longer a
|
|
22
|
+
* sentence its `source:` file contains, when `HOST_OPS` differs from the
|
|
23
|
+
* host's closed list, when `GATES` stops naming exactly the fields of
|
|
24
|
+
* `FunctionCapabilities`, when `ABSENT_GLOBALS` differs from `oxyc`'s lint
|
|
25
|
+
* list, or when `FETCH_RULES` differs from `is_safe_outbound`. One direction
|
|
26
|
+
* only: this file is checked against the host, the host is never checked
|
|
27
|
+
* against this file (`internal-docs/sdk-testing-context.md` §7, answer 9).
|
|
28
|
+
*
|
|
29
|
+
* THE RUST TEST READS TEXT, NOT TYPESCRIPT: after stripping comments it takes
|
|
30
|
+
* the block from `export const HOST_OPS` to `] as const;`, every object whose
|
|
31
|
+
* key line ends `: {` and carries `source:` and `refusal:` lines, the
|
|
32
|
+
* `hostField:` / `ops:` lines of `GATES`, the `name:` / `member:` lines of
|
|
33
|
+
* `ABSENT_GLOBALS`, and every quoted string in `FETCH_RULES`. Keep those keys
|
|
34
|
+
* spelled as they are, each `refusal:` a single string literal, and none of
|
|
35
|
+
* them behind a comment that is not a full-line one.
|
|
36
|
+
*
|
|
37
|
+
* NO `contractVersion` (§7, answer 10): a reworded refusal reaches an app on
|
|
38
|
+
* its next SDK bump, which is the right moment to see it; a version field
|
|
39
|
+
* with no consumer is a mechanism nobody reads.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Every name the host pages an op under — `HOST_OPS` in `host_call_attrs.rs`,
|
|
43
|
+
* "pinned by unit tests to `host_op_name` and `runtime::host_call_span`". The
|
|
44
|
+
* `HostOp` type is this list, so `"warehouse.inserts"` does not compile.
|
|
45
|
+
*/
|
|
46
|
+
const HOST_OPS = [
|
|
47
|
+
"query",
|
|
48
|
+
"query_stream",
|
|
49
|
+
"fetch",
|
|
50
|
+
"semantic.query",
|
|
51
|
+
"airway.run",
|
|
52
|
+
"warehouse.insert",
|
|
53
|
+
"warehouse.exec",
|
|
54
|
+
"warehouse.upsert",
|
|
55
|
+
"warehouse.query",
|
|
56
|
+
"tx.begin",
|
|
57
|
+
"tx.begin_oltp",
|
|
58
|
+
"tx.query",
|
|
59
|
+
"tx.exec",
|
|
60
|
+
"tx.commit",
|
|
61
|
+
"tx.rollback",
|
|
62
|
+
"oltp.query",
|
|
63
|
+
"oltp.exec",
|
|
64
|
+
"airhouse.query",
|
|
65
|
+
"airhouse.exec",
|
|
66
|
+
"airhouse.append",
|
|
67
|
+
"storage.getUploadUrl",
|
|
68
|
+
"storage.getDownloadUrl",
|
|
69
|
+
"storage.put",
|
|
70
|
+
"storage.get",
|
|
71
|
+
"storage.head",
|
|
72
|
+
"storage.list",
|
|
73
|
+
"storage.delete",
|
|
74
|
+
"storage.copy",
|
|
75
|
+
"secrets.set",
|
|
76
|
+
"email.send",
|
|
77
|
+
"org.people",
|
|
78
|
+
"org.places",
|
|
79
|
+
"org.assignments"
|
|
80
|
+
];
|
|
81
|
+
/**
|
|
82
|
+
* The surface `reply_json` prefixes a refusal with (`runtime.rs`): a thrown
|
|
83
|
+
* `HostError`'s message is `"<surface>: <host message>"`. Every `tx.*` verb —
|
|
84
|
+
* `begin_oltp` included — answers under `ctx.tx`, because one op carries all
|
|
85
|
+
* six.
|
|
86
|
+
*/
|
|
87
|
+
const SURFACES = {
|
|
88
|
+
query: "ctx.query",
|
|
89
|
+
queryStream: "ctx.queryStream",
|
|
90
|
+
fetch: "ctx.fetch",
|
|
91
|
+
warehouse: "ctx.warehouse",
|
|
92
|
+
tx: "ctx.tx",
|
|
93
|
+
oltp: "ctx.oltp",
|
|
94
|
+
airhouse: "ctx.airhouse",
|
|
95
|
+
secretsSet: "ctx.secrets.set",
|
|
96
|
+
emailSend: "ctx.email.send",
|
|
97
|
+
storage: "ctx.storage",
|
|
98
|
+
orgPeople: "ctx.org.people",
|
|
99
|
+
orgPlaces: "ctx.org.places",
|
|
100
|
+
orgAssignments: "ctx.org.assignments",
|
|
101
|
+
semanticQuery: "ctx.semantic.query",
|
|
102
|
+
airwayRun: "ctx.airway.run"
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Every refusal the context throws, quoted from the host. The context renders
|
|
106
|
+
* a template by substituting its `{placeholder}`s and prefixing the surface.
|
|
107
|
+
*/
|
|
108
|
+
const REFUSALS = {
|
|
109
|
+
secretsWrite: {
|
|
110
|
+
source: "host.rs",
|
|
111
|
+
refusal: "ctx.secrets.set: this function has not declared the `secrets.write` capability (add it to oxy-app.json to permit writes)"
|
|
112
|
+
},
|
|
113
|
+
emailSend: {
|
|
114
|
+
source: "host.rs",
|
|
115
|
+
refusal: "EmailCapabilityMissing: this function has not declared the `email.send` capability (add \"email\": { \"send\": true } to its oxy-app.json entry)"
|
|
116
|
+
},
|
|
117
|
+
orgPeople: {
|
|
118
|
+
source: "host.rs",
|
|
119
|
+
refusal: "OrgCapabilityMissing: this function has not declared the `org.read` capability (add \"org\": { \"read\": true } to its oxy-app.json entry)"
|
|
120
|
+
},
|
|
121
|
+
orgMember: {
|
|
122
|
+
source: "host.rs",
|
|
123
|
+
refusal: "OrgCapabilityMissing: this function has not declared the `org.read` capability (add \"org\": { \"read\": true } to its oxy-app.json entry) — ctx.org.{member}"
|
|
124
|
+
},
|
|
125
|
+
storageWrite: {
|
|
126
|
+
source: "host.rs",
|
|
127
|
+
refusal: "StorageCapabilityMissing: this function has not declared the `storage.write` capability (add \"storage\": { \"write\": true } to its oxy-app.json entry)"
|
|
128
|
+
},
|
|
129
|
+
storageRead: {
|
|
130
|
+
source: "host.rs",
|
|
131
|
+
refusal: "StorageCapabilityMissing: this function has not declared the `storage.read` capability (add \"storage\": { \"read\": true } to its oxy-app.json entry)"
|
|
132
|
+
},
|
|
133
|
+
oltp: {
|
|
134
|
+
source: "host.rs",
|
|
135
|
+
refusal: "OltpCapabilityMissing: this function has not declared the `oltp` capability (add \"oltp\": { \"enabled\": true } to its oxy-app.json entry). The schema it writes is derived from the app's own slug — the manifest only enables access."
|
|
136
|
+
},
|
|
137
|
+
airhouse: {
|
|
138
|
+
source: "host/airhouse_ops.rs",
|
|
139
|
+
refusal: "AirhouseCapabilityMissing: this function has not declared the `airhouse` capability (add \"airhouse\": { \"enabled\": true } to its oxy-app.json entry). The schema it writes is derived from the app's own slug — the manifest only enables access."
|
|
140
|
+
},
|
|
141
|
+
destinationNotDeclared: {
|
|
142
|
+
source: "host.rs",
|
|
143
|
+
refusal: "database '{database}' is not in this function's `destinations` allowlist (declare it in oxy-app.json to permit writes)"
|
|
144
|
+
},
|
|
145
|
+
databaseNotConfigured: {
|
|
146
|
+
source: "host.rs",
|
|
147
|
+
refusal: "database '{database}' is not configured for this project"
|
|
148
|
+
},
|
|
149
|
+
managedOltpWrite: {
|
|
150
|
+
source: "host/destinations.rs",
|
|
151
|
+
refusal: "database '{database}' is the org's OLTP store, which this surface reaches as the read-only analyst — write this app's own records with ctx.oltp"
|
|
152
|
+
},
|
|
153
|
+
customerWarehouseWrite: {
|
|
154
|
+
source: "host/destinations.rs",
|
|
155
|
+
refusal: "database '{database}' is a customer warehouse, and customer warehouses are read-only to apps. Facts this app records belong in Airhouse (ctx.airhouse); records it edits belong in ctx.oltp. If this write has to stay, say why on the function in oxy-app.json: \"customerWarehouseWrites\": { \"{database}\": \"<reason>\" }"
|
|
156
|
+
},
|
|
157
|
+
customerWarehouseTransaction: {
|
|
158
|
+
source: "host/destinations.rs",
|
|
159
|
+
refusal: ". A ctx.tx transaction counts as a write even when it only reads — read with ctx.warehouse.query instead"
|
|
160
|
+
},
|
|
161
|
+
upsertUnsupported: {
|
|
162
|
+
source: "upsert_support.rs",
|
|
163
|
+
refusal: "warehouse.upsert is not supported on {dialect}: it compiles to `INSERT … ON CONFLICT … DO UPDATE`, which only Postgres and DuckDB parse. Use warehouse.insert, or warehouse.exec with this warehouse's own upsert statement."
|
|
164
|
+
},
|
|
165
|
+
transactionOpen: {
|
|
166
|
+
source: "host.rs",
|
|
167
|
+
refusal: "could not open a transaction on '{database}': {e}"
|
|
168
|
+
},
|
|
169
|
+
transactionUnsupported: {
|
|
170
|
+
source: "transaction.rs",
|
|
171
|
+
refusal: "{backend} does not support multi-statement transactions — ctx.tx() requires a Postgres-backed database (`type: postgres`). Use ctx.warehouse.{insert,exec,upsert} for single-statement writes."
|
|
172
|
+
},
|
|
173
|
+
transactionsOpen: {
|
|
174
|
+
source: "tx.rs",
|
|
175
|
+
refusal: "this invocation already has {MAX_OPEN} transactions open. Commit or roll one back before opening another."
|
|
176
|
+
},
|
|
177
|
+
transactionFinished: {
|
|
178
|
+
source: "runtime.rs",
|
|
179
|
+
refusal: "{label}: this transaction is already finished — {what} was called after the callback returned"
|
|
180
|
+
},
|
|
181
|
+
transactionFnNotFunction: {
|
|
182
|
+
source: "runtime.rs",
|
|
183
|
+
refusal: "{signature}: fn must be a function"
|
|
184
|
+
},
|
|
185
|
+
btoaBytes: {
|
|
186
|
+
source: "runtime.rs",
|
|
187
|
+
refusal: "btoa: expected a string. For bytes use bytesToBase64() from @oxy-hq/sdk"
|
|
188
|
+
},
|
|
189
|
+
atobPadding: {
|
|
190
|
+
source: "runtime.rs",
|
|
191
|
+
refusal: "atob: '=' may only appear as trailing padding"
|
|
192
|
+
},
|
|
193
|
+
atobLength: {
|
|
194
|
+
source: "runtime.rs",
|
|
195
|
+
refusal: "atob: invalid base64 length"
|
|
196
|
+
},
|
|
197
|
+
fetchInvalidUrl: {
|
|
198
|
+
source: "host.rs",
|
|
199
|
+
refusal: "invalid url: {e}"
|
|
200
|
+
},
|
|
201
|
+
fetchBlocked: {
|
|
202
|
+
source: "host.rs",
|
|
203
|
+
refusal: "fetch to '{url}' blocked by SSRF allowlist"
|
|
204
|
+
},
|
|
205
|
+
fetchInvalidHeaderName: {
|
|
206
|
+
source: "host.rs",
|
|
207
|
+
refusal: "InvalidFetchHeader: `{k}` is not a header name"
|
|
208
|
+
},
|
|
209
|
+
fetchInvalidHeaderValue: {
|
|
210
|
+
source: "host.rs",
|
|
211
|
+
refusal: "InvalidFetchHeader: the value of `{k}` is not a header value"
|
|
212
|
+
},
|
|
213
|
+
fetchTooLarge: {
|
|
214
|
+
source: "host.rs",
|
|
215
|
+
refusal: "response too large ({len} bytes > {max_bytes} cap)"
|
|
216
|
+
},
|
|
217
|
+
slugCannotBackASchema: {
|
|
218
|
+
source: "host.rs",
|
|
219
|
+
refusal: "the capability is enabled, but this app's slug '{slug}' cannot back an {store} schema: to do so a slug must start with a letter, be at most {max} characters, and use only lowercase letters, digits and hyphens (a `_` is refused — it would collide with the hyphenated form). A leading digit is a legal app slug but not a legal schema name. Rename the app to one that qualifies."
|
|
220
|
+
},
|
|
221
|
+
oltpParamsNotArray: {
|
|
222
|
+
source: "host.rs",
|
|
223
|
+
refusal: "`params` must be an array of values. Pass positional arguments for $1, $2, … — e.g. [name, partySize]."
|
|
224
|
+
},
|
|
225
|
+
noDefaultDatabase: {
|
|
226
|
+
source: "host.rs",
|
|
227
|
+
refusal: "this project has no databases configured"
|
|
228
|
+
},
|
|
229
|
+
oltpUnsupportedColumn: {
|
|
230
|
+
source: "postgres_tx/convert.rs",
|
|
231
|
+
refusal: "result column `{name}` has Postgres type `{ty}`, which cannot be returned directly. Cast it to text in the SELECT list and parse it in your function — cast the whole expression, not the output name (`avg(qty)::text`, `amount::text`). Note `avg`/`sum` over any numeric type, and a bare decimal literal, are all `numeric`. (Supported as-is: bool, int2/4/8, float4/8, text/varchar/char/name, json/jsonb, uuid, timestamptz/timestamp, date, time.)"
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* Every gate in `FunctionCapabilities`, in the struct's order. `storage_read`
|
|
236
|
+
* and `storage_write` list `check_storage_capability`'s arms (`copy` needs
|
|
237
|
+
* both); `customer_warehouse_writes` is decided per database by
|
|
238
|
+
* `destination_write_policy`, after the `destinations` allowlist.
|
|
239
|
+
* `storage_retention` and `fetch_max_bytes` are fields of the struct and
|
|
240
|
+
* deliberately absent: they shape an allowed call, they never refuse one.
|
|
241
|
+
*/
|
|
242
|
+
const GATES = [
|
|
243
|
+
{
|
|
244
|
+
hostField: "secrets_write",
|
|
245
|
+
manifest: "secrets.write",
|
|
246
|
+
ops: ["secrets.set"]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
hostField: "email_send",
|
|
250
|
+
manifest: "email.send",
|
|
251
|
+
ops: ["email.send"]
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
hostField: "org_read",
|
|
255
|
+
manifest: "org.read",
|
|
256
|
+
ops: [
|
|
257
|
+
"org.people",
|
|
258
|
+
"org.places",
|
|
259
|
+
"org.assignments"
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
hostField: "storage_read",
|
|
264
|
+
manifest: "storage.read",
|
|
265
|
+
ops: [
|
|
266
|
+
"storage.getDownloadUrl",
|
|
267
|
+
"storage.get",
|
|
268
|
+
"storage.head",
|
|
269
|
+
"storage.list",
|
|
270
|
+
"storage.copy"
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
hostField: "storage_write",
|
|
275
|
+
manifest: "storage.write",
|
|
276
|
+
ops: [
|
|
277
|
+
"storage.getUploadUrl",
|
|
278
|
+
"storage.put",
|
|
279
|
+
"storage.delete",
|
|
280
|
+
"storage.copy"
|
|
281
|
+
]
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
hostField: "oltp",
|
|
285
|
+
manifest: "oltp.enabled",
|
|
286
|
+
ops: [
|
|
287
|
+
"oltp.query",
|
|
288
|
+
"oltp.exec",
|
|
289
|
+
"tx.begin_oltp"
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
hostField: "airhouse",
|
|
294
|
+
manifest: "airhouse.enabled",
|
|
295
|
+
ops: [
|
|
296
|
+
"airhouse.query",
|
|
297
|
+
"airhouse.exec",
|
|
298
|
+
"airhouse.append"
|
|
299
|
+
]
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
hostField: "customer_warehouse_writes",
|
|
303
|
+
manifest: "customerWarehouseWrites",
|
|
304
|
+
ops: [
|
|
305
|
+
"warehouse.insert",
|
|
306
|
+
"warehouse.exec",
|
|
307
|
+
"warehouse.upsert",
|
|
308
|
+
"tx.begin"
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
];
|
|
312
|
+
/**
|
|
313
|
+
* The globals the isolate does not define, as `oxyc`'s `isolate-global` lint
|
|
314
|
+
* names them (`ABSENT_GLOBALS` in `sdk/cli/src/publish/function-lint.ts`).
|
|
315
|
+
* `member` narrows an entry to `<name>.<member>`; an empty `member` is the
|
|
316
|
+
* whole of `process.*`. The isolate's bootstrap defines `Response`, `btoa`,
|
|
317
|
+
* `atob`, `console` and `__buildCtx` and nothing else (`runtime.rs`), so it has
|
|
318
|
+
* no `crypto` global at all; the lint singles out `subtle` because that is what
|
|
319
|
+
* an author reaches for, and `run()` removes the whole global as production
|
|
320
|
+
* does.
|
|
321
|
+
*/
|
|
322
|
+
const ABSENT_GLOBALS = [
|
|
323
|
+
{ name: "Buffer" },
|
|
324
|
+
{ name: "TextEncoder" },
|
|
325
|
+
{ name: "TextDecoder" },
|
|
326
|
+
{ name: "Blob" },
|
|
327
|
+
{ name: "File" },
|
|
328
|
+
{ name: "FormData" },
|
|
329
|
+
{
|
|
330
|
+
name: "crypto",
|
|
331
|
+
member: "subtle"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "process",
|
|
335
|
+
member: ""
|
|
336
|
+
}
|
|
337
|
+
];
|
|
338
|
+
/**
|
|
339
|
+
* `is_safe_outbound` in `host.rs`, the first-layer check every `ctx.fetch`
|
|
340
|
+
* URL meets before anything is sent: the scheme, the host names refused by
|
|
341
|
+
* name, and the suffixes an internal name ends with. The IP rules (loopback,
|
|
342
|
+
* unspecified, private, link-local, broadcast, documentation, v4-mapped and
|
|
343
|
+
* embedded v6) are code, in `fetch-rules.ts`. What no unit test can reproduce
|
|
344
|
+
* is the second layer — a public name that RESOLVES to a private address is
|
|
345
|
+
* caught at connect time by `PublicOnlyDnsResolver` — so the context does not
|
|
346
|
+
* pretend to.
|
|
347
|
+
*/
|
|
348
|
+
const FETCH_RULES = {
|
|
349
|
+
scheme: "https",
|
|
350
|
+
literalHosts: [
|
|
351
|
+
"localhost",
|
|
352
|
+
"ip6-localhost",
|
|
353
|
+
"ip6-loopback"
|
|
354
|
+
],
|
|
355
|
+
internalSuffixes: [
|
|
356
|
+
".internal",
|
|
357
|
+
".local",
|
|
358
|
+
".localdomain",
|
|
359
|
+
".svc",
|
|
360
|
+
".cluster.local"
|
|
361
|
+
]
|
|
362
|
+
};
|
|
363
|
+
/** `FETCH_MAX_BYTES` in `host.rs`: the default `ctx.fetch` response ceiling. */
|
|
364
|
+
const FETCH_MAX_BYTES = 10485760;
|
|
365
|
+
/** `MAX_OPEN` in `tx.rs`: transactions one invocation may hold open at once. */
|
|
366
|
+
const MAX_OPEN_TRANSACTIONS = 4;
|
|
367
|
+
/**
|
|
368
|
+
* How each dialect names itself in a refusal — `SqlDialect::as_str` in the
|
|
369
|
+
* connector crate, which `upsert_support::check` and `transaction::unsupported`
|
|
370
|
+
* format in. Only Postgres, DuckDB and SQLite parse `ON CONFLICT`
|
|
371
|
+
* (`parses_on_conflict`), and only the Postgres connector opens a transaction.
|
|
372
|
+
*/
|
|
373
|
+
const DIALECTS = {
|
|
374
|
+
clickhouse: {
|
|
375
|
+
name: "ClickHouse",
|
|
376
|
+
parsesOnConflict: false,
|
|
377
|
+
opensTransactions: false
|
|
378
|
+
},
|
|
379
|
+
postgres: {
|
|
380
|
+
name: "PostgreSQL",
|
|
381
|
+
parsesOnConflict: true,
|
|
382
|
+
opensTransactions: true
|
|
383
|
+
},
|
|
384
|
+
duckdb: {
|
|
385
|
+
name: "DuckDB",
|
|
386
|
+
parsesOnConflict: true,
|
|
387
|
+
opensTransactions: false
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/testing/fetch-rules.ts
|
|
393
|
+
/** The host's verdict on a URL: allowed, or refused before sending. */
|
|
394
|
+
function isSafeOutbound(url) {
|
|
395
|
+
if (url.protocol !== `${FETCH_RULES.scheme}:`) return false;
|
|
396
|
+
const host = url.hostname;
|
|
397
|
+
if (host === "") return false;
|
|
398
|
+
const ip = parseIp(host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host);
|
|
399
|
+
if (ip) return isPublicIp(ip);
|
|
400
|
+
const lower = host.toLowerCase();
|
|
401
|
+
if (FETCH_RULES.literalHosts.includes(lower)) return false;
|
|
402
|
+
return !FETCH_RULES.internalSuffixes.some((suffix) => lower.endsWith(suffix));
|
|
403
|
+
}
|
|
404
|
+
/** A literal IPv4 or IPv6 address, or `null` when `s` is a name. */
|
|
405
|
+
function parseIp(s) {
|
|
406
|
+
const v4 = parseV4(s);
|
|
407
|
+
if (v4) return { v4 };
|
|
408
|
+
const v6 = parseV6(s);
|
|
409
|
+
return v6 ? { v6 } : null;
|
|
410
|
+
}
|
|
411
|
+
function parseV4(s) {
|
|
412
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(s);
|
|
413
|
+
if (!m) return null;
|
|
414
|
+
const parts = m.slice(1).map(Number);
|
|
415
|
+
if (parts.some((p) => p > 255)) return null;
|
|
416
|
+
return parts;
|
|
417
|
+
}
|
|
418
|
+
/** Eight 16-bit segments, or `null`. Accepts `::`, and a dotted v4 tail. */
|
|
419
|
+
function parseV6(s) {
|
|
420
|
+
if (!s.includes(":")) return null;
|
|
421
|
+
const zone = s.indexOf("%");
|
|
422
|
+
const halves = (zone >= 0 ? s.slice(0, zone) : s).split("::");
|
|
423
|
+
if (halves.length > 2) return null;
|
|
424
|
+
const head = halves[0] === "" ? [] : halves[0].split(":");
|
|
425
|
+
const tail = halves.length === 2 && halves[1] !== "" ? halves[1].split(":") : [];
|
|
426
|
+
const words = (groups) => {
|
|
427
|
+
const out = [];
|
|
428
|
+
for (const [i, g] of groups.entries()) if (g.includes(".")) {
|
|
429
|
+
if (i !== groups.length - 1) return null;
|
|
430
|
+
const v4 = parseV4(g);
|
|
431
|
+
if (!v4) return null;
|
|
432
|
+
out.push(v4[0] << 8 | v4[1], v4[2] << 8 | v4[3]);
|
|
433
|
+
} else {
|
|
434
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(g)) return null;
|
|
435
|
+
out.push(Number.parseInt(g, 16));
|
|
436
|
+
}
|
|
437
|
+
return out;
|
|
438
|
+
};
|
|
439
|
+
const h = words(head);
|
|
440
|
+
const t = words(tail);
|
|
441
|
+
if (!h || !t) return null;
|
|
442
|
+
if (halves.length === 1) return h.length === 8 ? h : null;
|
|
443
|
+
const fill = 8 - h.length - t.length;
|
|
444
|
+
if (fill < 1) return null;
|
|
445
|
+
return [
|
|
446
|
+
...h,
|
|
447
|
+
...new Array(fill).fill(0),
|
|
448
|
+
...t
|
|
449
|
+
];
|
|
450
|
+
}
|
|
451
|
+
/** `is_public_ip` in `host.rs`. */
|
|
452
|
+
function isPublicIp(ip) {
|
|
453
|
+
const canonical = "v6" in ip ? mappedV4(ip.v6) ?? ip : ip;
|
|
454
|
+
if ("v4" in canonical) return isPublicV4(canonical.v4);
|
|
455
|
+
const seg = canonical.v6;
|
|
456
|
+
if (seg.every((w) => w === 0)) return false;
|
|
457
|
+
if (seg.slice(0, 7).every((w) => w === 0) && seg[7] === 1) return false;
|
|
458
|
+
const embedded = embeddedV4(seg);
|
|
459
|
+
if (embedded) return isPublicV4(embedded);
|
|
460
|
+
const first = seg[0];
|
|
461
|
+
const linkLocal = (first & 65472) === 65152;
|
|
462
|
+
const uniqueLocal = (first & 65024) === 64512;
|
|
463
|
+
return !linkLocal && !uniqueLocal;
|
|
464
|
+
}
|
|
465
|
+
function isPublicV4([a, b, c, d]) {
|
|
466
|
+
if (a === 127) return false;
|
|
467
|
+
if (a === 0 && b === 0 && c === 0 && d === 0) return false;
|
|
468
|
+
return !(a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168) && !(a === 169 && b === 254) && !(a === 255 && b === 255 && c === 255 && d === 255) && !(a === 192 && b === 0 && c === 2 || a === 198 && b === 51 && c === 100 || a === 203 && b === 0 && c === 113);
|
|
469
|
+
}
|
|
470
|
+
/** `to_canonical()`: an IPv4-mapped address (`::ffff:a.b.c.d`) as its v4. */
|
|
471
|
+
function mappedV4(seg) {
|
|
472
|
+
if (seg.slice(0, 5).every((w) => w === 0) && seg[5] === 65535) return { v4: [
|
|
473
|
+
seg[6] >> 8,
|
|
474
|
+
seg[6] & 255,
|
|
475
|
+
seg[7] >> 8,
|
|
476
|
+
seg[7] & 255
|
|
477
|
+
] };
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
/** `embedded_ipv4`: the v4 in a `::/96` or `64:ff9b::/96` address. */
|
|
481
|
+
function embeddedV4(seg) {
|
|
482
|
+
const compatible = seg.slice(0, 6).every((w) => w === 0);
|
|
483
|
+
const nat64 = seg[0] === 100 && seg[1] === 65435 && seg.slice(2, 6).every((w) => w === 0);
|
|
484
|
+
if (!compatible && !nat64) return null;
|
|
485
|
+
return [
|
|
486
|
+
seg[6] >> 8,
|
|
487
|
+
seg[6] & 255,
|
|
488
|
+
seg[7] >> 8,
|
|
489
|
+
seg[7] & 255
|
|
490
|
+
];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/testing/host-error.ts
|
|
495
|
+
/** The `name` the runtime gives every refusal it unwraps. */
|
|
496
|
+
const HOST_ERROR_NAME = "HostError";
|
|
497
|
+
/**
|
|
498
|
+
* Render a refusal template: every `{placeholder}` replaced by `vars[name]`.
|
|
499
|
+
* A placeholder the caller did not supply is a bug in the context, not in the
|
|
500
|
+
* app under test, so it throws rather than leaking `{database}` into a message
|
|
501
|
+
* a test might then match on.
|
|
502
|
+
*/
|
|
503
|
+
function render(template, vars = {}) {
|
|
504
|
+
return template.refusal.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (whole, name) => {
|
|
505
|
+
const value = vars[name];
|
|
506
|
+
if (value === void 0) throw new Error(`test context bug: refusal template leaves {${name}} unfilled: ${whole}`);
|
|
507
|
+
return String(value);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The error a function sees when the host refuses: `name` `"HostError"`,
|
|
512
|
+
* message `"<surface>: <message>"`.
|
|
513
|
+
*/
|
|
514
|
+
function hostError(surface, message) {
|
|
515
|
+
const err = /* @__PURE__ */ new Error(`${surface}: ${message}`);
|
|
516
|
+
err.name = HOST_ERROR_NAME;
|
|
517
|
+
return err;
|
|
518
|
+
}
|
|
519
|
+
/** `hostError` over a rendered template. */
|
|
520
|
+
function refuse(surface, template, vars = {}) {
|
|
521
|
+
return hostError(surface, render(template, vars));
|
|
522
|
+
}
|
|
523
|
+
/** Whether `err` is a refusal the context (or the runtime) threw. */
|
|
524
|
+
function isHostError(err) {
|
|
525
|
+
return err instanceof Error && err.name === "HostError";
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* An error in the context's OWN words — never the host's. Thrown where the
|
|
529
|
+
* context cannot model what the host would do (an unrecognised statement, an
|
|
530
|
+
* unregistered `fetch` answer) and says so, rather than answering something
|
|
531
|
+
* production would not. Its `name` is `"TestContextError"` so a test can never
|
|
532
|
+
* mistake it for a refusal.
|
|
533
|
+
*/
|
|
534
|
+
function contextError(message) {
|
|
535
|
+
const err = /* @__PURE__ */ new Error(`@oxy-hq/sdk/testing: ${message}`);
|
|
536
|
+
err.name = "TestContextError";
|
|
537
|
+
return err;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/testing/gates.ts
|
|
542
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
543
|
+
/** `spec.<a>.<b> === true` — `flag` in `capabilities.ts`. */
|
|
544
|
+
function flag(spec, a, b) {
|
|
545
|
+
const block = spec[a];
|
|
546
|
+
return isObject(block) && block[b] === true;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* `MAX_NAME_LEN` in `crates/oltp/src/schema.rs`: 63 (Postgres) less `app_` and
|
|
550
|
+
* the longest role suffix `_rw`.
|
|
551
|
+
*/
|
|
552
|
+
const MAX_SCHEMA_NAME_LEN = 56;
|
|
553
|
+
/**
|
|
554
|
+
* `app_writer_name` + `WriterRef::schema_name`: the schema an app's slug
|
|
555
|
+
* derives, or `null` when it derives none.
|
|
556
|
+
*
|
|
557
|
+
* Both halves matter. `app_writer_name` refuses a slug containing `_` outright
|
|
558
|
+
* — `my_app` would otherwise alias the hyphenated sibling `my-app` onto one
|
|
559
|
+
* schema and role — and then runs the derived name through `validate_name`,
|
|
560
|
+
* which is what rejects a leading digit, an over-long name, and any character
|
|
561
|
+
* outside `[a-z0-9_]`. Skipping that second half handed back a schema for slugs
|
|
562
|
+
* the host answers `None` for, which is the difference between a green test and
|
|
563
|
+
* a closed `ctx.oltp` in production.
|
|
564
|
+
*/
|
|
565
|
+
function writerSchema(slug) {
|
|
566
|
+
if (slug.includes("_")) return null;
|
|
567
|
+
const name = slug.replace(/-/g, "_");
|
|
568
|
+
if (name === "" || name.length > 56) return null;
|
|
569
|
+
if (!/^[a-z]/.test(name)) return null;
|
|
570
|
+
if (!/^[a-z0-9_]+$/.test(name)) return null;
|
|
571
|
+
return `app_${name}`;
|
|
572
|
+
}
|
|
573
|
+
/** Read the gates for `functionName` from `manifest`, or throw naming what is missing. */
|
|
574
|
+
function readGates(manifest, functionName) {
|
|
575
|
+
const spec = manifest.functions?.[functionName];
|
|
576
|
+
if (!isObject(spec)) {
|
|
577
|
+
const known = Object.keys(manifest.functions ?? {});
|
|
578
|
+
throw contextError(`oxy-app.json declares no function "${functionName}" (functions: ${known.length ? known.join(", ") : "none"})`);
|
|
579
|
+
}
|
|
580
|
+
const slug = typeof manifest.slug === "string" ? manifest.slug : "";
|
|
581
|
+
const destinations = Array.isArray(spec.destinations) ? spec.destinations.filter((d) => typeof d === "string") : [];
|
|
582
|
+
const customerWarehouseWrites = {};
|
|
583
|
+
if (isObject(spec.customerWarehouseWrites)) {
|
|
584
|
+
for (const [db, reason] of Object.entries(spec.customerWarehouseWrites)) if (typeof reason === "string" && reason.trim() !== "") customerWarehouseWrites[db] = reason;
|
|
585
|
+
}
|
|
586
|
+
const fetch = isObject(spec.fetch) ? spec.fetch : {};
|
|
587
|
+
return {
|
|
588
|
+
functionName,
|
|
589
|
+
slug,
|
|
590
|
+
writerSchema: writerSchema(slug),
|
|
591
|
+
secretsWrite: flag(spec, "secrets", "write"),
|
|
592
|
+
emailSend: flag(spec, "email", "send"),
|
|
593
|
+
orgRead: flag(spec, "org", "read"),
|
|
594
|
+
storageRead: flag(spec, "storage", "read"),
|
|
595
|
+
storageWrite: flag(spec, "storage", "write"),
|
|
596
|
+
oltp: flag(spec, "oltp", "enabled"),
|
|
597
|
+
airhouse: flag(spec, "airhouse", "enabled"),
|
|
598
|
+
destinations,
|
|
599
|
+
customerWarehouseWrites,
|
|
600
|
+
fetchMaxBytes: typeof fetch.maxResponseBytes === "number" ? fetch.maxResponseBytes : null
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/testing/run.ts
|
|
606
|
+
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
607
|
+
const B64R = (/* @__PURE__ */ new Uint8Array(256)).fill(255);
|
|
608
|
+
for (let i = 0; i < 64; i++) B64R[B64.charCodeAt(i)] = i;
|
|
609
|
+
/** `globalThis.btoa` as the isolate defines it: strings only, Latin1 only. */
|
|
610
|
+
function isolateBtoa(input) {
|
|
611
|
+
if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) throw new TypeError(render(REFUSALS.btoaBytes));
|
|
612
|
+
const s = String(input);
|
|
613
|
+
let out = "";
|
|
614
|
+
for (let i = 0; i < s.length; i += 3) {
|
|
615
|
+
const c0 = s.charCodeAt(i);
|
|
616
|
+
const c1 = i + 1 < s.length ? s.charCodeAt(i + 1) : 0;
|
|
617
|
+
const c2 = i + 2 < s.length ? s.charCodeAt(i + 2) : 0;
|
|
618
|
+
if (c0 > 255 || c1 > 255 || c2 > 255) throw new TypeError("btoa: input contains characters outside the Latin1 range; for text pass it directly with { encoding: \"utf8\" }");
|
|
619
|
+
const n = c0 << 16 | c1 << 8 | c2;
|
|
620
|
+
out += B64[n >> 18 & 63] + B64[n >> 12 & 63] + (i + 1 < s.length ? B64[n >> 6 & 63] : "=") + (i + 2 < s.length ? B64[n & 63] : "=");
|
|
621
|
+
}
|
|
622
|
+
return out;
|
|
623
|
+
}
|
|
624
|
+
/** `globalThis.atob` as the isolate defines it: WHATWG forgiving-base64. */
|
|
625
|
+
function isolateAtob(input) {
|
|
626
|
+
let s = String(input).replace(/[ \t\n\f\r]/g, "");
|
|
627
|
+
if (s.length % 4 === 0) {
|
|
628
|
+
let pad = 0;
|
|
629
|
+
while (pad < 2 && s.charCodeAt(s.length - 1) === 61) {
|
|
630
|
+
s = s.slice(0, -1);
|
|
631
|
+
pad++;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (s.indexOf("=") >= 0) throw new TypeError(render(REFUSALS.atobPadding));
|
|
635
|
+
if (s.length % 4 === 1) throw new TypeError(render(REFUSALS.atobLength));
|
|
636
|
+
let out = "";
|
|
637
|
+
let buf = 0;
|
|
638
|
+
let bits = 0;
|
|
639
|
+
for (let i = 0; i < s.length; i++) {
|
|
640
|
+
const code = s.charCodeAt(i);
|
|
641
|
+
const v = code < 256 ? B64R[code] : 255;
|
|
642
|
+
if (v === 255) throw new TypeError(`atob: invalid base64 character '${s[i]}'`);
|
|
643
|
+
buf = buf << 6 | v;
|
|
644
|
+
bits += 6;
|
|
645
|
+
if (bits >= 8) {
|
|
646
|
+
bits -= 8;
|
|
647
|
+
out += String.fromCharCode(buf >> bits & 255);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return out;
|
|
651
|
+
}
|
|
652
|
+
let running = false;
|
|
653
|
+
/**
|
|
654
|
+
* Evaluate `fn` with every `ABSENT_GLOBALS` entry removed from `globalThis` and
|
|
655
|
+
* `btoa` / `atob` replaced by the isolate's, restoring all of them afterwards
|
|
656
|
+
* — whether `fn` resolves or throws.
|
|
657
|
+
*/
|
|
658
|
+
async function runWithoutIsolateGlobals(fn) {
|
|
659
|
+
if (running) throw contextError("t.run(fn) calls must not overlap — one at a time per test");
|
|
660
|
+
running = true;
|
|
661
|
+
const saved = [];
|
|
662
|
+
const g = globalThis;
|
|
663
|
+
const shadow = (name, value) => {
|
|
664
|
+
saved.push([name, Object.getOwnPropertyDescriptor(globalThis, name)]);
|
|
665
|
+
if (value === void 0) delete g[name];
|
|
666
|
+
else Object.defineProperty(globalThis, name, {
|
|
667
|
+
value,
|
|
668
|
+
configurable: true,
|
|
669
|
+
writable: true
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
try {
|
|
673
|
+
for (const entry of ABSENT_GLOBALS) shadow(entry.name, void 0);
|
|
674
|
+
shadow("btoa", isolateBtoa);
|
|
675
|
+
shadow("atob", isolateAtob);
|
|
676
|
+
return await fn();
|
|
677
|
+
} finally {
|
|
678
|
+
for (const [name, descriptor] of saved.reverse()) if (descriptor) Object.defineProperty(globalThis, name, descriptor);
|
|
679
|
+
else delete g[name];
|
|
680
|
+
running = false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/testing/sql.ts
|
|
686
|
+
const STOP_WORDS = /^(DEFAULT|NOT|NULL|PRIMARY|UNIQUE|REFERENCES|CHECK|GENERATED|CODEC|COMMENT|TTL)$/i;
|
|
687
|
+
const CONSTRAINT_HEAD = /^(PRIMARY|UNIQUE|CONSTRAINT|FOREIGN|CHECK|KEY|INDEX)\b/i;
|
|
688
|
+
/** `sql` without its leading line and block comments and whitespace. */
|
|
689
|
+
function stripLeadingComments(sql) {
|
|
690
|
+
let s = sql;
|
|
691
|
+
for (;;) {
|
|
692
|
+
const before = s;
|
|
693
|
+
s = s.replace(/^\s+/, "").replace(/^--[^\n]*\n?/, "").replace(/^\/\*[\s\S]*?\*\//, "");
|
|
694
|
+
if (s === before) return s;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/** Parse one statement, or throw in the context's own words. */
|
|
698
|
+
function parse(sql) {
|
|
699
|
+
const text = stripLeadingComments(sql).replace(/;\s*$/, "");
|
|
700
|
+
switch (/^(\w+)/.exec(text)?.[1]?.toUpperCase() ?? "") {
|
|
701
|
+
case "CREATE": return parseCreate(text);
|
|
702
|
+
case "INSERT": return parseInsert(text);
|
|
703
|
+
case "SELECT":
|
|
704
|
+
case "WITH": return parseSelect(text);
|
|
705
|
+
case "DELETE": return parseDelete(text);
|
|
706
|
+
case "UPDATE": return parseUpdate(text);
|
|
707
|
+
default: throw unsupported(text);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function unsupported(sql) {
|
|
711
|
+
const head = sql.trim().slice(0, 60).replace(/\s+/g, " ");
|
|
712
|
+
return contextError(`the test context cannot evaluate this statement: "${head}…". It reads CREATE TABLE, INSERT … VALUES, SELECT <columns|count(*)|literals> FROM <table> [WHERE col = value AND …] [ORDER BY col] [LIMIT n], DELETE FROM and UPDATE … SET. Answer the call with t.override(op, …), or seed the rows it should find with t.state.<store>.raw(table, rows).`);
|
|
713
|
+
}
|
|
714
|
+
/** Strip the quotes an identifier was written with. */
|
|
715
|
+
function ident(raw) {
|
|
716
|
+
const s = raw.trim();
|
|
717
|
+
if (s.startsWith("\"") && s.endsWith("\"") || s.startsWith("`") && s.endsWith("`")) return s.slice(1, -1);
|
|
718
|
+
return s;
|
|
719
|
+
}
|
|
720
|
+
/** Split `text` on top-level commas, respecting parentheses, brackets and quotes. */
|
|
721
|
+
function splitTopLevel(text, separator = ",") {
|
|
722
|
+
const out = [];
|
|
723
|
+
let depth = 0;
|
|
724
|
+
let quote = null;
|
|
725
|
+
let start = 0;
|
|
726
|
+
for (let i = 0; i < text.length; i++) {
|
|
727
|
+
const c = text[i];
|
|
728
|
+
if (quote) {
|
|
729
|
+
if (c === quote) {
|
|
730
|
+
if (text[i + 1] === quote) i++;
|
|
731
|
+
else quote = null;
|
|
732
|
+
}
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (c === "'" || c === "\"" || c === "`") quote = c;
|
|
736
|
+
else if (c === "(" || c === "[") depth++;
|
|
737
|
+
else if (c === ")" || c === "]") depth--;
|
|
738
|
+
else if (depth === 0 && text.startsWith(separator, i)) {
|
|
739
|
+
out.push(text.slice(start, i).trim());
|
|
740
|
+
i += separator.length - 1;
|
|
741
|
+
start = i + 1;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
out.push(text.slice(start).trim());
|
|
745
|
+
return out.filter((s) => s !== "");
|
|
746
|
+
}
|
|
747
|
+
/** The index just past the `)` matching the `(` at `open`. */
|
|
748
|
+
function closeParen(text, open) {
|
|
749
|
+
let depth = 0;
|
|
750
|
+
let quote = null;
|
|
751
|
+
for (let i = open; i < text.length; i++) {
|
|
752
|
+
const c = text[i];
|
|
753
|
+
if (quote) {
|
|
754
|
+
if (c === quote) {
|
|
755
|
+
if (text[i + 1] === quote) i++;
|
|
756
|
+
else quote = null;
|
|
757
|
+
}
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (c === "'" || c === "\"") quote = c;
|
|
761
|
+
else if (c === "(") depth++;
|
|
762
|
+
else if (c === ")" && --depth === 0) return i + 1;
|
|
763
|
+
}
|
|
764
|
+
throw unsupported(text);
|
|
765
|
+
}
|
|
766
|
+
function parseCreate(text) {
|
|
767
|
+
const m = /^CREATE\s+TABLE\s+(IF\s+NOT\s+EXISTS\s+)?([\w."`]+)\s*\(/i.exec(text);
|
|
768
|
+
if (!m) throw unsupported(text);
|
|
769
|
+
const open = m[0].length - 1;
|
|
770
|
+
const end = closeParen(text, open);
|
|
771
|
+
const columns = [];
|
|
772
|
+
for (const def of splitTopLevel(text.slice(open + 1, end - 1))) {
|
|
773
|
+
if (CONSTRAINT_HEAD.test(def)) continue;
|
|
774
|
+
const words = splitTopLevel(def, " ");
|
|
775
|
+
const name = ident(words[0] ?? "");
|
|
776
|
+
const typeWords = splitTopLevel(def.slice(def.indexOf(words[0] ?? "") + (words[0]?.length ?? 0)).trim(), " ");
|
|
777
|
+
const stop = typeWords.findIndex((w) => STOP_WORDS.test(w));
|
|
778
|
+
const type = (stop < 0 ? typeWords : typeWords.slice(0, stop)).join(" ");
|
|
779
|
+
if (name) columns.push({
|
|
780
|
+
name,
|
|
781
|
+
type
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
kind: "create",
|
|
786
|
+
table: ident(m[2]),
|
|
787
|
+
ifNotExists: Boolean(m[1]),
|
|
788
|
+
columns
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function parseInsert(text) {
|
|
792
|
+
const m = /^INSERT\s+INTO\s+([\w."`]+)\s*(\(([^)]*)\))?\s*VALUES\s*/i.exec(text);
|
|
793
|
+
if (!m) throw unsupported(text);
|
|
794
|
+
const columns = m[3] ? splitTopLevel(m[3]).map(ident) : null;
|
|
795
|
+
const tuples = [];
|
|
796
|
+
let at = m[0].length;
|
|
797
|
+
while (at < text.length) {
|
|
798
|
+
if (text[at] !== "(") throw unsupported(text);
|
|
799
|
+
const end = closeParen(text, at);
|
|
800
|
+
tuples.push(splitTopLevel(text.slice(at + 1, end - 1)));
|
|
801
|
+
at = end;
|
|
802
|
+
const gap = /^\s*,?\s*/.exec(text.slice(at))?.[0] ?? "";
|
|
803
|
+
at += gap.length;
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
kind: "insert",
|
|
807
|
+
table: ident(m[1]),
|
|
808
|
+
columns,
|
|
809
|
+
tuples
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
/** `a = 1 AND b = 'x' AND c IN (1, 2)` → predicates; anything else throws. */
|
|
813
|
+
function parseWhere(text, whole) {
|
|
814
|
+
if (!text) return [];
|
|
815
|
+
return splitTopLevel(text, " AND ").map((clause) => {
|
|
816
|
+
const eq = /^([\w."`]+)\s*=\s*(.+)$/s.exec(clause);
|
|
817
|
+
if (eq) return {
|
|
818
|
+
column: ident(eq[1]),
|
|
819
|
+
values: [eq[2].trim()]
|
|
820
|
+
};
|
|
821
|
+
const inList = /^([\w."`]+)\s+IN\s*\((.*)\)$/is.exec(clause);
|
|
822
|
+
if (inList) return {
|
|
823
|
+
column: ident(inList[1]),
|
|
824
|
+
values: splitTopLevel(inList[2])
|
|
825
|
+
};
|
|
826
|
+
throw contextError(`the test context cannot evaluate this WHERE clause: "${clause}". It reads "col = value" and "col IN (…)" joined by AND. Answer the call with t.override(op, …) instead: ${whole.slice(0, 80)}`);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Whether `literal()` will read this expression as a value rather than hand it
|
|
831
|
+
* back as its own SQL text. The two must agree: a projection the parser accepts
|
|
832
|
+
* as a literal but `literal()` cannot read comes back to the caller as the SQL
|
|
833
|
+
* they wrote — a column named `sum(amount)` holding the string `"sum(amount)"`,
|
|
834
|
+
* which is the one thing this module promises never to do.
|
|
835
|
+
*/
|
|
836
|
+
function isReadableLiteral(expr) {
|
|
837
|
+
const s = expr.trim();
|
|
838
|
+
return /^\$\d+$/.test(s) || /^(NULL|TRUE|FALSE)$/i.test(s) || s.startsWith("'") && s.endsWith("'") && s.length >= 2 || /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(s);
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* The statement with every quoted string blanked, so a keyword scan reads the
|
|
842
|
+
* SQL and not the data: `WHERE label = 'GROUP BY'` is a row, not a grouping.
|
|
843
|
+
*/
|
|
844
|
+
function withoutStringLiterals(text) {
|
|
845
|
+
return text.replace(/'(?:[^']|'')*'/g, "''");
|
|
846
|
+
}
|
|
847
|
+
function parseSelect(text) {
|
|
848
|
+
if (/\bGROUP\s+BY\b/is.test(withoutStringLiterals(text))) throw contextError(`the test context cannot evaluate GROUP BY: "${text.trim().slice(0, 60).replace(/\s+/g, " ")}…". It would have to aggregate, and a wrong aggregate reads like a right one. Answer the call with t.override(op, …) and assert on the arguments instead.`);
|
|
849
|
+
const m = /^SELECT\s+(.*?)(?:\s+FROM\s+([\w."`]+))?(?:\s+WHERE\s+(.*?))?(?:\s+ORDER\s+BY\s+([\w."`]+)(?:\s+(ASC|DESC))?)?(?:\s+LIMIT\s+(\d+))?\s*$/is.exec(text);
|
|
850
|
+
if (!m || !m[2] && /\bFROM\b/i.test(m[1])) throw unsupported(text);
|
|
851
|
+
const items = splitTopLevel(m[1]).map((item) => {
|
|
852
|
+
if (item === "*") return { kind: "all" };
|
|
853
|
+
const aliased = /^(.*?)\s+AS\s+([\w"`]+)$/is.exec(item);
|
|
854
|
+
const expr = (aliased ? aliased[1] : item).trim();
|
|
855
|
+
const alias = aliased ? ident(aliased[2]) : null;
|
|
856
|
+
if (/^count\s*\(\s*\*\s*\)$/i.test(expr)) return {
|
|
857
|
+
kind: "count",
|
|
858
|
+
alias: alias ?? "count(*)"
|
|
859
|
+
};
|
|
860
|
+
if (/^[\w."`]+$/.test(expr) && !/^\d/.test(expr) && !/^(true|false|null)$/i.test(expr)) return {
|
|
861
|
+
kind: "column",
|
|
862
|
+
column: ident(expr),
|
|
863
|
+
alias: alias ?? ident(expr)
|
|
864
|
+
};
|
|
865
|
+
if (!isReadableLiteral(expr)) throw unsupported(text);
|
|
866
|
+
return {
|
|
867
|
+
kind: "literal",
|
|
868
|
+
raw: expr,
|
|
869
|
+
alias: alias ?? expr
|
|
870
|
+
};
|
|
871
|
+
});
|
|
872
|
+
return {
|
|
873
|
+
kind: "select",
|
|
874
|
+
table: m[2] ? ident(m[2]) : null,
|
|
875
|
+
items,
|
|
876
|
+
where: parseWhere(m[3], text),
|
|
877
|
+
orderBy: m[4] ? {
|
|
878
|
+
column: ident(m[4]),
|
|
879
|
+
desc: (m[5] ?? "").toUpperCase() === "DESC"
|
|
880
|
+
} : null,
|
|
881
|
+
limit: m[6] ? Number(m[6]) : null
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function parseDelete(text) {
|
|
885
|
+
const m = /^DELETE\s+FROM\s+([\w."`]+)(?:\s+WHERE\s+(.*))?\s*$/is.exec(text);
|
|
886
|
+
if (!m) throw unsupported(text);
|
|
887
|
+
return {
|
|
888
|
+
kind: "delete",
|
|
889
|
+
table: ident(m[1]),
|
|
890
|
+
where: parseWhere(m[2], text)
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
function parseUpdate(text) {
|
|
894
|
+
const m = /^UPDATE\s+([\w."`]+)\s+SET\s+(.*?)(?:\s+WHERE\s+(.*))?\s*$/is.exec(text);
|
|
895
|
+
if (!m) throw unsupported(text);
|
|
896
|
+
const set = splitTopLevel(m[2]).map((assignment) => {
|
|
897
|
+
const eq = /^([\w."`]+)\s*=\s*(.+)$/s.exec(assignment);
|
|
898
|
+
if (!eq) throw unsupported(text);
|
|
899
|
+
const raw = eq[2].trim();
|
|
900
|
+
if (!isReadableLiteral(raw)) throw unsupported(text);
|
|
901
|
+
return {
|
|
902
|
+
column: ident(eq[1]),
|
|
903
|
+
raw
|
|
904
|
+
};
|
|
905
|
+
});
|
|
906
|
+
return {
|
|
907
|
+
kind: "update",
|
|
908
|
+
table: ident(m[1]),
|
|
909
|
+
set,
|
|
910
|
+
where: parseWhere(m[3], text)
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* A raw SQL value as a JS value, with no engine's rendering applied: a quoted
|
|
915
|
+
* string, a number, NULL, TRUE/FALSE, or a `$n` bound parameter. Anything else
|
|
916
|
+
* (a function call, a cast, an array literal) stays as its SQL text.
|
|
917
|
+
*/
|
|
918
|
+
function literal(raw, params = []) {
|
|
919
|
+
const s = raw.trim();
|
|
920
|
+
const param = /^\$(\d+)$/.exec(s);
|
|
921
|
+
if (param) return params[Number(param[1]) - 1];
|
|
922
|
+
if (/^NULL$/i.test(s)) return null;
|
|
923
|
+
if (/^TRUE$/i.test(s)) return true;
|
|
924
|
+
if (/^FALSE$/i.test(s)) return false;
|
|
925
|
+
if (s.startsWith("'") && s.endsWith("'")) return s.slice(1, -1).replace(/''/g, "'");
|
|
926
|
+
if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(s)) return Number(s);
|
|
927
|
+
return s;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
//#endregion
|
|
931
|
+
//#region src/testing/shape-zoo.json
|
|
932
|
+
var shape_zoo_default = {
|
|
933
|
+
version: 1,
|
|
934
|
+
engines: {
|
|
935
|
+
"clickhouse": { "cases": [
|
|
936
|
+
{
|
|
937
|
+
"key": "clickhouse/Bool/plain",
|
|
938
|
+
"native_type": "Bool",
|
|
939
|
+
"class": "plain",
|
|
940
|
+
"value_sql": "true",
|
|
941
|
+
"expect": { "warehouse": true }
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
"key": "clickhouse/Nullable(Bool)/null",
|
|
945
|
+
"native_type": "Nullable(Bool)",
|
|
946
|
+
"class": "null",
|
|
947
|
+
"value_sql": "NULL",
|
|
948
|
+
"expect": { "warehouse": null }
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
"key": "clickhouse/Boolean/plain",
|
|
952
|
+
"native_type": "Boolean",
|
|
953
|
+
"class": "plain",
|
|
954
|
+
"value_sql": "false",
|
|
955
|
+
"expect": { "warehouse": false }
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
"key": "clickhouse/Int8/plain",
|
|
959
|
+
"native_type": "Int8",
|
|
960
|
+
"class": "plain",
|
|
961
|
+
"value_sql": "42",
|
|
962
|
+
"expect": { "warehouse": 42 }
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
"key": "clickhouse/Int8/negative",
|
|
966
|
+
"native_type": "Int8",
|
|
967
|
+
"class": "negative",
|
|
968
|
+
"value_sql": "-128",
|
|
969
|
+
"expect": { "warehouse": -128 }
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
"key": "clickhouse/Int8/zero",
|
|
973
|
+
"native_type": "Int8",
|
|
974
|
+
"class": "zero",
|
|
975
|
+
"value_sql": "0",
|
|
976
|
+
"expect": { "warehouse": 0 }
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
"key": "clickhouse/Nullable(Int8)/null",
|
|
980
|
+
"native_type": "Nullable(Int8)",
|
|
981
|
+
"class": "null",
|
|
982
|
+
"value_sql": "NULL",
|
|
983
|
+
"expect": { "warehouse": null }
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
"key": "clickhouse/Int16/plain",
|
|
987
|
+
"native_type": "Int16",
|
|
988
|
+
"class": "plain",
|
|
989
|
+
"value_sql": "42",
|
|
990
|
+
"expect": { "warehouse": 42 }
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
"key": "clickhouse/Int16/negative",
|
|
994
|
+
"native_type": "Int16",
|
|
995
|
+
"class": "negative",
|
|
996
|
+
"value_sql": "-32768",
|
|
997
|
+
"expect": { "warehouse": -32768 }
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
"key": "clickhouse/Int16/zero",
|
|
1001
|
+
"native_type": "Int16",
|
|
1002
|
+
"class": "zero",
|
|
1003
|
+
"value_sql": "0",
|
|
1004
|
+
"expect": { "warehouse": 0 }
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
"key": "clickhouse/Nullable(Int16)/null",
|
|
1008
|
+
"native_type": "Nullable(Int16)",
|
|
1009
|
+
"class": "null",
|
|
1010
|
+
"value_sql": "NULL",
|
|
1011
|
+
"expect": { "warehouse": null }
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
"key": "clickhouse/Int32/plain",
|
|
1015
|
+
"native_type": "Int32",
|
|
1016
|
+
"class": "plain",
|
|
1017
|
+
"value_sql": "42",
|
|
1018
|
+
"expect": { "warehouse": 42 }
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
"key": "clickhouse/Int32/negative",
|
|
1022
|
+
"native_type": "Int32",
|
|
1023
|
+
"class": "negative",
|
|
1024
|
+
"value_sql": "-2147483648",
|
|
1025
|
+
"expect": { "warehouse": -2147483648 }
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
"key": "clickhouse/Int32/zero",
|
|
1029
|
+
"native_type": "Int32",
|
|
1030
|
+
"class": "zero",
|
|
1031
|
+
"value_sql": "0",
|
|
1032
|
+
"expect": { "warehouse": 0 }
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
"key": "clickhouse/Nullable(Int32)/null",
|
|
1036
|
+
"native_type": "Nullable(Int32)",
|
|
1037
|
+
"class": "null",
|
|
1038
|
+
"value_sql": "NULL",
|
|
1039
|
+
"expect": { "warehouse": null }
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
"key": "clickhouse/Int64/plain",
|
|
1043
|
+
"native_type": "Int64",
|
|
1044
|
+
"class": "plain",
|
|
1045
|
+
"value_sql": "42",
|
|
1046
|
+
"expect": { "warehouse": 42 }
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
"key": "clickhouse/Int64/negative",
|
|
1050
|
+
"native_type": "Int64",
|
|
1051
|
+
"class": "negative",
|
|
1052
|
+
"value_sql": "-9007199254740991",
|
|
1053
|
+
"expect": { "warehouse": -9007199254740991 }
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
"key": "clickhouse/Int64/zero",
|
|
1057
|
+
"native_type": "Int64",
|
|
1058
|
+
"class": "zero",
|
|
1059
|
+
"value_sql": "0",
|
|
1060
|
+
"expect": { "warehouse": 0 }
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
"key": "clickhouse/Int64/precision_gt_18",
|
|
1064
|
+
"native_type": "Int64",
|
|
1065
|
+
"class": "precision_gt_18",
|
|
1066
|
+
"value_sql": "9223372036854775807",
|
|
1067
|
+
"expect": { "warehouse": 0x8000000000000000 }
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
"key": "clickhouse/Nullable(Int64)/null",
|
|
1071
|
+
"native_type": "Nullable(Int64)",
|
|
1072
|
+
"class": "null",
|
|
1073
|
+
"value_sql": "NULL",
|
|
1074
|
+
"expect": { "warehouse": null }
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
"key": "clickhouse/UInt8/plain",
|
|
1078
|
+
"native_type": "UInt8",
|
|
1079
|
+
"class": "plain",
|
|
1080
|
+
"value_sql": "42",
|
|
1081
|
+
"expect": { "warehouse": 42 }
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
"key": "clickhouse/UInt8/zero",
|
|
1085
|
+
"native_type": "UInt8",
|
|
1086
|
+
"class": "zero",
|
|
1087
|
+
"value_sql": "0",
|
|
1088
|
+
"expect": { "warehouse": 0 }
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
"key": "clickhouse/Nullable(UInt8)/null",
|
|
1092
|
+
"native_type": "Nullable(UInt8)",
|
|
1093
|
+
"class": "null",
|
|
1094
|
+
"value_sql": "NULL",
|
|
1095
|
+
"expect": { "warehouse": null }
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
"key": "clickhouse/UInt16/plain",
|
|
1099
|
+
"native_type": "UInt16",
|
|
1100
|
+
"class": "plain",
|
|
1101
|
+
"value_sql": "42",
|
|
1102
|
+
"expect": { "warehouse": 42 }
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
"key": "clickhouse/UInt16/zero",
|
|
1106
|
+
"native_type": "UInt16",
|
|
1107
|
+
"class": "zero",
|
|
1108
|
+
"value_sql": "0",
|
|
1109
|
+
"expect": { "warehouse": 0 }
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
"key": "clickhouse/Nullable(UInt16)/null",
|
|
1113
|
+
"native_type": "Nullable(UInt16)",
|
|
1114
|
+
"class": "null",
|
|
1115
|
+
"value_sql": "NULL",
|
|
1116
|
+
"expect": { "warehouse": null }
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
"key": "clickhouse/UInt32/plain",
|
|
1120
|
+
"native_type": "UInt32",
|
|
1121
|
+
"class": "plain",
|
|
1122
|
+
"value_sql": "42",
|
|
1123
|
+
"expect": { "warehouse": 42 }
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
"key": "clickhouse/UInt32/zero",
|
|
1127
|
+
"native_type": "UInt32",
|
|
1128
|
+
"class": "zero",
|
|
1129
|
+
"value_sql": "0",
|
|
1130
|
+
"expect": { "warehouse": 0 }
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
"key": "clickhouse/Nullable(UInt32)/null",
|
|
1134
|
+
"native_type": "Nullable(UInt32)",
|
|
1135
|
+
"class": "null",
|
|
1136
|
+
"value_sql": "NULL",
|
|
1137
|
+
"expect": { "warehouse": null }
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
"key": "clickhouse/UInt64/plain",
|
|
1141
|
+
"native_type": "UInt64",
|
|
1142
|
+
"class": "plain",
|
|
1143
|
+
"value_sql": "42",
|
|
1144
|
+
"expect": { "warehouse": 42 }
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
"key": "clickhouse/UInt64/zero",
|
|
1148
|
+
"native_type": "UInt64",
|
|
1149
|
+
"class": "zero",
|
|
1150
|
+
"value_sql": "0",
|
|
1151
|
+
"expect": { "warehouse": 0 }
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
"key": "clickhouse/UInt64/precision_gt_18",
|
|
1155
|
+
"native_type": "UInt64",
|
|
1156
|
+
"class": "precision_gt_18",
|
|
1157
|
+
"value_sql": "18446744073709551615",
|
|
1158
|
+
"expect": { "warehouse": "18446744073709551615" }
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
"key": "clickhouse/Nullable(UInt64)/null",
|
|
1162
|
+
"native_type": "Nullable(UInt64)",
|
|
1163
|
+
"class": "null",
|
|
1164
|
+
"value_sql": "NULL",
|
|
1165
|
+
"expect": { "warehouse": null }
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
"key": "clickhouse/Int128/plain",
|
|
1169
|
+
"native_type": "Int128",
|
|
1170
|
+
"class": "plain",
|
|
1171
|
+
"value_sql": "42",
|
|
1172
|
+
"expect": { "warehouse": "42" }
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
"key": "clickhouse/Int128/negative",
|
|
1176
|
+
"native_type": "Int128",
|
|
1177
|
+
"class": "negative",
|
|
1178
|
+
"value_sql": "-42",
|
|
1179
|
+
"expect": { "warehouse": "-42" }
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
"key": "clickhouse/Int128/zero",
|
|
1183
|
+
"native_type": "Int128",
|
|
1184
|
+
"class": "zero",
|
|
1185
|
+
"value_sql": "0",
|
|
1186
|
+
"expect": { "warehouse": "0" }
|
|
1187
|
+
},
|
|
1188
|
+
{
|
|
1189
|
+
"key": "clickhouse/Int128/precision_gt_18",
|
|
1190
|
+
"native_type": "Int128",
|
|
1191
|
+
"class": "precision_gt_18",
|
|
1192
|
+
"value_sql": "toInt128('-170141183460469231731687303715884105728')",
|
|
1193
|
+
"expect": { "warehouse": "-170141183460469231731687303715884105728" }
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
"key": "clickhouse/Nullable(Int128)/null",
|
|
1197
|
+
"native_type": "Nullable(Int128)",
|
|
1198
|
+
"class": "null",
|
|
1199
|
+
"value_sql": "NULL",
|
|
1200
|
+
"expect": { "warehouse": null }
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
"key": "clickhouse/UInt128/plain",
|
|
1204
|
+
"native_type": "UInt128",
|
|
1205
|
+
"class": "plain",
|
|
1206
|
+
"value_sql": "42",
|
|
1207
|
+
"expect": { "warehouse": "42" }
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
"key": "clickhouse/UInt128/zero",
|
|
1211
|
+
"native_type": "UInt128",
|
|
1212
|
+
"class": "zero",
|
|
1213
|
+
"value_sql": "0",
|
|
1214
|
+
"expect": { "warehouse": "0" }
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
"key": "clickhouse/UInt128/precision_gt_18",
|
|
1218
|
+
"native_type": "UInt128",
|
|
1219
|
+
"class": "precision_gt_18",
|
|
1220
|
+
"value_sql": "toUInt128('340282366920938463463374607431768211455')",
|
|
1221
|
+
"expect": { "warehouse": "340282366920938463463374607431768211455" }
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
"key": "clickhouse/Nullable(UInt128)/null",
|
|
1225
|
+
"native_type": "Nullable(UInt128)",
|
|
1226
|
+
"class": "null",
|
|
1227
|
+
"value_sql": "NULL",
|
|
1228
|
+
"expect": { "warehouse": null }
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
"key": "clickhouse/Int256/plain",
|
|
1232
|
+
"native_type": "Int256",
|
|
1233
|
+
"class": "plain",
|
|
1234
|
+
"value_sql": "42",
|
|
1235
|
+
"expect": { "warehouse": "42" }
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
"key": "clickhouse/Int256/negative",
|
|
1239
|
+
"native_type": "Int256",
|
|
1240
|
+
"class": "negative",
|
|
1241
|
+
"value_sql": "-42",
|
|
1242
|
+
"expect": { "warehouse": "-42" }
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
"key": "clickhouse/Int256/zero",
|
|
1246
|
+
"native_type": "Int256",
|
|
1247
|
+
"class": "zero",
|
|
1248
|
+
"value_sql": "0",
|
|
1249
|
+
"expect": { "warehouse": "0" }
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
"key": "clickhouse/Int256/precision_gt_18",
|
|
1253
|
+
"native_type": "Int256",
|
|
1254
|
+
"class": "precision_gt_18",
|
|
1255
|
+
"value_sql": "toInt256('-57896044618658097711785492504343953926634992332820282019728792003956564819968')",
|
|
1256
|
+
"expect": { "warehouse": "-57896044618658097711785492504343953926634992332820282019728792003956564819968" }
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
"key": "clickhouse/Nullable(Int256)/null",
|
|
1260
|
+
"native_type": "Nullable(Int256)",
|
|
1261
|
+
"class": "null",
|
|
1262
|
+
"value_sql": "NULL",
|
|
1263
|
+
"expect": { "warehouse": null }
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
"key": "clickhouse/UInt256/plain",
|
|
1267
|
+
"native_type": "UInt256",
|
|
1268
|
+
"class": "plain",
|
|
1269
|
+
"value_sql": "42",
|
|
1270
|
+
"expect": { "warehouse": "42" }
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
"key": "clickhouse/UInt256/zero",
|
|
1274
|
+
"native_type": "UInt256",
|
|
1275
|
+
"class": "zero",
|
|
1276
|
+
"value_sql": "0",
|
|
1277
|
+
"expect": { "warehouse": "0" }
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
"key": "clickhouse/UInt256/precision_gt_18",
|
|
1281
|
+
"native_type": "UInt256",
|
|
1282
|
+
"class": "precision_gt_18",
|
|
1283
|
+
"value_sql": "toUInt256('115792089237316195423570985008687907853269984665640564039457584007913129639935')",
|
|
1284
|
+
"expect": { "warehouse": "115792089237316195423570985008687907853269984665640564039457584007913129639935" }
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
"key": "clickhouse/Nullable(UInt256)/null",
|
|
1288
|
+
"native_type": "Nullable(UInt256)",
|
|
1289
|
+
"class": "null",
|
|
1290
|
+
"value_sql": "NULL",
|
|
1291
|
+
"expect": { "warehouse": null }
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
"key": "clickhouse/Float32/plain",
|
|
1295
|
+
"native_type": "Float32",
|
|
1296
|
+
"class": "plain",
|
|
1297
|
+
"value_sql": "0.1",
|
|
1298
|
+
"expect": { "warehouse": .1 }
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
"key": "clickhouse/Float32/negative",
|
|
1302
|
+
"native_type": "Float32",
|
|
1303
|
+
"class": "negative",
|
|
1304
|
+
"value_sql": "-1.5",
|
|
1305
|
+
"expect": { "warehouse": -1.5 }
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
"key": "clickhouse/Float32/zero",
|
|
1309
|
+
"native_type": "Float32",
|
|
1310
|
+
"class": "zero",
|
|
1311
|
+
"value_sql": "0",
|
|
1312
|
+
"expect": { "warehouse": 0 }
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
"key": "clickhouse/Float32/nan_inf",
|
|
1316
|
+
"native_type": "Float32",
|
|
1317
|
+
"class": "nan_inf",
|
|
1318
|
+
"value_sql": "nan",
|
|
1319
|
+
"expect": { "warehouse": null }
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
"key": "clickhouse/Nullable(Float32)/null",
|
|
1323
|
+
"native_type": "Nullable(Float32)",
|
|
1324
|
+
"class": "null",
|
|
1325
|
+
"value_sql": "NULL",
|
|
1326
|
+
"expect": { "warehouse": null }
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
"key": "clickhouse/Float64/plain",
|
|
1330
|
+
"native_type": "Float64",
|
|
1331
|
+
"class": "plain",
|
|
1332
|
+
"value_sql": "0.1",
|
|
1333
|
+
"expect": { "warehouse": .1 }
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
"key": "clickhouse/Float64/negative",
|
|
1337
|
+
"native_type": "Float64",
|
|
1338
|
+
"class": "negative",
|
|
1339
|
+
"value_sql": "-1.5",
|
|
1340
|
+
"expect": { "warehouse": -1.5 }
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
"key": "clickhouse/Float64/zero",
|
|
1344
|
+
"native_type": "Float64",
|
|
1345
|
+
"class": "zero",
|
|
1346
|
+
"value_sql": "0",
|
|
1347
|
+
"expect": { "warehouse": 0 }
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
"key": "clickhouse/Float64/nan_inf",
|
|
1351
|
+
"native_type": "Float64",
|
|
1352
|
+
"class": "nan_inf",
|
|
1353
|
+
"value_sql": "inf",
|
|
1354
|
+
"expect": { "warehouse": null }
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
"key": "clickhouse/Nullable(Float64)/null",
|
|
1358
|
+
"native_type": "Nullable(Float64)",
|
|
1359
|
+
"class": "null",
|
|
1360
|
+
"value_sql": "NULL",
|
|
1361
|
+
"expect": { "warehouse": null }
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
"key": "clickhouse/Decimal(18, 4)/plain",
|
|
1365
|
+
"native_type": "Decimal(18, 4)",
|
|
1366
|
+
"class": "plain",
|
|
1367
|
+
"value_sql": "1234.5678",
|
|
1368
|
+
"expect": { "warehouse": "1234.5678" }
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
"key": "clickhouse/Decimal(18, 4)/negative",
|
|
1372
|
+
"native_type": "Decimal(18, 4)",
|
|
1373
|
+
"class": "negative",
|
|
1374
|
+
"value_sql": "-1.25",
|
|
1375
|
+
"expect": { "warehouse": "-1.25" }
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
"key": "clickhouse/Decimal(18, 4)/zero",
|
|
1379
|
+
"native_type": "Decimal(18, 4)",
|
|
1380
|
+
"class": "zero",
|
|
1381
|
+
"value_sql": "0",
|
|
1382
|
+
"expect": { "warehouse": "0" }
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
"key": "clickhouse/Nullable(Decimal(18, 4))/null",
|
|
1386
|
+
"native_type": "Nullable(Decimal(18, 4))",
|
|
1387
|
+
"class": "null",
|
|
1388
|
+
"value_sql": "NULL",
|
|
1389
|
+
"expect": { "warehouse": null }
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
"key": "clickhouse/Decimal(38, 10)/precision_gt_18",
|
|
1393
|
+
"native_type": "Decimal(38, 10)",
|
|
1394
|
+
"class": "precision_gt_18",
|
|
1395
|
+
"value_sql": "toDecimal128('1234567890123456789.0123456789', 10)",
|
|
1396
|
+
"expect": { "warehouse": "1.2345678901234568e+18" }
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
"key": "clickhouse/Decimal(38, 0)/precision_gt_18",
|
|
1400
|
+
"native_type": "Decimal(38, 0)",
|
|
1401
|
+
"class": "precision_gt_18",
|
|
1402
|
+
"value_sql": "toDecimal128('12345678901234567890123456789012345678', 0)",
|
|
1403
|
+
"expect": { "warehouse": "12345678901234567890123456789012345678" }
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
"key": "clickhouse/Decimal32(4)/plain",
|
|
1407
|
+
"native_type": "Decimal32(4)",
|
|
1408
|
+
"class": "plain",
|
|
1409
|
+
"value_sql": "1.5",
|
|
1410
|
+
"expect": { "warehouse": "1.5" }
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
"key": "clickhouse/Date/plain",
|
|
1414
|
+
"native_type": "Date",
|
|
1415
|
+
"class": "plain",
|
|
1416
|
+
"value_sql": "'2024-03-10'",
|
|
1417
|
+
"expect": { "warehouse": "2024-03-10" }
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
"key": "clickhouse/Date/date_before_1970",
|
|
1421
|
+
"native_type": "Date",
|
|
1422
|
+
"class": "date_before_1970",
|
|
1423
|
+
"value_sql": "'1969-12-31'",
|
|
1424
|
+
"expect": { "warehouse": "1970-01-01" }
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
"key": "clickhouse/Date/date_after_2100",
|
|
1428
|
+
"native_type": "Date",
|
|
1429
|
+
"class": "date_after_2100",
|
|
1430
|
+
"value_sql": "'2149-06-06'",
|
|
1431
|
+
"expect": { "warehouse": "2149-06-06" }
|
|
1432
|
+
},
|
|
1433
|
+
{
|
|
1434
|
+
"key": "clickhouse/Nullable(Date)/null",
|
|
1435
|
+
"native_type": "Nullable(Date)",
|
|
1436
|
+
"class": "null",
|
|
1437
|
+
"value_sql": "NULL",
|
|
1438
|
+
"expect": { "warehouse": null }
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
"key": "clickhouse/Date32/plain",
|
|
1442
|
+
"native_type": "Date32",
|
|
1443
|
+
"class": "plain",
|
|
1444
|
+
"value_sql": "'2024-03-10'",
|
|
1445
|
+
"expect": { "warehouse": "2024-03-10" }
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
"key": "clickhouse/Date32/date_before_1970",
|
|
1449
|
+
"native_type": "Date32",
|
|
1450
|
+
"class": "date_before_1970",
|
|
1451
|
+
"value_sql": "'1900-01-01'",
|
|
1452
|
+
"expect": { "warehouse": "1900-01-01" }
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
"key": "clickhouse/Date32/date_after_2100",
|
|
1456
|
+
"native_type": "Date32",
|
|
1457
|
+
"class": "date_after_2100",
|
|
1458
|
+
"value_sql": "'2299-12-31'",
|
|
1459
|
+
"expect": { "warehouse": "2299-12-31" }
|
|
1460
|
+
},
|
|
1461
|
+
{
|
|
1462
|
+
"key": "clickhouse/Nullable(Date32)/null",
|
|
1463
|
+
"native_type": "Nullable(Date32)",
|
|
1464
|
+
"class": "null",
|
|
1465
|
+
"value_sql": "NULL",
|
|
1466
|
+
"expect": { "warehouse": null }
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
"key": "clickhouse/DateTime/plain",
|
|
1470
|
+
"native_type": "DateTime",
|
|
1471
|
+
"class": "plain",
|
|
1472
|
+
"value_sql": "'2024-03-10 12:34:56'",
|
|
1473
|
+
"expect": { "warehouse": "2024-03-10 12:34:56" }
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
"key": "clickhouse/DateTime/date_before_1970",
|
|
1477
|
+
"native_type": "DateTime",
|
|
1478
|
+
"class": "date_before_1970",
|
|
1479
|
+
"value_sql": "'1969-12-31 23:59:59'",
|
|
1480
|
+
"expect": { "warehouse": "1970-01-01 00:00:00" }
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
"key": "clickhouse/DateTime/date_after_2100",
|
|
1484
|
+
"native_type": "DateTime",
|
|
1485
|
+
"class": "date_after_2100",
|
|
1486
|
+
"value_sql": "'2105-12-31 23:59:59'",
|
|
1487
|
+
"expect": { "warehouse": "2105-12-31 23:59:59" }
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
"key": "clickhouse/Nullable(DateTime)/null",
|
|
1491
|
+
"native_type": "Nullable(DateTime)",
|
|
1492
|
+
"class": "null",
|
|
1493
|
+
"value_sql": "NULL",
|
|
1494
|
+
"expect": { "warehouse": null }
|
|
1495
|
+
},
|
|
1496
|
+
{
|
|
1497
|
+
"key": "clickhouse/DateTime('Asia/Kolkata')/tz_bearing",
|
|
1498
|
+
"native_type": "DateTime('Asia/Kolkata')",
|
|
1499
|
+
"class": "tz_bearing",
|
|
1500
|
+
"value_sql": "'2024-03-10 12:34:56'",
|
|
1501
|
+
"expect": { "warehouse": "2024-03-10 12:34:56" }
|
|
1502
|
+
},
|
|
1503
|
+
{
|
|
1504
|
+
"key": "clickhouse/DateTime64(3)/plain",
|
|
1505
|
+
"native_type": "DateTime64(3)",
|
|
1506
|
+
"class": "plain",
|
|
1507
|
+
"value_sql": "'2024-03-10 12:34:56.789'",
|
|
1508
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.789000" }
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
"key": "clickhouse/DateTime64(3)/date_before_1970",
|
|
1512
|
+
"native_type": "DateTime64(3)",
|
|
1513
|
+
"class": "date_before_1970",
|
|
1514
|
+
"value_sql": "'1969-07-20 20:17:40.500'",
|
|
1515
|
+
"expect": { "warehouse": "1969-07-20 20:17:40.500000" }
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
"key": "clickhouse/DateTime64(3)/date_after_2100",
|
|
1519
|
+
"native_type": "DateTime64(3)",
|
|
1520
|
+
"class": "date_after_2100",
|
|
1521
|
+
"value_sql": "'2150-01-01 00:00:00.000'",
|
|
1522
|
+
"expect": { "warehouse": "2150-01-01 00:00:00" }
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
"key": "clickhouse/Nullable(DateTime64(3))/null",
|
|
1526
|
+
"native_type": "Nullable(DateTime64(3))",
|
|
1527
|
+
"class": "null",
|
|
1528
|
+
"value_sql": "NULL",
|
|
1529
|
+
"expect": { "warehouse": null }
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
"key": "clickhouse/DateTime64(6, 'America/New_York')/tz_bearing",
|
|
1533
|
+
"native_type": "DateTime64(6, 'America/New_York')",
|
|
1534
|
+
"class": "tz_bearing",
|
|
1535
|
+
"value_sql": "'2024-03-10 12:34:56.123456'",
|
|
1536
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.123456" }
|
|
1537
|
+
},
|
|
1538
|
+
{
|
|
1539
|
+
"key": "clickhouse/String/plain",
|
|
1540
|
+
"native_type": "String",
|
|
1541
|
+
"class": "plain",
|
|
1542
|
+
"value_sql": "'plain text'",
|
|
1543
|
+
"expect": { "warehouse": "plain text" }
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
"key": "clickhouse/String/empty",
|
|
1547
|
+
"native_type": "String",
|
|
1548
|
+
"class": "empty",
|
|
1549
|
+
"value_sql": "''",
|
|
1550
|
+
"expect": { "warehouse": "" }
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
"key": "clickhouse/String/whitespace",
|
|
1554
|
+
"native_type": "String",
|
|
1555
|
+
"class": "whitespace",
|
|
1556
|
+
"value_sql": "' \\t\\n '",
|
|
1557
|
+
"expect": { "warehouse": " \n " }
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
"key": "clickhouse/String/non_bmp",
|
|
1561
|
+
"native_type": "String",
|
|
1562
|
+
"class": "non_bmp",
|
|
1563
|
+
"value_sql": "'𝔘nicode'",
|
|
1564
|
+
"expect": { "warehouse": "𝔘nicode" }
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
"key": "clickhouse/String/len_gt_1k",
|
|
1568
|
+
"native_type": "String",
|
|
1569
|
+
"class": "len_gt_1k",
|
|
1570
|
+
"value_sql": "repeat('0123456789', 101)",
|
|
1571
|
+
"expect": { "warehouse": "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" }
|
|
1572
|
+
},
|
|
1573
|
+
{
|
|
1574
|
+
"key": "clickhouse/Nullable(String)/null",
|
|
1575
|
+
"native_type": "Nullable(String)",
|
|
1576
|
+
"class": "null",
|
|
1577
|
+
"value_sql": "NULL",
|
|
1578
|
+
"expect": { "warehouse": null }
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
"key": "clickhouse/FixedString(4)/plain",
|
|
1582
|
+
"native_type": "FixedString(4)",
|
|
1583
|
+
"class": "plain",
|
|
1584
|
+
"value_sql": "'ab'",
|
|
1585
|
+
"expect": { "warehouse": "ab\0\0" }
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
"key": "clickhouse/FixedString(4)/empty",
|
|
1589
|
+
"native_type": "FixedString(4)",
|
|
1590
|
+
"class": "empty",
|
|
1591
|
+
"value_sql": "''",
|
|
1592
|
+
"expect": { "warehouse": "\0\0\0\0" }
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
"key": "clickhouse/Nullable(FixedString(4))/null",
|
|
1596
|
+
"native_type": "Nullable(FixedString(4))",
|
|
1597
|
+
"class": "null",
|
|
1598
|
+
"value_sql": "NULL",
|
|
1599
|
+
"expect": { "warehouse": null }
|
|
1600
|
+
},
|
|
1601
|
+
{
|
|
1602
|
+
"key": "clickhouse/LowCardinality(String)/plain",
|
|
1603
|
+
"native_type": "LowCardinality(String)",
|
|
1604
|
+
"class": "plain",
|
|
1605
|
+
"value_sql": "'low cardinality'",
|
|
1606
|
+
"expect": { "warehouse": "low cardinality" }
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
"key": "clickhouse/LowCardinality(Nullable(String))/null",
|
|
1610
|
+
"native_type": "LowCardinality(Nullable(String))",
|
|
1611
|
+
"class": "null",
|
|
1612
|
+
"value_sql": "NULL",
|
|
1613
|
+
"expect": { "warehouse": null }
|
|
1614
|
+
},
|
|
1615
|
+
{
|
|
1616
|
+
"key": "clickhouse/Enum8('a' = 1, 'b' = 2)/plain",
|
|
1617
|
+
"native_type": "Enum8('a' = 1, 'b' = 2)",
|
|
1618
|
+
"class": "plain",
|
|
1619
|
+
"value_sql": "'b'",
|
|
1620
|
+
"expect": { "warehouse": "b" }
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
"key": "clickhouse/Nullable(Enum8('a' = 1, 'b' = 2))/null",
|
|
1624
|
+
"native_type": "Nullable(Enum8('a' = 1, 'b' = 2))",
|
|
1625
|
+
"class": "null",
|
|
1626
|
+
"value_sql": "NULL",
|
|
1627
|
+
"expect": { "warehouse": null }
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
"key": "clickhouse/UUID/plain",
|
|
1631
|
+
"native_type": "UUID",
|
|
1632
|
+
"class": "plain",
|
|
1633
|
+
"value_sql": "'00000000-0000-4000-8000-000000000001'",
|
|
1634
|
+
"expect": { "warehouse": "00000000-0000-4000-8000-000000000001" }
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
"key": "clickhouse/Nullable(UUID)/null",
|
|
1638
|
+
"native_type": "Nullable(UUID)",
|
|
1639
|
+
"class": "null",
|
|
1640
|
+
"value_sql": "NULL",
|
|
1641
|
+
"expect": { "warehouse": null }
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
"key": "clickhouse/IPv4/plain",
|
|
1645
|
+
"native_type": "IPv4",
|
|
1646
|
+
"class": "plain",
|
|
1647
|
+
"value_sql": "'10.0.0.1'",
|
|
1648
|
+
"expect": { "warehouse": "10.0.0.1" }
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
"key": "clickhouse/Nullable(IPv4)/null",
|
|
1652
|
+
"native_type": "Nullable(IPv4)",
|
|
1653
|
+
"class": "null",
|
|
1654
|
+
"value_sql": "NULL",
|
|
1655
|
+
"expect": { "warehouse": null }
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
"key": "clickhouse/IPv6/plain",
|
|
1659
|
+
"native_type": "IPv6",
|
|
1660
|
+
"class": "plain",
|
|
1661
|
+
"value_sql": "'2001:db8::1'",
|
|
1662
|
+
"expect": { "warehouse": "2001:db8::1" }
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
"key": "clickhouse/Nullable(IPv6)/null",
|
|
1666
|
+
"native_type": "Nullable(IPv6)",
|
|
1667
|
+
"class": "null",
|
|
1668
|
+
"value_sql": "NULL",
|
|
1669
|
+
"expect": { "warehouse": null }
|
|
1670
|
+
},
|
|
1671
|
+
{
|
|
1672
|
+
"key": "clickhouse/JSON/nested_json",
|
|
1673
|
+
"native_type": "JSON",
|
|
1674
|
+
"class": "nested_json",
|
|
1675
|
+
"value_sql": "'{\"a\":{\"b\":\"x\",\"n\":1.5}}'",
|
|
1676
|
+
"expect": { "warehouse": { "a": {
|
|
1677
|
+
"b": "x",
|
|
1678
|
+
"n": 1.5
|
|
1679
|
+
} } }
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
"key": "clickhouse/JSON/empty",
|
|
1683
|
+
"native_type": "JSON",
|
|
1684
|
+
"class": "empty",
|
|
1685
|
+
"value_sql": "'{}'",
|
|
1686
|
+
"expect": { "warehouse": {} }
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
"key": "clickhouse/Array(Int32)/array",
|
|
1690
|
+
"native_type": "Array(Int32)",
|
|
1691
|
+
"class": "array",
|
|
1692
|
+
"value_sql": "[1, -2, 3]",
|
|
1693
|
+
"expect": { "warehouse": [
|
|
1694
|
+
1,
|
|
1695
|
+
-2,
|
|
1696
|
+
3
|
|
1697
|
+
] }
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
"key": "clickhouse/Array(Int32)/empty",
|
|
1701
|
+
"native_type": "Array(Int32)",
|
|
1702
|
+
"class": "empty",
|
|
1703
|
+
"value_sql": "[]",
|
|
1704
|
+
"expect": { "warehouse": [] }
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
"key": "clickhouse/Array(Nullable(String))/array",
|
|
1708
|
+
"native_type": "Array(Nullable(String))",
|
|
1709
|
+
"class": "array",
|
|
1710
|
+
"value_sql": "['a', NULL]",
|
|
1711
|
+
"expect": { "warehouse": ["a", null] }
|
|
1712
|
+
},
|
|
1713
|
+
{
|
|
1714
|
+
"key": "clickhouse/Tuple(Int32, String)/array",
|
|
1715
|
+
"native_type": "Tuple(Int32, String)",
|
|
1716
|
+
"class": "array",
|
|
1717
|
+
"value_sql": "(1, 'a')",
|
|
1718
|
+
"expect": { "warehouse": [1, "a"] }
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
"key": "clickhouse/Map(String, Int32)/nested_json",
|
|
1722
|
+
"native_type": "Map(String, Int32)",
|
|
1723
|
+
"class": "nested_json",
|
|
1724
|
+
"value_sql": "map('a', 1, 'b', 2)",
|
|
1725
|
+
"expect": { "warehouse": {
|
|
1726
|
+
"a": 1,
|
|
1727
|
+
"b": 2
|
|
1728
|
+
} }
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
"key": "clickhouse/Map(String, Int32)/empty",
|
|
1732
|
+
"native_type": "Map(String, Int32)",
|
|
1733
|
+
"class": "empty",
|
|
1734
|
+
"value_sql": "map()",
|
|
1735
|
+
"expect": { "warehouse": {} }
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
"key": "clickhouse/SimpleAggregateFunction(sum, UInt64)/plain",
|
|
1739
|
+
"native_type": "SimpleAggregateFunction(sum, UInt64)",
|
|
1740
|
+
"class": "plain",
|
|
1741
|
+
"value_sql": "3",
|
|
1742
|
+
"expect": { "warehouse": 3 }
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
"key": "clickhouse/AggregateFunction(sum, UInt64)/plain",
|
|
1746
|
+
"native_type": "AggregateFunction(sum, UInt64)",
|
|
1747
|
+
"class": "plain",
|
|
1748
|
+
"value_sql": "arrayReduce('sumState', [toUInt64(3)])",
|
|
1749
|
+
"expect": { "warehouse": "\0\0\0\0\0\0\0" }
|
|
1750
|
+
}
|
|
1751
|
+
] },
|
|
1752
|
+
"postgres": { "cases": [
|
|
1753
|
+
{
|
|
1754
|
+
"key": "postgres/bool/plain",
|
|
1755
|
+
"native_type": "bool",
|
|
1756
|
+
"class": "plain",
|
|
1757
|
+
"value_sql": "true",
|
|
1758
|
+
"expect": {
|
|
1759
|
+
"warehouse": true,
|
|
1760
|
+
"oltp": true
|
|
1761
|
+
}
|
|
1762
|
+
},
|
|
1763
|
+
{
|
|
1764
|
+
"key": "postgres/bool/null",
|
|
1765
|
+
"native_type": "bool",
|
|
1766
|
+
"class": "null",
|
|
1767
|
+
"value_sql": "NULL",
|
|
1768
|
+
"expect": {
|
|
1769
|
+
"warehouse": null,
|
|
1770
|
+
"oltp": null
|
|
1771
|
+
}
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
"key": "postgres/int2/plain",
|
|
1775
|
+
"native_type": "int2",
|
|
1776
|
+
"class": "plain",
|
|
1777
|
+
"value_sql": "42",
|
|
1778
|
+
"expect": {
|
|
1779
|
+
"warehouse": 42,
|
|
1780
|
+
"oltp": 42
|
|
1781
|
+
}
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
"key": "postgres/int2/negative",
|
|
1785
|
+
"native_type": "int2",
|
|
1786
|
+
"class": "negative",
|
|
1787
|
+
"value_sql": "-32768",
|
|
1788
|
+
"expect": {
|
|
1789
|
+
"warehouse": -32768,
|
|
1790
|
+
"oltp": -32768
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
"key": "postgres/int2/zero",
|
|
1795
|
+
"native_type": "int2",
|
|
1796
|
+
"class": "zero",
|
|
1797
|
+
"value_sql": "0",
|
|
1798
|
+
"expect": {
|
|
1799
|
+
"warehouse": 0,
|
|
1800
|
+
"oltp": 0
|
|
1801
|
+
}
|
|
1802
|
+
},
|
|
1803
|
+
{
|
|
1804
|
+
"key": "postgres/int2/null",
|
|
1805
|
+
"native_type": "int2",
|
|
1806
|
+
"class": "null",
|
|
1807
|
+
"value_sql": "NULL",
|
|
1808
|
+
"expect": {
|
|
1809
|
+
"warehouse": null,
|
|
1810
|
+
"oltp": null
|
|
1811
|
+
}
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
"key": "postgres/int4/plain",
|
|
1815
|
+
"native_type": "int4",
|
|
1816
|
+
"class": "plain",
|
|
1817
|
+
"value_sql": "42",
|
|
1818
|
+
"expect": {
|
|
1819
|
+
"warehouse": 42,
|
|
1820
|
+
"oltp": 42
|
|
1821
|
+
}
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
"key": "postgres/int4/negative",
|
|
1825
|
+
"native_type": "int4",
|
|
1826
|
+
"class": "negative",
|
|
1827
|
+
"value_sql": "-2147483648",
|
|
1828
|
+
"expect": {
|
|
1829
|
+
"warehouse": -2147483648,
|
|
1830
|
+
"oltp": -2147483648
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
"key": "postgres/int4/zero",
|
|
1835
|
+
"native_type": "int4",
|
|
1836
|
+
"class": "zero",
|
|
1837
|
+
"value_sql": "0",
|
|
1838
|
+
"expect": {
|
|
1839
|
+
"warehouse": 0,
|
|
1840
|
+
"oltp": 0
|
|
1841
|
+
}
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
"key": "postgres/int4/null",
|
|
1845
|
+
"native_type": "int4",
|
|
1846
|
+
"class": "null",
|
|
1847
|
+
"value_sql": "NULL",
|
|
1848
|
+
"expect": {
|
|
1849
|
+
"warehouse": null,
|
|
1850
|
+
"oltp": null
|
|
1851
|
+
}
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
"key": "postgres/int8/plain",
|
|
1855
|
+
"native_type": "int8",
|
|
1856
|
+
"class": "plain",
|
|
1857
|
+
"value_sql": "42",
|
|
1858
|
+
"expect": {
|
|
1859
|
+
"warehouse": 42,
|
|
1860
|
+
"oltp": 42
|
|
1861
|
+
}
|
|
1862
|
+
},
|
|
1863
|
+
{
|
|
1864
|
+
"key": "postgres/int8/negative",
|
|
1865
|
+
"native_type": "int8",
|
|
1866
|
+
"class": "negative",
|
|
1867
|
+
"value_sql": "-9007199254740991",
|
|
1868
|
+
"expect": {
|
|
1869
|
+
"warehouse": -9007199254740991,
|
|
1870
|
+
"oltp": -9007199254740991
|
|
1871
|
+
}
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
"key": "postgres/int8/zero",
|
|
1875
|
+
"native_type": "int8",
|
|
1876
|
+
"class": "zero",
|
|
1877
|
+
"value_sql": "0",
|
|
1878
|
+
"expect": {
|
|
1879
|
+
"warehouse": 0,
|
|
1880
|
+
"oltp": 0
|
|
1881
|
+
}
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
"key": "postgres/int8/precision_gt_18",
|
|
1885
|
+
"native_type": "int8",
|
|
1886
|
+
"class": "precision_gt_18",
|
|
1887
|
+
"value_sql": "9223372036854775807",
|
|
1888
|
+
"expect": {
|
|
1889
|
+
"warehouse": 0x8000000000000000,
|
|
1890
|
+
"oltp": 0x8000000000000000
|
|
1891
|
+
}
|
|
1892
|
+
},
|
|
1893
|
+
{
|
|
1894
|
+
"key": "postgres/int8/null",
|
|
1895
|
+
"native_type": "int8",
|
|
1896
|
+
"class": "null",
|
|
1897
|
+
"value_sql": "NULL",
|
|
1898
|
+
"expect": {
|
|
1899
|
+
"warehouse": null,
|
|
1900
|
+
"oltp": null
|
|
1901
|
+
}
|
|
1902
|
+
},
|
|
1903
|
+
{
|
|
1904
|
+
"key": "postgres/float4/plain",
|
|
1905
|
+
"native_type": "float4",
|
|
1906
|
+
"class": "plain",
|
|
1907
|
+
"value_sql": "0.1",
|
|
1908
|
+
"expect": {
|
|
1909
|
+
"warehouse": .10000000149011612,
|
|
1910
|
+
"oltp": .10000000149011612
|
|
1911
|
+
}
|
|
1912
|
+
},
|
|
1913
|
+
{
|
|
1914
|
+
"key": "postgres/float4/negative",
|
|
1915
|
+
"native_type": "float4",
|
|
1916
|
+
"class": "negative",
|
|
1917
|
+
"value_sql": "-1.5",
|
|
1918
|
+
"expect": {
|
|
1919
|
+
"warehouse": -1.5,
|
|
1920
|
+
"oltp": -1.5
|
|
1921
|
+
}
|
|
1922
|
+
},
|
|
1923
|
+
{
|
|
1924
|
+
"key": "postgres/float4/zero",
|
|
1925
|
+
"native_type": "float4",
|
|
1926
|
+
"class": "zero",
|
|
1927
|
+
"value_sql": "0",
|
|
1928
|
+
"expect": {
|
|
1929
|
+
"warehouse": 0,
|
|
1930
|
+
"oltp": 0
|
|
1931
|
+
}
|
|
1932
|
+
},
|
|
1933
|
+
{
|
|
1934
|
+
"key": "postgres/float4/nan_inf",
|
|
1935
|
+
"native_type": "float4",
|
|
1936
|
+
"class": "nan_inf",
|
|
1937
|
+
"value_sql": "'NaN'",
|
|
1938
|
+
"expect": {
|
|
1939
|
+
"warehouse": null,
|
|
1940
|
+
"oltp": null
|
|
1941
|
+
}
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
"key": "postgres/float4/null",
|
|
1945
|
+
"native_type": "float4",
|
|
1946
|
+
"class": "null",
|
|
1947
|
+
"value_sql": "NULL",
|
|
1948
|
+
"expect": {
|
|
1949
|
+
"warehouse": null,
|
|
1950
|
+
"oltp": null
|
|
1951
|
+
}
|
|
1952
|
+
},
|
|
1953
|
+
{
|
|
1954
|
+
"key": "postgres/float8/plain",
|
|
1955
|
+
"native_type": "float8",
|
|
1956
|
+
"class": "plain",
|
|
1957
|
+
"value_sql": "0.1",
|
|
1958
|
+
"expect": {
|
|
1959
|
+
"warehouse": .1,
|
|
1960
|
+
"oltp": .1
|
|
1961
|
+
}
|
|
1962
|
+
},
|
|
1963
|
+
{
|
|
1964
|
+
"key": "postgres/float8/negative",
|
|
1965
|
+
"native_type": "float8",
|
|
1966
|
+
"class": "negative",
|
|
1967
|
+
"value_sql": "-1.5",
|
|
1968
|
+
"expect": {
|
|
1969
|
+
"warehouse": -1.5,
|
|
1970
|
+
"oltp": -1.5
|
|
1971
|
+
}
|
|
1972
|
+
},
|
|
1973
|
+
{
|
|
1974
|
+
"key": "postgres/float8/zero",
|
|
1975
|
+
"native_type": "float8",
|
|
1976
|
+
"class": "zero",
|
|
1977
|
+
"value_sql": "0",
|
|
1978
|
+
"expect": {
|
|
1979
|
+
"warehouse": 0,
|
|
1980
|
+
"oltp": 0
|
|
1981
|
+
}
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
"key": "postgres/float8/nan_inf",
|
|
1985
|
+
"native_type": "float8",
|
|
1986
|
+
"class": "nan_inf",
|
|
1987
|
+
"value_sql": "'Infinity'",
|
|
1988
|
+
"expect": {
|
|
1989
|
+
"warehouse": null,
|
|
1990
|
+
"oltp": null
|
|
1991
|
+
}
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
"key": "postgres/float8/null",
|
|
1995
|
+
"native_type": "float8",
|
|
1996
|
+
"class": "null",
|
|
1997
|
+
"value_sql": "NULL",
|
|
1998
|
+
"expect": {
|
|
1999
|
+
"warehouse": null,
|
|
2000
|
+
"oltp": null
|
|
2001
|
+
}
|
|
2002
|
+
},
|
|
2003
|
+
{
|
|
2004
|
+
"key": "postgres/numeric/plain",
|
|
2005
|
+
"native_type": "numeric",
|
|
2006
|
+
"class": "plain",
|
|
2007
|
+
"value_sql": "1234.5678",
|
|
2008
|
+
"expect": {
|
|
2009
|
+
"warehouse": "1234.5678",
|
|
2010
|
+
"oltp": { "$error": "Postgres type `numeric`, which cannot be returned directly" }
|
|
2011
|
+
}
|
|
2012
|
+
},
|
|
2013
|
+
{
|
|
2014
|
+
"key": "postgres/numeric/negative",
|
|
2015
|
+
"native_type": "numeric",
|
|
2016
|
+
"class": "negative",
|
|
2017
|
+
"value_sql": "-1.25",
|
|
2018
|
+
"expect": {
|
|
2019
|
+
"warehouse": "-1.25",
|
|
2020
|
+
"oltp": { "$error": "Postgres type `numeric`, which cannot be returned directly" }
|
|
2021
|
+
}
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
"key": "postgres/numeric/zero",
|
|
2025
|
+
"native_type": "numeric",
|
|
2026
|
+
"class": "zero",
|
|
2027
|
+
"value_sql": "0",
|
|
2028
|
+
"expect": {
|
|
2029
|
+
"warehouse": "0.0",
|
|
2030
|
+
"oltp": { "$error": "Postgres type `numeric`, which cannot be returned directly" }
|
|
2031
|
+
}
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
"key": "postgres/numeric/nan_inf",
|
|
2035
|
+
"native_type": "numeric",
|
|
2036
|
+
"class": "nan_inf",
|
|
2037
|
+
"value_sql": "'NaN'",
|
|
2038
|
+
"expect": {
|
|
2039
|
+
"warehouse": "NaN",
|
|
2040
|
+
"oltp": { "$error": "Postgres type `numeric`, which cannot be returned directly" }
|
|
2041
|
+
}
|
|
2042
|
+
},
|
|
2043
|
+
{
|
|
2044
|
+
"key": "postgres/numeric/precision_gt_18",
|
|
2045
|
+
"native_type": "numeric",
|
|
2046
|
+
"class": "precision_gt_18",
|
|
2047
|
+
"value_sql": "1234567890123456789.0123456789",
|
|
2048
|
+
"expect": {
|
|
2049
|
+
"warehouse": "1234567890123456768.0",
|
|
2050
|
+
"oltp": { "$error": "Postgres type `numeric`, which cannot be returned directly" }
|
|
2051
|
+
}
|
|
2052
|
+
},
|
|
2053
|
+
{
|
|
2054
|
+
"key": "postgres/numeric/null",
|
|
2055
|
+
"native_type": "numeric",
|
|
2056
|
+
"class": "null",
|
|
2057
|
+
"value_sql": "NULL",
|
|
2058
|
+
"expect": {
|
|
2059
|
+
"warehouse": null,
|
|
2060
|
+
"oltp": { "$error": "Postgres type `numeric`, which cannot be returned directly" }
|
|
2061
|
+
}
|
|
2062
|
+
},
|
|
2063
|
+
{
|
|
2064
|
+
"key": "postgres/text/plain",
|
|
2065
|
+
"native_type": "text",
|
|
2066
|
+
"class": "plain",
|
|
2067
|
+
"value_sql": "'plain text'",
|
|
2068
|
+
"expect": {
|
|
2069
|
+
"warehouse": "plain text",
|
|
2070
|
+
"oltp": "plain text"
|
|
2071
|
+
}
|
|
2072
|
+
},
|
|
2073
|
+
{
|
|
2074
|
+
"key": "postgres/text/empty",
|
|
2075
|
+
"native_type": "text",
|
|
2076
|
+
"class": "empty",
|
|
2077
|
+
"value_sql": "''",
|
|
2078
|
+
"expect": {
|
|
2079
|
+
"warehouse": "",
|
|
2080
|
+
"oltp": ""
|
|
2081
|
+
}
|
|
2082
|
+
},
|
|
2083
|
+
{
|
|
2084
|
+
"key": "postgres/text/whitespace",
|
|
2085
|
+
"native_type": "text",
|
|
2086
|
+
"class": "whitespace",
|
|
2087
|
+
"value_sql": "E' \\t\\n '",
|
|
2088
|
+
"expect": {
|
|
2089
|
+
"warehouse": " \n ",
|
|
2090
|
+
"oltp": " \n "
|
|
2091
|
+
}
|
|
2092
|
+
},
|
|
2093
|
+
{
|
|
2094
|
+
"key": "postgres/text/non_bmp",
|
|
2095
|
+
"native_type": "text",
|
|
2096
|
+
"class": "non_bmp",
|
|
2097
|
+
"value_sql": "'𝔘nicode'",
|
|
2098
|
+
"expect": {
|
|
2099
|
+
"warehouse": "𝔘nicode",
|
|
2100
|
+
"oltp": "𝔘nicode"
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
"key": "postgres/text/len_gt_1k",
|
|
2105
|
+
"native_type": "text",
|
|
2106
|
+
"class": "len_gt_1k",
|
|
2107
|
+
"value_sql": "repeat('0123456789', 101)",
|
|
2108
|
+
"expect": {
|
|
2109
|
+
"warehouse": "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
|
|
2110
|
+
"oltp": "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
|
2111
|
+
}
|
|
2112
|
+
},
|
|
2113
|
+
{
|
|
2114
|
+
"key": "postgres/text/null",
|
|
2115
|
+
"native_type": "text",
|
|
2116
|
+
"class": "null",
|
|
2117
|
+
"value_sql": "NULL",
|
|
2118
|
+
"expect": {
|
|
2119
|
+
"warehouse": null,
|
|
2120
|
+
"oltp": null
|
|
2121
|
+
}
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
"key": "postgres/varchar(2000)/plain",
|
|
2125
|
+
"native_type": "varchar(2000)",
|
|
2126
|
+
"class": "plain",
|
|
2127
|
+
"value_sql": "'plain varchar'",
|
|
2128
|
+
"expect": {
|
|
2129
|
+
"warehouse": "plain varchar",
|
|
2130
|
+
"oltp": "plain varchar"
|
|
2131
|
+
}
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
"key": "postgres/varchar(2000)/empty",
|
|
2135
|
+
"native_type": "varchar(2000)",
|
|
2136
|
+
"class": "empty",
|
|
2137
|
+
"value_sql": "''",
|
|
2138
|
+
"expect": {
|
|
2139
|
+
"warehouse": "",
|
|
2140
|
+
"oltp": ""
|
|
2141
|
+
}
|
|
2142
|
+
},
|
|
2143
|
+
{
|
|
2144
|
+
"key": "postgres/varchar(2000)/whitespace",
|
|
2145
|
+
"native_type": "varchar(2000)",
|
|
2146
|
+
"class": "whitespace",
|
|
2147
|
+
"value_sql": "E' \\t\\n '",
|
|
2148
|
+
"expect": {
|
|
2149
|
+
"warehouse": " \n ",
|
|
2150
|
+
"oltp": " \n "
|
|
2151
|
+
}
|
|
2152
|
+
},
|
|
2153
|
+
{
|
|
2154
|
+
"key": "postgres/varchar(2000)/non_bmp",
|
|
2155
|
+
"native_type": "varchar(2000)",
|
|
2156
|
+
"class": "non_bmp",
|
|
2157
|
+
"value_sql": "'𝔘nicode'",
|
|
2158
|
+
"expect": {
|
|
2159
|
+
"warehouse": "𝔘nicode",
|
|
2160
|
+
"oltp": "𝔘nicode"
|
|
2161
|
+
}
|
|
2162
|
+
},
|
|
2163
|
+
{
|
|
2164
|
+
"key": "postgres/varchar(2000)/len_gt_1k",
|
|
2165
|
+
"native_type": "varchar(2000)",
|
|
2166
|
+
"class": "len_gt_1k",
|
|
2167
|
+
"value_sql": "repeat('0123456789', 101)",
|
|
2168
|
+
"expect": {
|
|
2169
|
+
"warehouse": "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
|
|
2170
|
+
"oltp": "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
|
2171
|
+
}
|
|
2172
|
+
},
|
|
2173
|
+
{
|
|
2174
|
+
"key": "postgres/varchar(2000)/null",
|
|
2175
|
+
"native_type": "varchar(2000)",
|
|
2176
|
+
"class": "null",
|
|
2177
|
+
"value_sql": "NULL",
|
|
2178
|
+
"expect": {
|
|
2179
|
+
"warehouse": null,
|
|
2180
|
+
"oltp": null
|
|
2181
|
+
}
|
|
2182
|
+
},
|
|
2183
|
+
{
|
|
2184
|
+
"key": "postgres/bpchar(8)/plain",
|
|
2185
|
+
"native_type": "bpchar(8)",
|
|
2186
|
+
"class": "plain",
|
|
2187
|
+
"value_sql": "'ab'",
|
|
2188
|
+
"expect": {
|
|
2189
|
+
"warehouse": "ab ",
|
|
2190
|
+
"oltp": "ab "
|
|
2191
|
+
}
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
"key": "postgres/bpchar(8)/empty",
|
|
2195
|
+
"native_type": "bpchar(8)",
|
|
2196
|
+
"class": "empty",
|
|
2197
|
+
"value_sql": "''",
|
|
2198
|
+
"expect": {
|
|
2199
|
+
"warehouse": " ",
|
|
2200
|
+
"oltp": " "
|
|
2201
|
+
}
|
|
2202
|
+
},
|
|
2203
|
+
{
|
|
2204
|
+
"key": "postgres/bpchar(8)/whitespace",
|
|
2205
|
+
"native_type": "bpchar(8)",
|
|
2206
|
+
"class": "whitespace",
|
|
2207
|
+
"value_sql": "E' \\t\\n '",
|
|
2208
|
+
"expect": {
|
|
2209
|
+
"warehouse": " \n ",
|
|
2210
|
+
"oltp": " \n "
|
|
2211
|
+
}
|
|
2212
|
+
},
|
|
2213
|
+
{
|
|
2214
|
+
"key": "postgres/bpchar(8)/non_bmp",
|
|
2215
|
+
"native_type": "bpchar(8)",
|
|
2216
|
+
"class": "non_bmp",
|
|
2217
|
+
"value_sql": "'𝔘'",
|
|
2218
|
+
"expect": {
|
|
2219
|
+
"warehouse": "𝔘 ",
|
|
2220
|
+
"oltp": "𝔘 "
|
|
2221
|
+
}
|
|
2222
|
+
},
|
|
2223
|
+
{
|
|
2224
|
+
"key": "postgres/bpchar(8)/null",
|
|
2225
|
+
"native_type": "bpchar(8)",
|
|
2226
|
+
"class": "null",
|
|
2227
|
+
"value_sql": "NULL",
|
|
2228
|
+
"expect": {
|
|
2229
|
+
"warehouse": null,
|
|
2230
|
+
"oltp": null
|
|
2231
|
+
}
|
|
2232
|
+
},
|
|
2233
|
+
{
|
|
2234
|
+
"key": "postgres/\"char\"/plain",
|
|
2235
|
+
"native_type": "\"char\"",
|
|
2236
|
+
"class": "plain",
|
|
2237
|
+
"value_sql": "'a'",
|
|
2238
|
+
"expect": {
|
|
2239
|
+
"warehouse": "a",
|
|
2240
|
+
"oltp": { "$error": "Postgres type `char`, which cannot be returned directly" }
|
|
2241
|
+
}
|
|
2242
|
+
},
|
|
2243
|
+
{
|
|
2244
|
+
"key": "postgres/\"char\"/null",
|
|
2245
|
+
"native_type": "\"char\"",
|
|
2246
|
+
"class": "null",
|
|
2247
|
+
"value_sql": "NULL",
|
|
2248
|
+
"expect": {
|
|
2249
|
+
"warehouse": null,
|
|
2250
|
+
"oltp": { "$error": "Postgres type `char`, which cannot be returned directly" }
|
|
2251
|
+
}
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
"key": "postgres/name/plain",
|
|
2255
|
+
"native_type": "name",
|
|
2256
|
+
"class": "plain",
|
|
2257
|
+
"value_sql": "'plain_name'",
|
|
2258
|
+
"expect": {
|
|
2259
|
+
"warehouse": "plain_name",
|
|
2260
|
+
"oltp": "plain_name"
|
|
2261
|
+
}
|
|
2262
|
+
},
|
|
2263
|
+
{
|
|
2264
|
+
"key": "postgres/name/empty",
|
|
2265
|
+
"native_type": "name",
|
|
2266
|
+
"class": "empty",
|
|
2267
|
+
"value_sql": "''",
|
|
2268
|
+
"expect": {
|
|
2269
|
+
"warehouse": "",
|
|
2270
|
+
"oltp": ""
|
|
2271
|
+
}
|
|
2272
|
+
},
|
|
2273
|
+
{
|
|
2274
|
+
"key": "postgres/name/len_gt_1k",
|
|
2275
|
+
"native_type": "name",
|
|
2276
|
+
"class": "len_gt_1k",
|
|
2277
|
+
"value_sql": "repeat('0123456789', 101)",
|
|
2278
|
+
"expect": {
|
|
2279
|
+
"warehouse": "012345678901234567890123456789012345678901234567890123456789012",
|
|
2280
|
+
"oltp": "012345678901234567890123456789012345678901234567890123456789012"
|
|
2281
|
+
}
|
|
2282
|
+
},
|
|
2283
|
+
{
|
|
2284
|
+
"key": "postgres/name/null",
|
|
2285
|
+
"native_type": "name",
|
|
2286
|
+
"class": "null",
|
|
2287
|
+
"value_sql": "NULL",
|
|
2288
|
+
"expect": {
|
|
2289
|
+
"warehouse": null,
|
|
2290
|
+
"oltp": null
|
|
2291
|
+
}
|
|
2292
|
+
},
|
|
2293
|
+
{
|
|
2294
|
+
"key": "postgres/bytea/plain",
|
|
2295
|
+
"native_type": "bytea",
|
|
2296
|
+
"class": "plain",
|
|
2297
|
+
"value_sql": "decode('0001ff', 'hex')",
|
|
2298
|
+
"expect": {
|
|
2299
|
+
"warehouse": "<3 bytes>",
|
|
2300
|
+
"oltp": { "$error": "Postgres type `bytea`, which cannot be returned directly" }
|
|
2301
|
+
}
|
|
2302
|
+
},
|
|
2303
|
+
{
|
|
2304
|
+
"key": "postgres/bytea/empty",
|
|
2305
|
+
"native_type": "bytea",
|
|
2306
|
+
"class": "empty",
|
|
2307
|
+
"value_sql": "decode('', 'hex')",
|
|
2308
|
+
"expect": {
|
|
2309
|
+
"warehouse": "<0 bytes>",
|
|
2310
|
+
"oltp": { "$error": "Postgres type `bytea`, which cannot be returned directly" }
|
|
2311
|
+
}
|
|
2312
|
+
},
|
|
2313
|
+
{
|
|
2314
|
+
"key": "postgres/bytea/null",
|
|
2315
|
+
"native_type": "bytea",
|
|
2316
|
+
"class": "null",
|
|
2317
|
+
"value_sql": "NULL",
|
|
2318
|
+
"expect": {
|
|
2319
|
+
"warehouse": null,
|
|
2320
|
+
"oltp": { "$error": "Postgres type `bytea`, which cannot be returned directly" }
|
|
2321
|
+
}
|
|
2322
|
+
},
|
|
2323
|
+
{
|
|
2324
|
+
"key": "postgres/date/plain",
|
|
2325
|
+
"native_type": "date",
|
|
2326
|
+
"class": "plain",
|
|
2327
|
+
"value_sql": "DATE '2024-03-10'",
|
|
2328
|
+
"expect": {
|
|
2329
|
+
"warehouse": "2024-03-10",
|
|
2330
|
+
"oltp": "2024-03-10"
|
|
2331
|
+
}
|
|
2332
|
+
},
|
|
2333
|
+
{
|
|
2334
|
+
"key": "postgres/date/date_before_1970",
|
|
2335
|
+
"native_type": "date",
|
|
2336
|
+
"class": "date_before_1970",
|
|
2337
|
+
"value_sql": "DATE '1969-12-31'",
|
|
2338
|
+
"expect": {
|
|
2339
|
+
"warehouse": "1969-12-31",
|
|
2340
|
+
"oltp": "1969-12-31"
|
|
2341
|
+
}
|
|
2342
|
+
},
|
|
2343
|
+
{
|
|
2344
|
+
"key": "postgres/date/date_after_2100",
|
|
2345
|
+
"native_type": "date",
|
|
2346
|
+
"class": "date_after_2100",
|
|
2347
|
+
"value_sql": "DATE '2150-06-01'",
|
|
2348
|
+
"expect": {
|
|
2349
|
+
"warehouse": "2150-06-01",
|
|
2350
|
+
"oltp": "2150-06-01"
|
|
2351
|
+
}
|
|
2352
|
+
},
|
|
2353
|
+
{
|
|
2354
|
+
"key": "postgres/date/null",
|
|
2355
|
+
"native_type": "date",
|
|
2356
|
+
"class": "null",
|
|
2357
|
+
"value_sql": "NULL",
|
|
2358
|
+
"expect": {
|
|
2359
|
+
"warehouse": null,
|
|
2360
|
+
"oltp": null
|
|
2361
|
+
}
|
|
2362
|
+
},
|
|
2363
|
+
{
|
|
2364
|
+
"key": "postgres/timestamp/plain",
|
|
2365
|
+
"native_type": "timestamp",
|
|
2366
|
+
"class": "plain",
|
|
2367
|
+
"value_sql": "TIMESTAMP '2024-03-10 12:34:56.789'",
|
|
2368
|
+
"expect": {
|
|
2369
|
+
"warehouse": "2024-03-10 12:34:56.789000",
|
|
2370
|
+
"oltp": "2024-03-10T12:34:56.789000"
|
|
2371
|
+
}
|
|
2372
|
+
},
|
|
2373
|
+
{
|
|
2374
|
+
"key": "postgres/timestamp/date_before_1970",
|
|
2375
|
+
"native_type": "timestamp",
|
|
2376
|
+
"class": "date_before_1970",
|
|
2377
|
+
"value_sql": "TIMESTAMP '1969-07-20 20:17:40.5'",
|
|
2378
|
+
"expect": {
|
|
2379
|
+
"warehouse": "1969-07-20 20:17:40.500000",
|
|
2380
|
+
"oltp": "1969-07-20T20:17:40.500000"
|
|
2381
|
+
}
|
|
2382
|
+
},
|
|
2383
|
+
{
|
|
2384
|
+
"key": "postgres/timestamp/date_after_2100",
|
|
2385
|
+
"native_type": "timestamp",
|
|
2386
|
+
"class": "date_after_2100",
|
|
2387
|
+
"value_sql": "TIMESTAMP '2150-01-01 00:00:00'",
|
|
2388
|
+
"expect": {
|
|
2389
|
+
"warehouse": "2150-01-01 00:00:00",
|
|
2390
|
+
"oltp": "2150-01-01T00:00:00.000000"
|
|
2391
|
+
}
|
|
2392
|
+
},
|
|
2393
|
+
{
|
|
2394
|
+
"key": "postgres/timestamp/null",
|
|
2395
|
+
"native_type": "timestamp",
|
|
2396
|
+
"class": "null",
|
|
2397
|
+
"value_sql": "NULL",
|
|
2398
|
+
"expect": {
|
|
2399
|
+
"warehouse": null,
|
|
2400
|
+
"oltp": null
|
|
2401
|
+
}
|
|
2402
|
+
},
|
|
2403
|
+
{
|
|
2404
|
+
"key": "postgres/timestamptz/plain",
|
|
2405
|
+
"native_type": "timestamptz",
|
|
2406
|
+
"class": "plain",
|
|
2407
|
+
"value_sql": "TIMESTAMPTZ '2024-03-10 12:34:56.789+00'",
|
|
2408
|
+
"expect": {
|
|
2409
|
+
"warehouse": "2024-03-10 12:34:56.789000",
|
|
2410
|
+
"oltp": "2024-03-10T12:34:56.789000Z"
|
|
2411
|
+
}
|
|
2412
|
+
},
|
|
2413
|
+
{
|
|
2414
|
+
"key": "postgres/timestamptz/tz_bearing",
|
|
2415
|
+
"native_type": "timestamptz",
|
|
2416
|
+
"class": "tz_bearing",
|
|
2417
|
+
"value_sql": "TIMESTAMPTZ '2024-03-10 12:34:56+05:30'",
|
|
2418
|
+
"expect": {
|
|
2419
|
+
"warehouse": "2024-03-10 07:04:56",
|
|
2420
|
+
"oltp": "2024-03-10T07:04:56.000000Z"
|
|
2421
|
+
}
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
"key": "postgres/timestamptz/date_before_1970",
|
|
2425
|
+
"native_type": "timestamptz",
|
|
2426
|
+
"class": "date_before_1970",
|
|
2427
|
+
"value_sql": "TIMESTAMPTZ '1969-07-20 20:17:40.5+00'",
|
|
2428
|
+
"expect": {
|
|
2429
|
+
"warehouse": "1969-07-20 20:17:40.500000",
|
|
2430
|
+
"oltp": "1969-07-20T20:17:40.500000Z"
|
|
2431
|
+
}
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
"key": "postgres/timestamptz/date_after_2100",
|
|
2435
|
+
"native_type": "timestamptz",
|
|
2436
|
+
"class": "date_after_2100",
|
|
2437
|
+
"value_sql": "TIMESTAMPTZ '2150-01-01 00:00:00+00'",
|
|
2438
|
+
"expect": {
|
|
2439
|
+
"warehouse": "2150-01-01 00:00:00",
|
|
2440
|
+
"oltp": "2150-01-01T00:00:00.000000Z"
|
|
2441
|
+
}
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
"key": "postgres/timestamptz/null",
|
|
2445
|
+
"native_type": "timestamptz",
|
|
2446
|
+
"class": "null",
|
|
2447
|
+
"value_sql": "NULL",
|
|
2448
|
+
"expect": {
|
|
2449
|
+
"warehouse": null,
|
|
2450
|
+
"oltp": null
|
|
2451
|
+
}
|
|
2452
|
+
},
|
|
2453
|
+
{
|
|
2454
|
+
"key": "postgres/time/plain",
|
|
2455
|
+
"native_type": "time",
|
|
2456
|
+
"class": "plain",
|
|
2457
|
+
"value_sql": "TIME '12:34:56.789'",
|
|
2458
|
+
"expect": {
|
|
2459
|
+
"warehouse": "12:34:56.789",
|
|
2460
|
+
"oltp": "12:34:56.789000"
|
|
2461
|
+
}
|
|
2462
|
+
},
|
|
2463
|
+
{
|
|
2464
|
+
"key": "postgres/time/null",
|
|
2465
|
+
"native_type": "time",
|
|
2466
|
+
"class": "null",
|
|
2467
|
+
"value_sql": "NULL",
|
|
2468
|
+
"expect": {
|
|
2469
|
+
"warehouse": null,
|
|
2470
|
+
"oltp": null
|
|
2471
|
+
}
|
|
2472
|
+
},
|
|
2473
|
+
{
|
|
2474
|
+
"key": "postgres/json/nested_json",
|
|
2475
|
+
"native_type": "json",
|
|
2476
|
+
"class": "nested_json",
|
|
2477
|
+
"value_sql": "'{\"a\":{\"b\":[1,2]},\"s\":\"x\"}'",
|
|
2478
|
+
"expect": {
|
|
2479
|
+
"warehouse": {
|
|
2480
|
+
"a": { "b": [1, 2] },
|
|
2481
|
+
"s": "x"
|
|
2482
|
+
},
|
|
2483
|
+
"oltp": {
|
|
2484
|
+
"a": { "b": [1, 2] },
|
|
2485
|
+
"s": "x"
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
},
|
|
2489
|
+
{
|
|
2490
|
+
"key": "postgres/json/null",
|
|
2491
|
+
"native_type": "json",
|
|
2492
|
+
"class": "null",
|
|
2493
|
+
"value_sql": "NULL",
|
|
2494
|
+
"expect": {
|
|
2495
|
+
"warehouse": null,
|
|
2496
|
+
"oltp": null
|
|
2497
|
+
}
|
|
2498
|
+
},
|
|
2499
|
+
{
|
|
2500
|
+
"key": "postgres/jsonb/nested_json",
|
|
2501
|
+
"native_type": "jsonb",
|
|
2502
|
+
"class": "nested_json",
|
|
2503
|
+
"value_sql": "'{\"a\":{\"b\":[1,2]},\"s\":\"x\"}'",
|
|
2504
|
+
"expect": {
|
|
2505
|
+
"warehouse": {
|
|
2506
|
+
"a": { "b": [1, 2] },
|
|
2507
|
+
"s": "x"
|
|
2508
|
+
},
|
|
2509
|
+
"oltp": {
|
|
2510
|
+
"a": { "b": [1, 2] },
|
|
2511
|
+
"s": "x"
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
},
|
|
2515
|
+
{
|
|
2516
|
+
"key": "postgres/jsonb/array",
|
|
2517
|
+
"native_type": "jsonb",
|
|
2518
|
+
"class": "array",
|
|
2519
|
+
"value_sql": "'[1,\"two\",null]'",
|
|
2520
|
+
"expect": {
|
|
2521
|
+
"warehouse": [
|
|
2522
|
+
1,
|
|
2523
|
+
"two",
|
|
2524
|
+
null
|
|
2525
|
+
],
|
|
2526
|
+
"oltp": [
|
|
2527
|
+
1,
|
|
2528
|
+
"two",
|
|
2529
|
+
null
|
|
2530
|
+
]
|
|
2531
|
+
}
|
|
2532
|
+
},
|
|
2533
|
+
{
|
|
2534
|
+
"key": "postgres/jsonb/null",
|
|
2535
|
+
"native_type": "jsonb",
|
|
2536
|
+
"class": "null",
|
|
2537
|
+
"value_sql": "NULL",
|
|
2538
|
+
"expect": {
|
|
2539
|
+
"warehouse": null,
|
|
2540
|
+
"oltp": null
|
|
2541
|
+
}
|
|
2542
|
+
},
|
|
2543
|
+
{
|
|
2544
|
+
"key": "postgres/uuid/plain",
|
|
2545
|
+
"native_type": "uuid",
|
|
2546
|
+
"class": "plain",
|
|
2547
|
+
"value_sql": "'00000000-0000-4000-8000-000000000001'",
|
|
2548
|
+
"expect": {
|
|
2549
|
+
"warehouse": "00000000-0000-4000-8000-000000000001",
|
|
2550
|
+
"oltp": "00000000-0000-4000-8000-000000000001"
|
|
2551
|
+
}
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
"key": "postgres/uuid/null",
|
|
2555
|
+
"native_type": "uuid",
|
|
2556
|
+
"class": "null",
|
|
2557
|
+
"value_sql": "NULL",
|
|
2558
|
+
"expect": {
|
|
2559
|
+
"warehouse": null,
|
|
2560
|
+
"oltp": null
|
|
2561
|
+
}
|
|
2562
|
+
},
|
|
2563
|
+
{
|
|
2564
|
+
"key": "postgres/int4[]/array",
|
|
2565
|
+
"native_type": "int4[]",
|
|
2566
|
+
"class": "array",
|
|
2567
|
+
"value_sql": "ARRAY[1, -2, 3]",
|
|
2568
|
+
"expect": {
|
|
2569
|
+
"warehouse": "{1,-2,3}",
|
|
2570
|
+
"oltp": { "$error": "Postgres type `_int4`, which cannot be returned directly" }
|
|
2571
|
+
}
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
"key": "postgres/int4[]/empty",
|
|
2575
|
+
"native_type": "int4[]",
|
|
2576
|
+
"class": "empty",
|
|
2577
|
+
"value_sql": "'{}'",
|
|
2578
|
+
"expect": {
|
|
2579
|
+
"warehouse": "{}",
|
|
2580
|
+
"oltp": { "$error": "Postgres type `_int4`, which cannot be returned directly" }
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
] },
|
|
2584
|
+
"duckdb": { "cases": [
|
|
2585
|
+
{
|
|
2586
|
+
"key": "duckdb/BOOLEAN/plain",
|
|
2587
|
+
"native_type": "BOOLEAN",
|
|
2588
|
+
"class": "plain",
|
|
2589
|
+
"value_sql": "true",
|
|
2590
|
+
"expect": { "warehouse": true }
|
|
2591
|
+
},
|
|
2592
|
+
{
|
|
2593
|
+
"key": "duckdb/BOOLEAN/null",
|
|
2594
|
+
"native_type": "BOOLEAN",
|
|
2595
|
+
"class": "null",
|
|
2596
|
+
"value_sql": "NULL",
|
|
2597
|
+
"expect": { "warehouse": null }
|
|
2598
|
+
},
|
|
2599
|
+
{
|
|
2600
|
+
"key": "duckdb/BOOL/plain",
|
|
2601
|
+
"native_type": "BOOL",
|
|
2602
|
+
"class": "plain",
|
|
2603
|
+
"value_sql": "false",
|
|
2604
|
+
"expect": { "warehouse": false }
|
|
2605
|
+
},
|
|
2606
|
+
{
|
|
2607
|
+
"key": "duckdb/TINYINT/plain",
|
|
2608
|
+
"native_type": "TINYINT",
|
|
2609
|
+
"class": "plain",
|
|
2610
|
+
"value_sql": "42",
|
|
2611
|
+
"expect": { "warehouse": 42 }
|
|
2612
|
+
},
|
|
2613
|
+
{
|
|
2614
|
+
"key": "duckdb/TINYINT/negative",
|
|
2615
|
+
"native_type": "TINYINT",
|
|
2616
|
+
"class": "negative",
|
|
2617
|
+
"value_sql": "-128",
|
|
2618
|
+
"expect": { "warehouse": -128 }
|
|
2619
|
+
},
|
|
2620
|
+
{
|
|
2621
|
+
"key": "duckdb/TINYINT/zero",
|
|
2622
|
+
"native_type": "TINYINT",
|
|
2623
|
+
"class": "zero",
|
|
2624
|
+
"value_sql": "0",
|
|
2625
|
+
"expect": { "warehouse": 0 }
|
|
2626
|
+
},
|
|
2627
|
+
{
|
|
2628
|
+
"key": "duckdb/TINYINT/null",
|
|
2629
|
+
"native_type": "TINYINT",
|
|
2630
|
+
"class": "null",
|
|
2631
|
+
"value_sql": "NULL",
|
|
2632
|
+
"expect": { "warehouse": null }
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
"key": "duckdb/INT1/plain",
|
|
2636
|
+
"native_type": "INT1",
|
|
2637
|
+
"class": "plain",
|
|
2638
|
+
"value_sql": "42",
|
|
2639
|
+
"expect": { "warehouse": 42 }
|
|
2640
|
+
},
|
|
2641
|
+
{
|
|
2642
|
+
"key": "duckdb/SMALLINT/plain",
|
|
2643
|
+
"native_type": "SMALLINT",
|
|
2644
|
+
"class": "plain",
|
|
2645
|
+
"value_sql": "42",
|
|
2646
|
+
"expect": { "warehouse": 42 }
|
|
2647
|
+
},
|
|
2648
|
+
{
|
|
2649
|
+
"key": "duckdb/SMALLINT/negative",
|
|
2650
|
+
"native_type": "SMALLINT",
|
|
2651
|
+
"class": "negative",
|
|
2652
|
+
"value_sql": "-32768",
|
|
2653
|
+
"expect": { "warehouse": -32768 }
|
|
2654
|
+
},
|
|
2655
|
+
{
|
|
2656
|
+
"key": "duckdb/SMALLINT/zero",
|
|
2657
|
+
"native_type": "SMALLINT",
|
|
2658
|
+
"class": "zero",
|
|
2659
|
+
"value_sql": "0",
|
|
2660
|
+
"expect": { "warehouse": 0 }
|
|
2661
|
+
},
|
|
2662
|
+
{
|
|
2663
|
+
"key": "duckdb/SMALLINT/null",
|
|
2664
|
+
"native_type": "SMALLINT",
|
|
2665
|
+
"class": "null",
|
|
2666
|
+
"value_sql": "NULL",
|
|
2667
|
+
"expect": { "warehouse": null }
|
|
2668
|
+
},
|
|
2669
|
+
{
|
|
2670
|
+
"key": "duckdb/INT2/plain",
|
|
2671
|
+
"native_type": "INT2",
|
|
2672
|
+
"class": "plain",
|
|
2673
|
+
"value_sql": "42",
|
|
2674
|
+
"expect": { "warehouse": 42 }
|
|
2675
|
+
},
|
|
2676
|
+
{
|
|
2677
|
+
"key": "duckdb/INTEGER/plain",
|
|
2678
|
+
"native_type": "INTEGER",
|
|
2679
|
+
"class": "plain",
|
|
2680
|
+
"value_sql": "42",
|
|
2681
|
+
"expect": { "warehouse": 42 }
|
|
2682
|
+
},
|
|
2683
|
+
{
|
|
2684
|
+
"key": "duckdb/INTEGER/negative",
|
|
2685
|
+
"native_type": "INTEGER",
|
|
2686
|
+
"class": "negative",
|
|
2687
|
+
"value_sql": "-2147483648",
|
|
2688
|
+
"expect": { "warehouse": -2147483648 }
|
|
2689
|
+
},
|
|
2690
|
+
{
|
|
2691
|
+
"key": "duckdb/INTEGER/zero",
|
|
2692
|
+
"native_type": "INTEGER",
|
|
2693
|
+
"class": "zero",
|
|
2694
|
+
"value_sql": "0",
|
|
2695
|
+
"expect": { "warehouse": 0 }
|
|
2696
|
+
},
|
|
2697
|
+
{
|
|
2698
|
+
"key": "duckdb/INTEGER/null",
|
|
2699
|
+
"native_type": "INTEGER",
|
|
2700
|
+
"class": "null",
|
|
2701
|
+
"value_sql": "NULL",
|
|
2702
|
+
"expect": { "warehouse": null }
|
|
2703
|
+
},
|
|
2704
|
+
{
|
|
2705
|
+
"key": "duckdb/INT/plain",
|
|
2706
|
+
"native_type": "INT",
|
|
2707
|
+
"class": "plain",
|
|
2708
|
+
"value_sql": "42",
|
|
2709
|
+
"expect": { "warehouse": 42 }
|
|
2710
|
+
},
|
|
2711
|
+
{
|
|
2712
|
+
"key": "duckdb/INT4/plain",
|
|
2713
|
+
"native_type": "INT4",
|
|
2714
|
+
"class": "plain",
|
|
2715
|
+
"value_sql": "42",
|
|
2716
|
+
"expect": { "warehouse": 42 }
|
|
2717
|
+
},
|
|
2718
|
+
{
|
|
2719
|
+
"key": "duckdb/BIGINT/plain",
|
|
2720
|
+
"native_type": "BIGINT",
|
|
2721
|
+
"class": "plain",
|
|
2722
|
+
"value_sql": "42",
|
|
2723
|
+
"expect": { "warehouse": 42 }
|
|
2724
|
+
},
|
|
2725
|
+
{
|
|
2726
|
+
"key": "duckdb/BIGINT/negative",
|
|
2727
|
+
"native_type": "BIGINT",
|
|
2728
|
+
"class": "negative",
|
|
2729
|
+
"value_sql": "-9007199254740991",
|
|
2730
|
+
"expect": { "warehouse": -9007199254740991 }
|
|
2731
|
+
},
|
|
2732
|
+
{
|
|
2733
|
+
"key": "duckdb/BIGINT/zero",
|
|
2734
|
+
"native_type": "BIGINT",
|
|
2735
|
+
"class": "zero",
|
|
2736
|
+
"value_sql": "0",
|
|
2737
|
+
"expect": { "warehouse": 0 }
|
|
2738
|
+
},
|
|
2739
|
+
{
|
|
2740
|
+
"key": "duckdb/BIGINT/precision_gt_18",
|
|
2741
|
+
"native_type": "BIGINT",
|
|
2742
|
+
"class": "precision_gt_18",
|
|
2743
|
+
"value_sql": "9223372036854775807",
|
|
2744
|
+
"expect": { "warehouse": 0x8000000000000000 }
|
|
2745
|
+
},
|
|
2746
|
+
{
|
|
2747
|
+
"key": "duckdb/BIGINT/null",
|
|
2748
|
+
"native_type": "BIGINT",
|
|
2749
|
+
"class": "null",
|
|
2750
|
+
"value_sql": "NULL",
|
|
2751
|
+
"expect": { "warehouse": null }
|
|
2752
|
+
},
|
|
2753
|
+
{
|
|
2754
|
+
"key": "duckdb/INT8/plain",
|
|
2755
|
+
"native_type": "INT8",
|
|
2756
|
+
"class": "plain",
|
|
2757
|
+
"value_sql": "42",
|
|
2758
|
+
"expect": { "warehouse": 42 }
|
|
2759
|
+
},
|
|
2760
|
+
{
|
|
2761
|
+
"key": "duckdb/UTINYINT/plain",
|
|
2762
|
+
"native_type": "UTINYINT",
|
|
2763
|
+
"class": "plain",
|
|
2764
|
+
"value_sql": "42",
|
|
2765
|
+
"expect": { "warehouse": 42 }
|
|
2766
|
+
},
|
|
2767
|
+
{
|
|
2768
|
+
"key": "duckdb/UTINYINT/zero",
|
|
2769
|
+
"native_type": "UTINYINT",
|
|
2770
|
+
"class": "zero",
|
|
2771
|
+
"value_sql": "0",
|
|
2772
|
+
"expect": { "warehouse": 0 }
|
|
2773
|
+
},
|
|
2774
|
+
{
|
|
2775
|
+
"key": "duckdb/UTINYINT/null",
|
|
2776
|
+
"native_type": "UTINYINT",
|
|
2777
|
+
"class": "null",
|
|
2778
|
+
"value_sql": "NULL",
|
|
2779
|
+
"expect": { "warehouse": null }
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
"key": "duckdb/USMALLINT/plain",
|
|
2783
|
+
"native_type": "USMALLINT",
|
|
2784
|
+
"class": "plain",
|
|
2785
|
+
"value_sql": "42",
|
|
2786
|
+
"expect": { "warehouse": 42 }
|
|
2787
|
+
},
|
|
2788
|
+
{
|
|
2789
|
+
"key": "duckdb/USMALLINT/zero",
|
|
2790
|
+
"native_type": "USMALLINT",
|
|
2791
|
+
"class": "zero",
|
|
2792
|
+
"value_sql": "0",
|
|
2793
|
+
"expect": { "warehouse": 0 }
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
"key": "duckdb/USMALLINT/null",
|
|
2797
|
+
"native_type": "USMALLINT",
|
|
2798
|
+
"class": "null",
|
|
2799
|
+
"value_sql": "NULL",
|
|
2800
|
+
"expect": { "warehouse": null }
|
|
2801
|
+
},
|
|
2802
|
+
{
|
|
2803
|
+
"key": "duckdb/UINTEGER/plain",
|
|
2804
|
+
"native_type": "UINTEGER",
|
|
2805
|
+
"class": "plain",
|
|
2806
|
+
"value_sql": "42",
|
|
2807
|
+
"expect": { "warehouse": 42 }
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
"key": "duckdb/UINTEGER/zero",
|
|
2811
|
+
"native_type": "UINTEGER",
|
|
2812
|
+
"class": "zero",
|
|
2813
|
+
"value_sql": "0",
|
|
2814
|
+
"expect": { "warehouse": 0 }
|
|
2815
|
+
},
|
|
2816
|
+
{
|
|
2817
|
+
"key": "duckdb/UINTEGER/null",
|
|
2818
|
+
"native_type": "UINTEGER",
|
|
2819
|
+
"class": "null",
|
|
2820
|
+
"value_sql": "NULL",
|
|
2821
|
+
"expect": { "warehouse": null }
|
|
2822
|
+
},
|
|
2823
|
+
{
|
|
2824
|
+
"key": "duckdb/UBIGINT/plain",
|
|
2825
|
+
"native_type": "UBIGINT",
|
|
2826
|
+
"class": "plain",
|
|
2827
|
+
"value_sql": "42",
|
|
2828
|
+
"expect": { "warehouse": 42 }
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
"key": "duckdb/UBIGINT/zero",
|
|
2832
|
+
"native_type": "UBIGINT",
|
|
2833
|
+
"class": "zero",
|
|
2834
|
+
"value_sql": "0",
|
|
2835
|
+
"expect": { "warehouse": 0 }
|
|
2836
|
+
},
|
|
2837
|
+
{
|
|
2838
|
+
"key": "duckdb/UBIGINT/precision_gt_18",
|
|
2839
|
+
"native_type": "UBIGINT",
|
|
2840
|
+
"class": "precision_gt_18",
|
|
2841
|
+
"value_sql": "'18446744073709551615'::UBIGINT",
|
|
2842
|
+
"expect": { "warehouse": "18446744073709551615" }
|
|
2843
|
+
},
|
|
2844
|
+
{
|
|
2845
|
+
"key": "duckdb/UBIGINT/null",
|
|
2846
|
+
"native_type": "UBIGINT",
|
|
2847
|
+
"class": "null",
|
|
2848
|
+
"value_sql": "NULL",
|
|
2849
|
+
"expect": { "warehouse": null }
|
|
2850
|
+
},
|
|
2851
|
+
{
|
|
2852
|
+
"key": "duckdb/HUGEINT/plain",
|
|
2853
|
+
"native_type": "HUGEINT",
|
|
2854
|
+
"class": "plain",
|
|
2855
|
+
"value_sql": "42",
|
|
2856
|
+
"expect": { "warehouse": "42" }
|
|
2857
|
+
},
|
|
2858
|
+
{
|
|
2859
|
+
"key": "duckdb/HUGEINT/negative",
|
|
2860
|
+
"native_type": "HUGEINT",
|
|
2861
|
+
"class": "negative",
|
|
2862
|
+
"value_sql": "-42",
|
|
2863
|
+
"expect": { "warehouse": "-42" }
|
|
2864
|
+
},
|
|
2865
|
+
{
|
|
2866
|
+
"key": "duckdb/HUGEINT/zero",
|
|
2867
|
+
"native_type": "HUGEINT",
|
|
2868
|
+
"class": "zero",
|
|
2869
|
+
"value_sql": "0",
|
|
2870
|
+
"expect": { "warehouse": "0" }
|
|
2871
|
+
},
|
|
2872
|
+
{
|
|
2873
|
+
"key": "duckdb/HUGEINT/precision_gt_18",
|
|
2874
|
+
"native_type": "HUGEINT",
|
|
2875
|
+
"class": "precision_gt_18",
|
|
2876
|
+
"value_sql": "'12345678901234567890123456789012345678'::HUGEINT",
|
|
2877
|
+
"expect": { "warehouse": "12345678901234567890123456789012345678" }
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
"key": "duckdb/HUGEINT/null",
|
|
2881
|
+
"native_type": "HUGEINT",
|
|
2882
|
+
"class": "null",
|
|
2883
|
+
"value_sql": "NULL",
|
|
2884
|
+
"expect": { "warehouse": null }
|
|
2885
|
+
},
|
|
2886
|
+
{
|
|
2887
|
+
"key": "duckdb/UHUGEINT/plain",
|
|
2888
|
+
"native_type": "UHUGEINT",
|
|
2889
|
+
"class": "plain",
|
|
2890
|
+
"value_sql": "42",
|
|
2891
|
+
"expect": { "warehouse": "42" }
|
|
2892
|
+
},
|
|
2893
|
+
{
|
|
2894
|
+
"key": "duckdb/UHUGEINT/zero",
|
|
2895
|
+
"native_type": "UHUGEINT",
|
|
2896
|
+
"class": "zero",
|
|
2897
|
+
"value_sql": "0",
|
|
2898
|
+
"expect": { "warehouse": "0" }
|
|
2899
|
+
},
|
|
2900
|
+
{
|
|
2901
|
+
"key": "duckdb/UHUGEINT/precision_gt_18",
|
|
2902
|
+
"native_type": "UHUGEINT",
|
|
2903
|
+
"class": "precision_gt_18",
|
|
2904
|
+
"value_sql": "'12345678901234567890123456789012345678'::UHUGEINT",
|
|
2905
|
+
"expect": { "warehouse": "12345678901234567890123456789012345678" }
|
|
2906
|
+
},
|
|
2907
|
+
{
|
|
2908
|
+
"key": "duckdb/UHUGEINT/null",
|
|
2909
|
+
"native_type": "UHUGEINT",
|
|
2910
|
+
"class": "null",
|
|
2911
|
+
"value_sql": "NULL",
|
|
2912
|
+
"expect": { "warehouse": null }
|
|
2913
|
+
},
|
|
2914
|
+
{
|
|
2915
|
+
"key": "duckdb/FLOAT/plain",
|
|
2916
|
+
"native_type": "FLOAT",
|
|
2917
|
+
"class": "plain",
|
|
2918
|
+
"value_sql": "0.1",
|
|
2919
|
+
"expect": { "warehouse": .10000000149011612 }
|
|
2920
|
+
},
|
|
2921
|
+
{
|
|
2922
|
+
"key": "duckdb/FLOAT/negative",
|
|
2923
|
+
"native_type": "FLOAT",
|
|
2924
|
+
"class": "negative",
|
|
2925
|
+
"value_sql": "-1.5",
|
|
2926
|
+
"expect": { "warehouse": -1.5 }
|
|
2927
|
+
},
|
|
2928
|
+
{
|
|
2929
|
+
"key": "duckdb/FLOAT/zero",
|
|
2930
|
+
"native_type": "FLOAT",
|
|
2931
|
+
"class": "zero",
|
|
2932
|
+
"value_sql": "0",
|
|
2933
|
+
"expect": { "warehouse": 0 }
|
|
2934
|
+
},
|
|
2935
|
+
{
|
|
2936
|
+
"key": "duckdb/FLOAT/nan_inf",
|
|
2937
|
+
"native_type": "FLOAT",
|
|
2938
|
+
"class": "nan_inf",
|
|
2939
|
+
"value_sql": "'nan'::FLOAT",
|
|
2940
|
+
"expect": { "warehouse": null }
|
|
2941
|
+
},
|
|
2942
|
+
{
|
|
2943
|
+
"key": "duckdb/FLOAT/null",
|
|
2944
|
+
"native_type": "FLOAT",
|
|
2945
|
+
"class": "null",
|
|
2946
|
+
"value_sql": "NULL",
|
|
2947
|
+
"expect": { "warehouse": null }
|
|
2948
|
+
},
|
|
2949
|
+
{
|
|
2950
|
+
"key": "duckdb/REAL/plain",
|
|
2951
|
+
"native_type": "REAL",
|
|
2952
|
+
"class": "plain",
|
|
2953
|
+
"value_sql": "0.1",
|
|
2954
|
+
"expect": { "warehouse": .10000000149011612 }
|
|
2955
|
+
},
|
|
2956
|
+
{
|
|
2957
|
+
"key": "duckdb/FLOAT4/plain",
|
|
2958
|
+
"native_type": "FLOAT4",
|
|
2959
|
+
"class": "plain",
|
|
2960
|
+
"value_sql": "0.1",
|
|
2961
|
+
"expect": { "warehouse": .10000000149011612 }
|
|
2962
|
+
},
|
|
2963
|
+
{
|
|
2964
|
+
"key": "duckdb/DOUBLE/plain",
|
|
2965
|
+
"native_type": "DOUBLE",
|
|
2966
|
+
"class": "plain",
|
|
2967
|
+
"value_sql": "0.1",
|
|
2968
|
+
"expect": { "warehouse": .1 }
|
|
2969
|
+
},
|
|
2970
|
+
{
|
|
2971
|
+
"key": "duckdb/DOUBLE/negative",
|
|
2972
|
+
"native_type": "DOUBLE",
|
|
2973
|
+
"class": "negative",
|
|
2974
|
+
"value_sql": "-1.5",
|
|
2975
|
+
"expect": { "warehouse": -1.5 }
|
|
2976
|
+
},
|
|
2977
|
+
{
|
|
2978
|
+
"key": "duckdb/DOUBLE/zero",
|
|
2979
|
+
"native_type": "DOUBLE",
|
|
2980
|
+
"class": "zero",
|
|
2981
|
+
"value_sql": "0",
|
|
2982
|
+
"expect": { "warehouse": 0 }
|
|
2983
|
+
},
|
|
2984
|
+
{
|
|
2985
|
+
"key": "duckdb/DOUBLE/nan_inf",
|
|
2986
|
+
"native_type": "DOUBLE",
|
|
2987
|
+
"class": "nan_inf",
|
|
2988
|
+
"value_sql": "'inf'::DOUBLE",
|
|
2989
|
+
"expect": { "warehouse": null }
|
|
2990
|
+
},
|
|
2991
|
+
{
|
|
2992
|
+
"key": "duckdb/DOUBLE/null",
|
|
2993
|
+
"native_type": "DOUBLE",
|
|
2994
|
+
"class": "null",
|
|
2995
|
+
"value_sql": "NULL",
|
|
2996
|
+
"expect": { "warehouse": null }
|
|
2997
|
+
},
|
|
2998
|
+
{
|
|
2999
|
+
"key": "duckdb/FLOAT8/plain",
|
|
3000
|
+
"native_type": "FLOAT8",
|
|
3001
|
+
"class": "plain",
|
|
3002
|
+
"value_sql": "0.1",
|
|
3003
|
+
"expect": { "warehouse": .1 }
|
|
3004
|
+
},
|
|
3005
|
+
{
|
|
3006
|
+
"key": "duckdb/DECIMAL(18,4)/plain",
|
|
3007
|
+
"native_type": "DECIMAL(18,4)",
|
|
3008
|
+
"class": "plain",
|
|
3009
|
+
"value_sql": "1234.5678",
|
|
3010
|
+
"expect": { "warehouse": "1234.5678" }
|
|
3011
|
+
},
|
|
3012
|
+
{
|
|
3013
|
+
"key": "duckdb/DECIMAL(18,4)/negative",
|
|
3014
|
+
"native_type": "DECIMAL(18,4)",
|
|
3015
|
+
"class": "negative",
|
|
3016
|
+
"value_sql": "-1.25",
|
|
3017
|
+
"expect": { "warehouse": "-1.2500" }
|
|
3018
|
+
},
|
|
3019
|
+
{
|
|
3020
|
+
"key": "duckdb/DECIMAL(18,4)/zero",
|
|
3021
|
+
"native_type": "DECIMAL(18,4)",
|
|
3022
|
+
"class": "zero",
|
|
3023
|
+
"value_sql": "0",
|
|
3024
|
+
"expect": { "warehouse": "0.0000" }
|
|
3025
|
+
},
|
|
3026
|
+
{
|
|
3027
|
+
"key": "duckdb/DECIMAL(18,4)/null",
|
|
3028
|
+
"native_type": "DECIMAL(18,4)",
|
|
3029
|
+
"class": "null",
|
|
3030
|
+
"value_sql": "NULL",
|
|
3031
|
+
"expect": { "warehouse": null }
|
|
3032
|
+
},
|
|
3033
|
+
{
|
|
3034
|
+
"key": "duckdb/DECIMAL(38,10)/precision_gt_18",
|
|
3035
|
+
"native_type": "DECIMAL(38,10)",
|
|
3036
|
+
"class": "precision_gt_18",
|
|
3037
|
+
"value_sql": "'1234567890123456789.0123456789'::DECIMAL(38,10)",
|
|
3038
|
+
"expect": { "warehouse": "1234567890123456789.0123456789" }
|
|
3039
|
+
},
|
|
3040
|
+
{
|
|
3041
|
+
"key": "duckdb/VARCHAR/plain",
|
|
3042
|
+
"native_type": "VARCHAR",
|
|
3043
|
+
"class": "plain",
|
|
3044
|
+
"value_sql": "'plain text'",
|
|
3045
|
+
"expect": { "warehouse": "plain text" }
|
|
3046
|
+
},
|
|
3047
|
+
{
|
|
3048
|
+
"key": "duckdb/VARCHAR/empty",
|
|
3049
|
+
"native_type": "VARCHAR",
|
|
3050
|
+
"class": "empty",
|
|
3051
|
+
"value_sql": "''",
|
|
3052
|
+
"expect": { "warehouse": "" }
|
|
3053
|
+
},
|
|
3054
|
+
{
|
|
3055
|
+
"key": "duckdb/VARCHAR/whitespace",
|
|
3056
|
+
"native_type": "VARCHAR",
|
|
3057
|
+
"class": "whitespace",
|
|
3058
|
+
"value_sql": "chr(32) || chr(9) || chr(10) || chr(32)",
|
|
3059
|
+
"expect": { "warehouse": " \n " }
|
|
3060
|
+
},
|
|
3061
|
+
{
|
|
3062
|
+
"key": "duckdb/VARCHAR/non_bmp",
|
|
3063
|
+
"native_type": "VARCHAR",
|
|
3064
|
+
"class": "non_bmp",
|
|
3065
|
+
"value_sql": "'𝔘nicode'",
|
|
3066
|
+
"expect": { "warehouse": "𝔘nicode" }
|
|
3067
|
+
},
|
|
3068
|
+
{
|
|
3069
|
+
"key": "duckdb/VARCHAR/len_gt_1k",
|
|
3070
|
+
"native_type": "VARCHAR",
|
|
3071
|
+
"class": "len_gt_1k",
|
|
3072
|
+
"value_sql": "repeat('0123456789', 101)",
|
|
3073
|
+
"expect": { "warehouse": "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" }
|
|
3074
|
+
},
|
|
3075
|
+
{
|
|
3076
|
+
"key": "duckdb/VARCHAR/null",
|
|
3077
|
+
"native_type": "VARCHAR",
|
|
3078
|
+
"class": "null",
|
|
3079
|
+
"value_sql": "NULL",
|
|
3080
|
+
"expect": { "warehouse": null }
|
|
3081
|
+
},
|
|
3082
|
+
{
|
|
3083
|
+
"key": "duckdb/CHAR/plain",
|
|
3084
|
+
"native_type": "CHAR",
|
|
3085
|
+
"class": "plain",
|
|
3086
|
+
"value_sql": "'plain text'",
|
|
3087
|
+
"expect": { "warehouse": "plain text" }
|
|
3088
|
+
},
|
|
3089
|
+
{
|
|
3090
|
+
"key": "duckdb/BPCHAR/plain",
|
|
3091
|
+
"native_type": "BPCHAR",
|
|
3092
|
+
"class": "plain",
|
|
3093
|
+
"value_sql": "'plain text'",
|
|
3094
|
+
"expect": { "warehouse": "plain text" }
|
|
3095
|
+
},
|
|
3096
|
+
{
|
|
3097
|
+
"key": "duckdb/TEXT/plain",
|
|
3098
|
+
"native_type": "TEXT",
|
|
3099
|
+
"class": "plain",
|
|
3100
|
+
"value_sql": "'plain text'",
|
|
3101
|
+
"expect": { "warehouse": "plain text" }
|
|
3102
|
+
},
|
|
3103
|
+
{
|
|
3104
|
+
"key": "duckdb/STRING/plain",
|
|
3105
|
+
"native_type": "STRING",
|
|
3106
|
+
"class": "plain",
|
|
3107
|
+
"value_sql": "'plain text'",
|
|
3108
|
+
"expect": { "warehouse": "plain text" }
|
|
3109
|
+
},
|
|
3110
|
+
{
|
|
3111
|
+
"key": "duckdb/UUID/plain",
|
|
3112
|
+
"native_type": "UUID",
|
|
3113
|
+
"class": "plain",
|
|
3114
|
+
"value_sql": "'00000000-0000-4000-8000-000000000001'",
|
|
3115
|
+
"expect": { "warehouse": "00000000-0000-4000-8000-000000000001" }
|
|
3116
|
+
},
|
|
3117
|
+
{
|
|
3118
|
+
"key": "duckdb/UUID/null",
|
|
3119
|
+
"native_type": "UUID",
|
|
3120
|
+
"class": "null",
|
|
3121
|
+
"value_sql": "NULL",
|
|
3122
|
+
"expect": { "warehouse": null }
|
|
3123
|
+
},
|
|
3124
|
+
{
|
|
3125
|
+
"key": "duckdb/BLOB/plain",
|
|
3126
|
+
"native_type": "BLOB",
|
|
3127
|
+
"class": "plain",
|
|
3128
|
+
"value_sql": "'\\x00\\x01\\xFF'::BLOB",
|
|
3129
|
+
"expect": { "warehouse": "<3 bytes>" }
|
|
3130
|
+
},
|
|
3131
|
+
{
|
|
3132
|
+
"key": "duckdb/BLOB/empty",
|
|
3133
|
+
"native_type": "BLOB",
|
|
3134
|
+
"class": "empty",
|
|
3135
|
+
"value_sql": "''::BLOB",
|
|
3136
|
+
"expect": { "warehouse": "<0 bytes>" }
|
|
3137
|
+
},
|
|
3138
|
+
{
|
|
3139
|
+
"key": "duckdb/BLOB/null",
|
|
3140
|
+
"native_type": "BLOB",
|
|
3141
|
+
"class": "null",
|
|
3142
|
+
"value_sql": "NULL",
|
|
3143
|
+
"expect": { "warehouse": null }
|
|
3144
|
+
},
|
|
3145
|
+
{
|
|
3146
|
+
"key": "duckdb/BYTEA/plain",
|
|
3147
|
+
"native_type": "BYTEA",
|
|
3148
|
+
"class": "plain",
|
|
3149
|
+
"value_sql": "'\\x00\\x01\\xFF'::BLOB",
|
|
3150
|
+
"expect": { "warehouse": "<3 bytes>" }
|
|
3151
|
+
},
|
|
3152
|
+
{
|
|
3153
|
+
"key": "duckdb/BINARY/plain",
|
|
3154
|
+
"native_type": "BINARY",
|
|
3155
|
+
"class": "plain",
|
|
3156
|
+
"value_sql": "'\\x00\\x01\\xFF'::BLOB",
|
|
3157
|
+
"expect": { "warehouse": "<3 bytes>" }
|
|
3158
|
+
},
|
|
3159
|
+
{
|
|
3160
|
+
"key": "duckdb/VARBINARY/plain",
|
|
3161
|
+
"native_type": "VARBINARY",
|
|
3162
|
+
"class": "plain",
|
|
3163
|
+
"value_sql": "'\\x00\\x01\\xFF'::BLOB",
|
|
3164
|
+
"expect": { "warehouse": "<3 bytes>" }
|
|
3165
|
+
},
|
|
3166
|
+
{
|
|
3167
|
+
"key": "duckdb/DATE/plain",
|
|
3168
|
+
"native_type": "DATE",
|
|
3169
|
+
"class": "plain",
|
|
3170
|
+
"value_sql": "DATE '2024-03-10'",
|
|
3171
|
+
"expect": { "warehouse": "2024-03-10" }
|
|
3172
|
+
},
|
|
3173
|
+
{
|
|
3174
|
+
"key": "duckdb/DATE/date_before_1970",
|
|
3175
|
+
"native_type": "DATE",
|
|
3176
|
+
"class": "date_before_1970",
|
|
3177
|
+
"value_sql": "DATE '1969-12-31'",
|
|
3178
|
+
"expect": { "warehouse": "1969-12-31" }
|
|
3179
|
+
},
|
|
3180
|
+
{
|
|
3181
|
+
"key": "duckdb/DATE/date_after_2100",
|
|
3182
|
+
"native_type": "DATE",
|
|
3183
|
+
"class": "date_after_2100",
|
|
3184
|
+
"value_sql": "DATE '2150-06-01'",
|
|
3185
|
+
"expect": { "warehouse": "2150-06-01" }
|
|
3186
|
+
},
|
|
3187
|
+
{
|
|
3188
|
+
"key": "duckdb/DATE/null",
|
|
3189
|
+
"native_type": "DATE",
|
|
3190
|
+
"class": "null",
|
|
3191
|
+
"value_sql": "NULL",
|
|
3192
|
+
"expect": { "warehouse": null }
|
|
3193
|
+
},
|
|
3194
|
+
{
|
|
3195
|
+
"key": "duckdb/TIMESTAMP/plain",
|
|
3196
|
+
"native_type": "TIMESTAMP",
|
|
3197
|
+
"class": "plain",
|
|
3198
|
+
"value_sql": "TIMESTAMP '2024-03-10 12:34:56.789'",
|
|
3199
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.789000" }
|
|
3200
|
+
},
|
|
3201
|
+
{
|
|
3202
|
+
"key": "duckdb/TIMESTAMP/date_before_1970",
|
|
3203
|
+
"native_type": "TIMESTAMP",
|
|
3204
|
+
"class": "date_before_1970",
|
|
3205
|
+
"value_sql": "TIMESTAMP '1969-07-20 20:17:40.5'",
|
|
3206
|
+
"expect": { "warehouse": "1969-07-20 20:17:40.500000" }
|
|
3207
|
+
},
|
|
3208
|
+
{
|
|
3209
|
+
"key": "duckdb/TIMESTAMP/date_after_2100",
|
|
3210
|
+
"native_type": "TIMESTAMP",
|
|
3211
|
+
"class": "date_after_2100",
|
|
3212
|
+
"value_sql": "TIMESTAMP '2150-01-01 00:00:00'",
|
|
3213
|
+
"expect": { "warehouse": "2150-01-01 00:00:00" }
|
|
3214
|
+
},
|
|
3215
|
+
{
|
|
3216
|
+
"key": "duckdb/TIMESTAMP/null",
|
|
3217
|
+
"native_type": "TIMESTAMP",
|
|
3218
|
+
"class": "null",
|
|
3219
|
+
"value_sql": "NULL",
|
|
3220
|
+
"expect": { "warehouse": null }
|
|
3221
|
+
},
|
|
3222
|
+
{
|
|
3223
|
+
"key": "duckdb/DATETIME/plain",
|
|
3224
|
+
"native_type": "DATETIME",
|
|
3225
|
+
"class": "plain",
|
|
3226
|
+
"value_sql": "TIMESTAMP '2024-03-10 12:34:56.789'",
|
|
3227
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.789000" }
|
|
3228
|
+
},
|
|
3229
|
+
{
|
|
3230
|
+
"key": "duckdb/TIMESTAMP_US/plain",
|
|
3231
|
+
"native_type": "TIMESTAMP_US",
|
|
3232
|
+
"class": "plain",
|
|
3233
|
+
"value_sql": "TIMESTAMP '2024-03-10 12:34:56.789'",
|
|
3234
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.789000" }
|
|
3235
|
+
},
|
|
3236
|
+
{
|
|
3237
|
+
"key": "duckdb/TIMESTAMP_S/plain",
|
|
3238
|
+
"native_type": "TIMESTAMP_S",
|
|
3239
|
+
"class": "plain",
|
|
3240
|
+
"value_sql": "'2024-03-10 12:34:56'::TIMESTAMP_S",
|
|
3241
|
+
"expect": { "warehouse": "2024-03-10 12:34:56" }
|
|
3242
|
+
},
|
|
3243
|
+
{
|
|
3244
|
+
"key": "duckdb/TIMESTAMP_S/date_before_1970",
|
|
3245
|
+
"native_type": "TIMESTAMP_S",
|
|
3246
|
+
"class": "date_before_1970",
|
|
3247
|
+
"value_sql": "'1969-07-20 20:17:40'::TIMESTAMP_S",
|
|
3248
|
+
"expect": { "warehouse": "1969-07-20 20:17:40" }
|
|
3249
|
+
},
|
|
3250
|
+
{
|
|
3251
|
+
"key": "duckdb/TIMESTAMP_S/date_after_2100",
|
|
3252
|
+
"native_type": "TIMESTAMP_S",
|
|
3253
|
+
"class": "date_after_2100",
|
|
3254
|
+
"value_sql": "'2150-01-01 00:00:00'::TIMESTAMP_S",
|
|
3255
|
+
"expect": { "warehouse": "2150-01-01 00:00:00" }
|
|
3256
|
+
},
|
|
3257
|
+
{
|
|
3258
|
+
"key": "duckdb/TIMESTAMP_S/null",
|
|
3259
|
+
"native_type": "TIMESTAMP_S",
|
|
3260
|
+
"class": "null",
|
|
3261
|
+
"value_sql": "NULL",
|
|
3262
|
+
"expect": { "warehouse": null }
|
|
3263
|
+
},
|
|
3264
|
+
{
|
|
3265
|
+
"key": "duckdb/TIMESTAMP_MS/plain",
|
|
3266
|
+
"native_type": "TIMESTAMP_MS",
|
|
3267
|
+
"class": "plain",
|
|
3268
|
+
"value_sql": "'2024-03-10 12:34:56.789'::TIMESTAMP_MS",
|
|
3269
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.789000" }
|
|
3270
|
+
},
|
|
3271
|
+
{
|
|
3272
|
+
"key": "duckdb/TIMESTAMP_MS/date_before_1970",
|
|
3273
|
+
"native_type": "TIMESTAMP_MS",
|
|
3274
|
+
"class": "date_before_1970",
|
|
3275
|
+
"value_sql": "'1969-07-20 20:17:40.5'::TIMESTAMP_MS",
|
|
3276
|
+
"expect": { "warehouse": "1969-07-20 20:17:40.500000" }
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
"key": "duckdb/TIMESTAMP_MS/date_after_2100",
|
|
3280
|
+
"native_type": "TIMESTAMP_MS",
|
|
3281
|
+
"class": "date_after_2100",
|
|
3282
|
+
"value_sql": "'2150-01-01 00:00:00'::TIMESTAMP_MS",
|
|
3283
|
+
"expect": { "warehouse": "2150-01-01 00:00:00" }
|
|
3284
|
+
},
|
|
3285
|
+
{
|
|
3286
|
+
"key": "duckdb/TIMESTAMP_MS/null",
|
|
3287
|
+
"native_type": "TIMESTAMP_MS",
|
|
3288
|
+
"class": "null",
|
|
3289
|
+
"value_sql": "NULL",
|
|
3290
|
+
"expect": { "warehouse": null }
|
|
3291
|
+
},
|
|
3292
|
+
{
|
|
3293
|
+
"key": "duckdb/TIMESTAMP_NS/plain",
|
|
3294
|
+
"native_type": "TIMESTAMP_NS",
|
|
3295
|
+
"class": "plain",
|
|
3296
|
+
"value_sql": "'2024-03-10 12:34:56.789123456'::TIMESTAMP_NS",
|
|
3297
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.789123" }
|
|
3298
|
+
},
|
|
3299
|
+
{
|
|
3300
|
+
"key": "duckdb/TIMESTAMP_NS/date_before_1970",
|
|
3301
|
+
"native_type": "TIMESTAMP_NS",
|
|
3302
|
+
"class": "date_before_1970",
|
|
3303
|
+
"value_sql": "'1969-07-20 20:17:40.500000001'::TIMESTAMP_NS",
|
|
3304
|
+
"expect": { "warehouse": "1969-07-20 20:17:40.500000" }
|
|
3305
|
+
},
|
|
3306
|
+
{
|
|
3307
|
+
"key": "duckdb/TIMESTAMP_NS/date_after_2100",
|
|
3308
|
+
"native_type": "TIMESTAMP_NS",
|
|
3309
|
+
"class": "date_after_2100",
|
|
3310
|
+
"value_sql": "'2150-01-01 00:00:00'::TIMESTAMP_NS",
|
|
3311
|
+
"expect": { "warehouse": "2150-01-01 00:00:00" }
|
|
3312
|
+
},
|
|
3313
|
+
{
|
|
3314
|
+
"key": "duckdb/TIMESTAMP_NS/null",
|
|
3315
|
+
"native_type": "TIMESTAMP_NS",
|
|
3316
|
+
"class": "null",
|
|
3317
|
+
"value_sql": "NULL",
|
|
3318
|
+
"expect": { "warehouse": null }
|
|
3319
|
+
},
|
|
3320
|
+
{
|
|
3321
|
+
"key": "duckdb/TIMESTAMP WITH TIME ZONE/plain",
|
|
3322
|
+
"native_type": "TIMESTAMP WITH TIME ZONE",
|
|
3323
|
+
"class": "plain",
|
|
3324
|
+
"value_sql": "TIMESTAMPTZ '2024-03-10 12:34:56.789+00'",
|
|
3325
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.789000" }
|
|
3326
|
+
},
|
|
3327
|
+
{
|
|
3328
|
+
"key": "duckdb/TIMESTAMP WITH TIME ZONE/tz_bearing",
|
|
3329
|
+
"native_type": "TIMESTAMP WITH TIME ZONE",
|
|
3330
|
+
"class": "tz_bearing",
|
|
3331
|
+
"value_sql": "TIMESTAMPTZ '2024-03-10 12:34:56+05:30'",
|
|
3332
|
+
"expect": { "warehouse": "2024-03-10 07:04:56" }
|
|
3333
|
+
},
|
|
3334
|
+
{
|
|
3335
|
+
"key": "duckdb/TIMESTAMP WITH TIME ZONE/date_before_1970",
|
|
3336
|
+
"native_type": "TIMESTAMP WITH TIME ZONE",
|
|
3337
|
+
"class": "date_before_1970",
|
|
3338
|
+
"value_sql": "TIMESTAMPTZ '1969-07-20 20:17:40.5+00'",
|
|
3339
|
+
"expect": { "warehouse": "1969-07-20 20:17:40.500000" }
|
|
3340
|
+
},
|
|
3341
|
+
{
|
|
3342
|
+
"key": "duckdb/TIMESTAMP WITH TIME ZONE/date_after_2100",
|
|
3343
|
+
"native_type": "TIMESTAMP WITH TIME ZONE",
|
|
3344
|
+
"class": "date_after_2100",
|
|
3345
|
+
"value_sql": "TIMESTAMPTZ '2150-01-01 00:00:00+00'",
|
|
3346
|
+
"expect": { "warehouse": "2150-01-01 00:00:00" }
|
|
3347
|
+
},
|
|
3348
|
+
{
|
|
3349
|
+
"key": "duckdb/TIMESTAMP WITH TIME ZONE/null",
|
|
3350
|
+
"native_type": "TIMESTAMP WITH TIME ZONE",
|
|
3351
|
+
"class": "null",
|
|
3352
|
+
"value_sql": "NULL",
|
|
3353
|
+
"expect": { "warehouse": null }
|
|
3354
|
+
},
|
|
3355
|
+
{
|
|
3356
|
+
"key": "duckdb/TIMESTAMPTZ/plain",
|
|
3357
|
+
"native_type": "TIMESTAMPTZ",
|
|
3358
|
+
"class": "plain",
|
|
3359
|
+
"value_sql": "TIMESTAMPTZ '2024-03-10 12:34:56.789+00'",
|
|
3360
|
+
"expect": { "warehouse": "2024-03-10 12:34:56.789000" }
|
|
3361
|
+
},
|
|
3362
|
+
{
|
|
3363
|
+
"key": "duckdb/JSON/nested_json",
|
|
3364
|
+
"native_type": "JSON",
|
|
3365
|
+
"class": "nested_json",
|
|
3366
|
+
"value_sql": "'{\"a\":{\"b\":[1,2]},\"s\":\"x\"}'",
|
|
3367
|
+
"expect": { "warehouse": "{\"a\":{\"b\":[1,2]},\"s\":\"x\"}" }
|
|
3368
|
+
},
|
|
3369
|
+
{
|
|
3370
|
+
"key": "duckdb/JSON/array",
|
|
3371
|
+
"native_type": "JSON",
|
|
3372
|
+
"class": "array",
|
|
3373
|
+
"value_sql": "'[1,\"two\",null]'",
|
|
3374
|
+
"expect": { "warehouse": "[1,\"two\",null]" }
|
|
3375
|
+
},
|
|
3376
|
+
{
|
|
3377
|
+
"key": "duckdb/JSON/null",
|
|
3378
|
+
"native_type": "JSON",
|
|
3379
|
+
"class": "null",
|
|
3380
|
+
"value_sql": "NULL",
|
|
3381
|
+
"expect": { "warehouse": null }
|
|
3382
|
+
},
|
|
3383
|
+
{
|
|
3384
|
+
"key": "duckdb/INTEGER[]/array",
|
|
3385
|
+
"native_type": "INTEGER[]",
|
|
3386
|
+
"class": "array",
|
|
3387
|
+
"value_sql": "[1, -2, 3]",
|
|
3388
|
+
"expect": { "warehouse": "List([Int(1), Int(-2), Int(3)])" }
|
|
3389
|
+
},
|
|
3390
|
+
{
|
|
3391
|
+
"key": "duckdb/INTEGER[]/empty",
|
|
3392
|
+
"native_type": "INTEGER[]",
|
|
3393
|
+
"class": "empty",
|
|
3394
|
+
"value_sql": "[]",
|
|
3395
|
+
"expect": { "warehouse": "List([])" }
|
|
3396
|
+
},
|
|
3397
|
+
{
|
|
3398
|
+
"key": "duckdb/INTEGER[]/null",
|
|
3399
|
+
"native_type": "INTEGER[]",
|
|
3400
|
+
"class": "null",
|
|
3401
|
+
"value_sql": "NULL",
|
|
3402
|
+
"expect": { "warehouse": null }
|
|
3403
|
+
}
|
|
3404
|
+
] }
|
|
3405
|
+
}
|
|
3406
|
+
};
|
|
3407
|
+
|
|
3408
|
+
//#endregion
|
|
3409
|
+
//#region src/testing/shape-zoo-sha256.ts
|
|
3410
|
+
const ZOO_SHA256 = "04f75f9054a46e521d8787d65f5d214440d041860971c77e66edc7bfbaf8d16a";
|
|
3411
|
+
|
|
3412
|
+
//#endregion
|
|
3413
|
+
//#region src/testing/zoo.ts
|
|
3414
|
+
/** A value a read would REFUSE rather than return: `expect.oltp.$error`. */
|
|
3415
|
+
var ZooRefusal = class {
|
|
3416
|
+
constructor(column, nativeType, detail) {
|
|
3417
|
+
this.column = column;
|
|
3418
|
+
this.nativeType = nativeType;
|
|
3419
|
+
this.detail = detail;
|
|
3420
|
+
}
|
|
3421
|
+
/** The host's full sentence for it, from the contract. */
|
|
3422
|
+
message() {
|
|
3423
|
+
return render(REFUSALS.oltpUnsupportedColumn, {
|
|
3424
|
+
name: this.column,
|
|
3425
|
+
ty: this.nativeType
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
3428
|
+
};
|
|
3429
|
+
const ZOO = shape_zoo_default;
|
|
3430
|
+
/** The zoo table's name, from the fixture's digest — the canary's `zooTableName`. */
|
|
3431
|
+
function zooTableName(sha256Hex = ZOO_SHA256) {
|
|
3432
|
+
return `oxy_shape_zoo_${sha256Hex.slice(0, 8)}`;
|
|
3433
|
+
}
|
|
3434
|
+
/** `c001`, `c002`, … — the zoo column for case `index`. */
|
|
3435
|
+
function zooColumn(index) {
|
|
3436
|
+
return `c${String(index + 1).padStart(3, "0")}`;
|
|
3437
|
+
}
|
|
3438
|
+
const isError = (v) => typeof v === "object" && v !== null && "$error" in v;
|
|
3439
|
+
/** What a read of case `c` on `plane` returns — or refuses. */
|
|
3440
|
+
function expectation(c, plane, column) {
|
|
3441
|
+
const value = plane === "oltp" ? c.expect.oltp : c.expect.warehouse;
|
|
3442
|
+
return isError(value) ? new ZooRefusal(column, c.native_type, value.$error) : value;
|
|
3443
|
+
}
|
|
3444
|
+
/** Every case of `engine` for `nativeType`, as written in the zoo. */
|
|
3445
|
+
function casesFor(engine, nativeType) {
|
|
3446
|
+
return ZOO.engines[engine].cases.filter((c) => c.native_type === nativeType);
|
|
3447
|
+
}
|
|
3448
|
+
/** The native types the zoo pins for `engine`, for a declaration error to list. */
|
|
3449
|
+
function knownTypes(engine) {
|
|
3450
|
+
return [...new Set(ZOO.engines[engine].cases.map((c) => c.native_type))];
|
|
3451
|
+
}
|
|
3452
|
+
const I64_MAX = 9223372036854775807n;
|
|
3453
|
+
const I64_MIN = -9223372036854775808n;
|
|
3454
|
+
/**
|
|
3455
|
+
* Render one SQL-side value of a typed column through the zoo.
|
|
3456
|
+
*
|
|
3457
|
+
* - The value IS a case's `value_sql` for this type → that case's expectation
|
|
3458
|
+
* (`source: "zoo"`).
|
|
3459
|
+
* - `NULL` → `null`.
|
|
3460
|
+
* - Otherwise the `plain` case's SHAPE decides: a number-shaped type parses the
|
|
3461
|
+
* literal, except that an integer outside `i64` becomes its exact digits as a
|
|
3462
|
+
* string where the zoo pins that (`precision_gt_18` expecting a string) — the
|
|
3463
|
+
* host reads an overflowing column as `u64` rendered to text; a string-shaped
|
|
3464
|
+
* type unquotes; a boolean-shaped type reads `true`/`false`/`1`/`0`; a JSON
|
|
3465
|
+
* shape parses (`source: "typed"`).
|
|
3466
|
+
*/
|
|
3467
|
+
function renderValue(engine, nativeType, valueSql, plane, column) {
|
|
3468
|
+
const cases = casesFor(engine, nativeType);
|
|
3469
|
+
const raw = valueSql.trim();
|
|
3470
|
+
const exact = cases.find((c) => c.value_sql.trim() === raw);
|
|
3471
|
+
if (exact) return {
|
|
3472
|
+
value: expectation(exact, plane, column),
|
|
3473
|
+
source: "zoo"
|
|
3474
|
+
};
|
|
3475
|
+
if (/^NULL$/i.test(raw)) return {
|
|
3476
|
+
value: null,
|
|
3477
|
+
source: "typed"
|
|
3478
|
+
};
|
|
3479
|
+
const shape = expectation(cases.find((c) => c.class === "plain") ?? cases[0], plane, column);
|
|
3480
|
+
if (shape instanceof ZooRefusal) return {
|
|
3481
|
+
value: shape,
|
|
3482
|
+
source: "zoo"
|
|
3483
|
+
};
|
|
3484
|
+
return {
|
|
3485
|
+
value: shaped(raw, shape, cases, plane, column),
|
|
3486
|
+
source: "typed"
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
function shaped(raw, shape, cases, plane, column) {
|
|
3490
|
+
const unquoted = raw.startsWith("'") && raw.endsWith("'") ? raw.slice(1, -1).replace(/''/g, "'") : raw;
|
|
3491
|
+
switch (typeof shape) {
|
|
3492
|
+
case "number": {
|
|
3493
|
+
if (/^-?\d+$/.test(raw)) {
|
|
3494
|
+
const big = BigInt(raw);
|
|
3495
|
+
if (big > I64_MAX || big < I64_MIN) {
|
|
3496
|
+
const wide = cases.find((c) => c.class === "precision_gt_18");
|
|
3497
|
+
if (wide && typeof expectation(wide, plane, column) === "string") return raw;
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
const n = Number(unquoted);
|
|
3501
|
+
return Number.isNaN(n) ? unquoted : n;
|
|
3502
|
+
}
|
|
3503
|
+
case "boolean": return /^(true|1)$/i.test(unquoted);
|
|
3504
|
+
case "string": return unquoted;
|
|
3505
|
+
case "object":
|
|
3506
|
+
if (shape === null) return null;
|
|
3507
|
+
try {
|
|
3508
|
+
return JSON.parse(unquoted);
|
|
3509
|
+
} catch {
|
|
3510
|
+
return unquoted;
|
|
3511
|
+
}
|
|
3512
|
+
default: return unquoted;
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
//#endregion
|
|
3517
|
+
//#region src/testing/stores.ts
|
|
3518
|
+
const RANK = {
|
|
3519
|
+
zoo: 0,
|
|
3520
|
+
typed: 1,
|
|
3521
|
+
author: 2
|
|
3522
|
+
};
|
|
3523
|
+
const weakest = (a, b) => a === null || RANK[b] > RANK[a] ? b : a;
|
|
3524
|
+
/**
|
|
3525
|
+
* One SQL-evaluating store: a warehouse database, the OLTP schema or the
|
|
3526
|
+
* Airhouse schema. `engine` picks which zoo cases render typed columns;
|
|
3527
|
+
* `plane` picks which expectation a case yields.
|
|
3528
|
+
*/
|
|
3529
|
+
var TableStore = class {
|
|
3530
|
+
constructor(engine, plane, schema = null) {
|
|
3531
|
+
this.engine = engine;
|
|
3532
|
+
this.plane = plane;
|
|
3533
|
+
this.schema = schema;
|
|
3534
|
+
this.tables = /* @__PURE__ */ new Map();
|
|
3535
|
+
}
|
|
3536
|
+
/** The zoo table for this engine: column `cNNN` is case NNN's expectation. */
|
|
3537
|
+
zoo() {
|
|
3538
|
+
const cases = ZOO.engines[this.engine].cases;
|
|
3539
|
+
const columns = {};
|
|
3540
|
+
const values = {};
|
|
3541
|
+
cases.forEach((c, i) => {
|
|
3542
|
+
const column = zooColumn(i);
|
|
3543
|
+
columns[column] = c.native_type;
|
|
3544
|
+
values[column] = expectation(c, this.plane, column);
|
|
3545
|
+
});
|
|
3546
|
+
this.tables.set(zooTableName(), {
|
|
3547
|
+
name: zooTableName(),
|
|
3548
|
+
columns,
|
|
3549
|
+
rows: [{
|
|
3550
|
+
values,
|
|
3551
|
+
source: "zoo"
|
|
3552
|
+
}]
|
|
3553
|
+
});
|
|
3554
|
+
return this;
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3557
|
+
* Declare a typed table: `{ id: "UInt64", amount: "Decimal(18, 4)" }`. A
|
|
3558
|
+
* type the zoo has no case for is an error here, not a guess at read time.
|
|
3559
|
+
*/
|
|
3560
|
+
table(name, columns) {
|
|
3561
|
+
const key = this.key(name);
|
|
3562
|
+
for (const [column, type] of Object.entries(columns)) if (!ZOO.engines[this.engine].cases.some((c) => c.native_type === type)) throw contextError(`the zoo has no ${this.engine} case for type "${type}" (column "${column}" of "${name}"); it pins: ${knownTypes(this.engine).join(", ")}`);
|
|
3563
|
+
if (!this.tables.has(key)) this.tables.set(key, {
|
|
3564
|
+
name: key,
|
|
3565
|
+
columns,
|
|
3566
|
+
rows: []
|
|
3567
|
+
});
|
|
3568
|
+
return new TypedTable(this, key);
|
|
3569
|
+
}
|
|
3570
|
+
/** Seed rows as JS values, unrendered — every read of them is `source: "author"`. */
|
|
3571
|
+
raw(name, rows) {
|
|
3572
|
+
const key = this.key(name);
|
|
3573
|
+
const table = this.tables.get(key) ?? {
|
|
3574
|
+
name: key,
|
|
3575
|
+
columns: null,
|
|
3576
|
+
rows: []
|
|
3577
|
+
};
|
|
3578
|
+
for (const row of rows) table.rows.push({
|
|
3579
|
+
values: { ...row },
|
|
3580
|
+
source: "author"
|
|
3581
|
+
});
|
|
3582
|
+
this.tables.set(key, table);
|
|
3583
|
+
return this;
|
|
3584
|
+
}
|
|
3585
|
+
/** Every row of `name` as a function would read it; a `$error` cell is its refusal text. */
|
|
3586
|
+
rows(name) {
|
|
3587
|
+
return this.lookup(name)?.rows.map((r) => materialise(r.values)) ?? [];
|
|
3588
|
+
}
|
|
3589
|
+
/** The names of every table declared, seeded or created. */
|
|
3590
|
+
tableNames() {
|
|
3591
|
+
return [...this.tables.keys()];
|
|
3592
|
+
}
|
|
3593
|
+
/** Whether `name` exists. */
|
|
3594
|
+
has(name) {
|
|
3595
|
+
return this.lookup(name) !== void 0;
|
|
3596
|
+
}
|
|
3597
|
+
/** Evaluate `sql` as a read: rows, or a throw. */
|
|
3598
|
+
query(sql, params = []) {
|
|
3599
|
+
const statement = parse(sql);
|
|
3600
|
+
if (statement.kind !== "select") throw contextError(`expected a SELECT, got ${statement.kind.toUpperCase()}: ${sql.slice(0, 80)}`);
|
|
3601
|
+
return this.select(statement, params);
|
|
3602
|
+
}
|
|
3603
|
+
/** Evaluate `sql` as a statement: rows affected, or a throw. */
|
|
3604
|
+
exec(sql, params = []) {
|
|
3605
|
+
const statement = parse(sql);
|
|
3606
|
+
switch (statement.kind) {
|
|
3607
|
+
case "create": return this.create(statement);
|
|
3608
|
+
case "insert": return this.insert(statement, params);
|
|
3609
|
+
case "delete": return this.remove(statement, params);
|
|
3610
|
+
case "update": return this.update(statement, params);
|
|
3611
|
+
case "select": return this.select(statement, params).rows.length;
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
/** Append JS rows to `name` (`ctx.warehouse.insert`, `ctx.airhouse.append`): unrendered. */
|
|
3615
|
+
append(name, rows) {
|
|
3616
|
+
this.raw(name, rows);
|
|
3617
|
+
return rows.length;
|
|
3618
|
+
}
|
|
3619
|
+
/** Snapshot every table, for `ctx.tx` rollback. */
|
|
3620
|
+
snapshot() {
|
|
3621
|
+
const copy = new Map([...this.tables].map(([k, t]) => [k, {
|
|
3622
|
+
...t,
|
|
3623
|
+
rows: t.rows.map((r) => ({
|
|
3624
|
+
...r,
|
|
3625
|
+
values: { ...r.values }
|
|
3626
|
+
}))
|
|
3627
|
+
}]));
|
|
3628
|
+
return () => {
|
|
3629
|
+
this.tables.clear();
|
|
3630
|
+
for (const [k, t] of copy) this.tables.set(k, t);
|
|
3631
|
+
};
|
|
3632
|
+
}
|
|
3633
|
+
key(name) {
|
|
3634
|
+
return this.schema && name.startsWith(`${this.schema}.`) ? name.slice(this.schema.length + 1) : name;
|
|
3635
|
+
}
|
|
3636
|
+
lookup(name) {
|
|
3637
|
+
return this.tables.get(this.key(name)) ?? this.tables.get(name);
|
|
3638
|
+
}
|
|
3639
|
+
require(name, statement) {
|
|
3640
|
+
const table = this.lookup(name);
|
|
3641
|
+
if (!table) throw contextError(`${statement.kind.toUpperCase()} names a table the test context does not have: "${name}". Declare it with t.state.<store>.table(name, columns), seed it with .raw(name, rows), or let the function CREATE it first. Known: ${this.tableNames().join(", ") || "none"}`);
|
|
3642
|
+
return table;
|
|
3643
|
+
}
|
|
3644
|
+
create(statement) {
|
|
3645
|
+
const key = this.key(statement.table);
|
|
3646
|
+
if (this.tables.has(key)) {
|
|
3647
|
+
if (statement.ifNotExists) return 0;
|
|
3648
|
+
throw contextError(`CREATE TABLE "${statement.table}": the table already exists`);
|
|
3649
|
+
}
|
|
3650
|
+
const columns = {};
|
|
3651
|
+
for (const c of statement.columns) columns[c.name] = c.type;
|
|
3652
|
+
this.tables.set(key, {
|
|
3653
|
+
name: key,
|
|
3654
|
+
columns,
|
|
3655
|
+
rows: []
|
|
3656
|
+
});
|
|
3657
|
+
return 0;
|
|
3658
|
+
}
|
|
3659
|
+
insert(statement, params) {
|
|
3660
|
+
const table = this.require(statement.table, statement);
|
|
3661
|
+
const names = statement.columns ?? (table.columns ? Object.keys(table.columns) : null);
|
|
3662
|
+
for (const tuple of statement.tuples) {
|
|
3663
|
+
if (!names || names.length !== tuple.length) throw contextError(`INSERT INTO "${statement.table}" gives ${tuple.length} value(s) for ${names?.length ?? "an unknown number of"} column(s)`);
|
|
3664
|
+
const values = {};
|
|
3665
|
+
let source = null;
|
|
3666
|
+
names.forEach((column, i) => {
|
|
3667
|
+
const type = table.columns?.[column];
|
|
3668
|
+
if (type && !/^\$\d+$/.test(tuple[i].trim())) {
|
|
3669
|
+
const rendered = renderValue(this.engine, type, tuple[i], this.plane, column);
|
|
3670
|
+
values[column] = rendered.value;
|
|
3671
|
+
source = weakest(source, rendered.source);
|
|
3672
|
+
} else {
|
|
3673
|
+
values[column] = literal(tuple[i], params);
|
|
3674
|
+
source = weakest(source, "author");
|
|
3675
|
+
}
|
|
3676
|
+
});
|
|
3677
|
+
table.rows.push({
|
|
3678
|
+
values,
|
|
3679
|
+
source: source ?? "author"
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
return statement.tuples.length;
|
|
3683
|
+
}
|
|
3684
|
+
matching(table, statement, params) {
|
|
3685
|
+
return table.rows.filter((row) => statement.where.every((p) => {
|
|
3686
|
+
const cell = row.values[p.column];
|
|
3687
|
+
return p.values.some((v) => looselyEqual(cell, literal(v, params)));
|
|
3688
|
+
}));
|
|
3689
|
+
}
|
|
3690
|
+
select(statement, params) {
|
|
3691
|
+
if (statement.table === null) {
|
|
3692
|
+
const row = {};
|
|
3693
|
+
for (const item of statement.items) if (item.kind === "literal") row[item.alias] = literal(item.raw, params);
|
|
3694
|
+
else throw contextError(`SELECT without FROM can only project literals: ${JSON.stringify(item)}`);
|
|
3695
|
+
return {
|
|
3696
|
+
rows: [row],
|
|
3697
|
+
source: "author"
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
const table = this.require(statement.table, statement);
|
|
3701
|
+
let matched = this.matching(table, statement, params);
|
|
3702
|
+
if (statement.items.some((i) => i.kind === "count")) return {
|
|
3703
|
+
rows: [{ [statement.items.find((i) => i.kind === "count")?.alias ?? "count(*)"]: matched.length }],
|
|
3704
|
+
source: matched[0]?.source ?? null
|
|
3705
|
+
};
|
|
3706
|
+
if (statement.orderBy) {
|
|
3707
|
+
const { column, desc } = statement.orderBy;
|
|
3708
|
+
matched = [...matched].sort((a, b) => compare(a.values[column], b.values[column]) * (desc ? -1 : 1));
|
|
3709
|
+
}
|
|
3710
|
+
if (statement.limit !== null) matched = matched.slice(0, statement.limit);
|
|
3711
|
+
let source = null;
|
|
3712
|
+
return {
|
|
3713
|
+
rows: matched.map((stored) => {
|
|
3714
|
+
source = weakest(source, stored.source);
|
|
3715
|
+
const out = {};
|
|
3716
|
+
for (const item of statement.items) if (item.kind === "all") Object.assign(out, materialise(stored.values));
|
|
3717
|
+
else if (item.kind === "column") out[item.alias] = materialise({ v: stored.values[item.column] }).v;
|
|
3718
|
+
else if (item.kind === "literal") out[item.alias] = literal(item.raw, params);
|
|
3719
|
+
return out;
|
|
3720
|
+
}),
|
|
3721
|
+
source
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
remove(statement, params) {
|
|
3725
|
+
const table = this.require(statement.table, statement);
|
|
3726
|
+
const gone = new Set(this.matching(table, statement, params));
|
|
3727
|
+
table.rows = table.rows.filter((r) => !gone.has(r));
|
|
3728
|
+
return gone.size;
|
|
3729
|
+
}
|
|
3730
|
+
update(statement, params) {
|
|
3731
|
+
const table = this.require(statement.table, statement);
|
|
3732
|
+
const hit = this.matching(table, statement, params);
|
|
3733
|
+
for (const row of hit) for (const { column, raw } of statement.set) {
|
|
3734
|
+
row.values[column] = literal(raw, params);
|
|
3735
|
+
row.source = "author";
|
|
3736
|
+
}
|
|
3737
|
+
return hit.length;
|
|
3738
|
+
}
|
|
3739
|
+
};
|
|
3740
|
+
/** A declared typed table: insert SQL-side values, rendered through the zoo. */
|
|
3741
|
+
var TypedTable = class {
|
|
3742
|
+
constructor(store, name) {
|
|
3743
|
+
this.store = store;
|
|
3744
|
+
this.name = name;
|
|
3745
|
+
}
|
|
3746
|
+
/** Insert rows of SQL-side values (`{ id: "42", at: "'2024-03-10 12:34:56'" }`). */
|
|
3747
|
+
insert(rows) {
|
|
3748
|
+
for (const row of rows) {
|
|
3749
|
+
const columns = Object.keys(row);
|
|
3750
|
+
const values = columns.map((c) => row[c]);
|
|
3751
|
+
this.store.exec(`INSERT INTO ${this.name} (${columns.join(", ")}) VALUES (${values.join(", ")})`);
|
|
3752
|
+
}
|
|
3753
|
+
return this;
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
/** A stored row as a read returns it; a refusal cell throws the host's sentence. */
|
|
3757
|
+
function materialise(values) {
|
|
3758
|
+
const out = {};
|
|
3759
|
+
for (const [k, v] of Object.entries(values)) {
|
|
3760
|
+
if (v instanceof ZooRefusal) throw refusalOf(v);
|
|
3761
|
+
out[k] = v;
|
|
3762
|
+
}
|
|
3763
|
+
return out;
|
|
3764
|
+
}
|
|
3765
|
+
/** Marker so the context can wrap a zoo refusal as the OLTP surface's `HostError`. */
|
|
3766
|
+
var ZooRefusalError = class extends Error {
|
|
3767
|
+
constructor(refusal) {
|
|
3768
|
+
super(refusal.message());
|
|
3769
|
+
this.refusal = refusal;
|
|
3770
|
+
this.name = "ZooRefusalError";
|
|
3771
|
+
}
|
|
3772
|
+
};
|
|
3773
|
+
const refusalOf = (r) => new ZooRefusalError(r);
|
|
3774
|
+
function looselyEqual(a, b) {
|
|
3775
|
+
if (a === b) return true;
|
|
3776
|
+
if (a === null || b === null || a === void 0 || b === void 0) return false;
|
|
3777
|
+
return String(a) === String(b);
|
|
3778
|
+
}
|
|
3779
|
+
function compare(a, b) {
|
|
3780
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
3781
|
+
return String(a).localeCompare(String(b));
|
|
3782
|
+
}
|
|
3783
|
+
/** The `ctx.fetch` side: answers by URL, and what was received. */
|
|
3784
|
+
var FetchStore = class {
|
|
3785
|
+
constructor() {
|
|
3786
|
+
this.received = [];
|
|
3787
|
+
this.answers = [];
|
|
3788
|
+
}
|
|
3789
|
+
/** Answer every URL equal to `match` (or matching the RegExp) with `answer`. */
|
|
3790
|
+
on(match, answer) {
|
|
3791
|
+
this.answers.push({
|
|
3792
|
+
match,
|
|
3793
|
+
answer
|
|
3794
|
+
});
|
|
3795
|
+
return this;
|
|
3796
|
+
}
|
|
3797
|
+
answerFor(url) {
|
|
3798
|
+
return this.answers.find(({ match }) => typeof match === "string" ? match === url : match.test(url))?.answer;
|
|
3799
|
+
}
|
|
3800
|
+
};
|
|
3801
|
+
|
|
3802
|
+
//#endregion
|
|
3803
|
+
//#region src/testing/context.ts
|
|
3804
|
+
const encoder = new TextEncoder();
|
|
3805
|
+
const decoder = new TextDecoder();
|
|
3806
|
+
const nodeBtoa = globalThis.btoa;
|
|
3807
|
+
const nodeAtob = globalThis.atob;
|
|
3808
|
+
const nodeCryptoModule = node_crypto;
|
|
3809
|
+
const DEFAULT_USER = {
|
|
3810
|
+
id: "user-1",
|
|
3811
|
+
email: "author@example.test",
|
|
3812
|
+
orgId: "org-1",
|
|
3813
|
+
name: "Author",
|
|
3814
|
+
appRole: "member",
|
|
3815
|
+
orgRole: "member",
|
|
3816
|
+
teams: [],
|
|
3817
|
+
kind: "user",
|
|
3818
|
+
reach: {
|
|
3819
|
+
everywhere: true,
|
|
3820
|
+
via: "org-member",
|
|
3821
|
+
locations: []
|
|
3822
|
+
}
|
|
3823
|
+
};
|
|
3824
|
+
function createTestContext(manifest, options) {
|
|
3825
|
+
const gates = readGates(manifest, options.function);
|
|
3826
|
+
const manifestSlug = typeof manifest.slug === "string" ? manifest.slug : "";
|
|
3827
|
+
const databases = options.databases ?? {};
|
|
3828
|
+
const calls = [];
|
|
3829
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
3830
|
+
const warehouses = /* @__PURE__ */ new Map();
|
|
3831
|
+
let semanticAnswer = null;
|
|
3832
|
+
const state = {
|
|
3833
|
+
warehouse(database) {
|
|
3834
|
+
const db = databases[database];
|
|
3835
|
+
if (!db) throw contextError(`no database "${database}" in options.databases`);
|
|
3836
|
+
let store = warehouses.get(database);
|
|
3837
|
+
if (!store) {
|
|
3838
|
+
store = new TableStore(db.dialect, "warehouse");
|
|
3839
|
+
warehouses.set(database, store);
|
|
3840
|
+
}
|
|
3841
|
+
return store;
|
|
3842
|
+
},
|
|
3843
|
+
oltp: new TableStore("postgres", "oltp", gates.writerSchema),
|
|
3844
|
+
airhouse: new TableStore("duckdb", "warehouse", gates.writerSchema),
|
|
3845
|
+
storage: /* @__PURE__ */ new Map(),
|
|
3846
|
+
secrets: /* @__PURE__ */ new Map(),
|
|
3847
|
+
emails: [],
|
|
3848
|
+
fetch: new FetchStore(),
|
|
3849
|
+
org: {
|
|
3850
|
+
people: [],
|
|
3851
|
+
places: [],
|
|
3852
|
+
assignments: []
|
|
3853
|
+
},
|
|
3854
|
+
semantic: { on: (answer) => {
|
|
3855
|
+
semanticAnswer = answer;
|
|
3856
|
+
} },
|
|
3857
|
+
airway: { runs: [] }
|
|
3858
|
+
};
|
|
3859
|
+
/** Run one host op: the override or `impl`, recorded either way. */
|
|
3860
|
+
async function op(name, args, impl) {
|
|
3861
|
+
const call = {
|
|
3862
|
+
op: name,
|
|
3863
|
+
args,
|
|
3864
|
+
outcome: "ok"
|
|
3865
|
+
};
|
|
3866
|
+
calls.push(call);
|
|
3867
|
+
try {
|
|
3868
|
+
const result = await (overrides.get(name) ?? impl)(args);
|
|
3869
|
+
if (result && typeof result === "object" && "__source" in result) {
|
|
3870
|
+
const { __source } = result;
|
|
3871
|
+
if (__source) call.source = __source;
|
|
3872
|
+
const rest = Array.isArray(result) ? [...result] : Object.fromEntries(Object.entries(result).filter(([k]) => k !== "__source"));
|
|
3873
|
+
call.result = rest;
|
|
3874
|
+
return rest;
|
|
3875
|
+
}
|
|
3876
|
+
call.result = result;
|
|
3877
|
+
return result;
|
|
3878
|
+
} catch (err) {
|
|
3879
|
+
const thrown = err instanceof ZooRefusalError ? hostError(name.startsWith("tx.") ? SURFACES.tx : SURFACES.oltp, err.message) : err;
|
|
3880
|
+
call.outcome = isHostError(thrown) ? "refused" : "threw";
|
|
3881
|
+
call.message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
3882
|
+
throw thrown;
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
/** `check_write_destination` then `destination_write_policy` (`host.rs`, `destinations.rs`). */
|
|
3886
|
+
function checkWriteDestination(surface, database, transaction) {
|
|
3887
|
+
if (!gates.destinations.includes(database)) throw refuse(surface, REFUSALS.destinationNotDeclared, { database });
|
|
3888
|
+
const db = databases[database];
|
|
3889
|
+
if (!db) throw refuse(surface, REFUSALS.databaseNotConfigured, { database });
|
|
3890
|
+
switch (db.kind) {
|
|
3891
|
+
case "airhouse":
|
|
3892
|
+
case "airhouse_managed": return db;
|
|
3893
|
+
case "postgres_managed": throw refuse(surface, REFUSALS.managedOltpWrite, { database });
|
|
3894
|
+
default: {
|
|
3895
|
+
if (gates.customerWarehouseWrites[database]) return db;
|
|
3896
|
+
const words = [REFUSALS.customerWarehouseWrite.refusal];
|
|
3897
|
+
if (transaction) words.push(REFUSALS.customerWarehouseTransaction.refusal);
|
|
3898
|
+
throw hostError(surface, words.join("").replace(/\{database\}/g, database));
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* The host's `WriterCapability` has three states, not two: enabled, disabled,
|
|
3904
|
+
* and enabled-but-the-slug-cannot-back-a-schema. Gating on the boolean alone
|
|
3905
|
+
* let an app whose slug derives no schema pass here and fail closed in
|
|
3906
|
+
* production — the exact shape this module exists to catch.
|
|
3907
|
+
*/
|
|
3908
|
+
function requireWriterSchema(surface, store) {
|
|
3909
|
+
if (gates.writerSchema === null) throw refuse(surface, REFUSALS.slugCannotBackASchema, {
|
|
3910
|
+
slug: manifestSlug,
|
|
3911
|
+
store,
|
|
3912
|
+
max: 56
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
3915
|
+
function requireOltp(surface) {
|
|
3916
|
+
if (!gates.oltp) throw refuse(surface, REFUSALS.oltp);
|
|
3917
|
+
requireWriterSchema(surface, "OLTP");
|
|
3918
|
+
return state.oltp;
|
|
3919
|
+
}
|
|
3920
|
+
function requireAirhouse() {
|
|
3921
|
+
if (!gates.airhouse) throw refuse(SURFACES.airhouse, REFUSALS.airhouse);
|
|
3922
|
+
requireWriterSchema(SURFACES.airhouse, "Airhouse");
|
|
3923
|
+
return state.airhouse;
|
|
3924
|
+
}
|
|
3925
|
+
function requireStorage(opName) {
|
|
3926
|
+
const needsWrite = [
|
|
3927
|
+
"getUploadUrl",
|
|
3928
|
+
"put",
|
|
3929
|
+
"delete",
|
|
3930
|
+
"copy"
|
|
3931
|
+
].includes(opName);
|
|
3932
|
+
const needsRead = [
|
|
3933
|
+
"getDownloadUrl",
|
|
3934
|
+
"get",
|
|
3935
|
+
"head",
|
|
3936
|
+
"list",
|
|
3937
|
+
"copy"
|
|
3938
|
+
].includes(opName);
|
|
3939
|
+
if (needsWrite && !gates.storageWrite) throw refuse(SURFACES.storage, REFUSALS.storageWrite);
|
|
3940
|
+
if (needsRead && !gates.storageRead) throw refuse(SURFACES.storage, REFUSALS.storageRead);
|
|
3941
|
+
}
|
|
3942
|
+
function defaultDatabase() {
|
|
3943
|
+
const name = options.defaultDatabase ?? Object.keys(databases)[0];
|
|
3944
|
+
if (!name) throw refuse(SURFACES.query, REFUSALS.noDefaultDatabase);
|
|
3945
|
+
return name;
|
|
3946
|
+
}
|
|
3947
|
+
/** `oltp_statement`: `params` absent or an array, else refused. */
|
|
3948
|
+
function oltpParams(surface, params) {
|
|
3949
|
+
if (params === void 0 || params === null) return [];
|
|
3950
|
+
if (Array.isArray(params)) return params;
|
|
3951
|
+
throw refuse(surface, REFUSALS.oltpParamsNotArray);
|
|
3952
|
+
}
|
|
3953
|
+
const read = (store, sql, params = []) => {
|
|
3954
|
+
const { rows, source } = store.query(sql, params);
|
|
3955
|
+
return {
|
|
3956
|
+
rows,
|
|
3957
|
+
truncated: false,
|
|
3958
|
+
__source: source
|
|
3959
|
+
};
|
|
3960
|
+
};
|
|
3961
|
+
let open = 0;
|
|
3962
|
+
let nextTxId = 0;
|
|
3963
|
+
async function runTx(signature, label, beginOp, beginPayload, fn) {
|
|
3964
|
+
if (typeof fn !== "function") throw new TypeError(REFUSALS.transactionFnNotFunction.refusal.replace("{signature}", signature));
|
|
3965
|
+
let store = null;
|
|
3966
|
+
let incremented = false;
|
|
3967
|
+
const { id } = await op(beginOp, beginPayload, () => {
|
|
3968
|
+
if (beginOp === "tx.begin") {
|
|
3969
|
+
const database = String(beginPayload.database);
|
|
3970
|
+
const db = checkWriteDestination(SURFACES.tx, database, true);
|
|
3971
|
+
if (!DIALECTS[db.dialect].opensTransactions) {
|
|
3972
|
+
const cause = REFUSALS.transactionUnsupported.refusal.replace("{backend}", DIALECTS[db.dialect].name);
|
|
3973
|
+
throw refuse(SURFACES.tx, REFUSALS.transactionOpen, {
|
|
3974
|
+
database,
|
|
3975
|
+
e: cause
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
3978
|
+
store = state.warehouse(database);
|
|
3979
|
+
} else store = requireOltp(SURFACES.tx);
|
|
3980
|
+
if (open >= 4) throw refuse(SURFACES.tx, REFUSALS.transactionsOpen, { MAX_OPEN: 4 });
|
|
3981
|
+
open++;
|
|
3982
|
+
incremented = true;
|
|
3983
|
+
return { id: ++nextTxId };
|
|
3984
|
+
});
|
|
3985
|
+
const table = store ?? (beginOp === "tx.begin_oltp" ? state.oltp : databases[String(beginPayload.database)] ? state.warehouse(String(beginPayload.database)) : new TableStore("postgres", "warehouse"));
|
|
3986
|
+
const restore = table.snapshot();
|
|
3987
|
+
let closed = false;
|
|
3988
|
+
const live = (what) => {
|
|
3989
|
+
if (closed) throw hostError(label, REFUSALS.transactionFinished.refusal.replace("{label}", label).replace("{what}", what));
|
|
3990
|
+
};
|
|
3991
|
+
const handle = {
|
|
3992
|
+
query: async (sql, params) => {
|
|
3993
|
+
live("query");
|
|
3994
|
+
return (await op("tx.query", {
|
|
3995
|
+
id,
|
|
3996
|
+
sql: String(sql),
|
|
3997
|
+
params: params ?? []
|
|
3998
|
+
}, (a) => read(table, String(a.sql), oltpParams(SURFACES.tx, a.params)))).rows;
|
|
3999
|
+
},
|
|
4000
|
+
exec: async (sql, params) => {
|
|
4001
|
+
live("exec");
|
|
4002
|
+
return (await op("tx.exec", {
|
|
4003
|
+
id,
|
|
4004
|
+
sql: String(sql),
|
|
4005
|
+
params: params ?? []
|
|
4006
|
+
}, (a) => ({ rowCount: table.exec(String(a.sql), oltpParams(SURFACES.tx, a.params)) }))).rowCount;
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
let result;
|
|
4010
|
+
try {
|
|
4011
|
+
result = await fn(handle);
|
|
4012
|
+
} catch (err) {
|
|
4013
|
+
closed = true;
|
|
4014
|
+
if (incremented) open--;
|
|
4015
|
+
try {
|
|
4016
|
+
await op("tx.rollback", { id }, () => {
|
|
4017
|
+
restore();
|
|
4018
|
+
return {};
|
|
4019
|
+
});
|
|
4020
|
+
} catch {}
|
|
4021
|
+
throw err;
|
|
4022
|
+
}
|
|
4023
|
+
closed = true;
|
|
4024
|
+
if (incremented) open--;
|
|
4025
|
+
await op("tx.commit", { id }, () => ({}));
|
|
4026
|
+
return result;
|
|
4027
|
+
}
|
|
4028
|
+
const ctx = {
|
|
4029
|
+
user: {
|
|
4030
|
+
...DEFAULT_USER,
|
|
4031
|
+
...options.user
|
|
4032
|
+
},
|
|
4033
|
+
env: { ...options.env ?? {} },
|
|
4034
|
+
log: () => {},
|
|
4035
|
+
crypto: nodeCrypto(),
|
|
4036
|
+
query: async (sql) => await op("query", { sql: String(sql) }, (a) => read(state.warehouse(defaultDatabase()), String(a.sql))),
|
|
4037
|
+
queryStream: async function* (sql, opts) {
|
|
4038
|
+
const batchSize = opts?.batchSize || 1e3;
|
|
4039
|
+
const rows = await op("query_stream", { sql: String(sql) }, (a) => {
|
|
4040
|
+
const { rows, __source } = read(state.warehouse(defaultDatabase()), String(a.sql));
|
|
4041
|
+
return Object.assign(rows, { __source });
|
|
4042
|
+
});
|
|
4043
|
+
for (let i = 0; i < rows.length; i += batchSize) yield rows.slice(i, i + batchSize);
|
|
4044
|
+
},
|
|
4045
|
+
fetch: (url, init) => op("fetch", {
|
|
4046
|
+
url,
|
|
4047
|
+
init: init ?? {}
|
|
4048
|
+
}, (a) => fetchOp(String(a.url), a.init ?? {})),
|
|
4049
|
+
warehouse: {
|
|
4050
|
+
insert: (database, table, rows) => op("warehouse.insert", {
|
|
4051
|
+
database,
|
|
4052
|
+
table,
|
|
4053
|
+
rows
|
|
4054
|
+
}, (a) => {
|
|
4055
|
+
checkWriteDestination(SURFACES.warehouse, String(a.database), false);
|
|
4056
|
+
state.warehouse(String(a.database)).append(String(a.table), a.rows);
|
|
4057
|
+
return { ok: true };
|
|
4058
|
+
}),
|
|
4059
|
+
exec: (database, sql) => op("warehouse.exec", {
|
|
4060
|
+
database,
|
|
4061
|
+
sql
|
|
4062
|
+
}, (a) => {
|
|
4063
|
+
checkWriteDestination(SURFACES.warehouse, String(a.database), false);
|
|
4064
|
+
state.warehouse(String(a.database)).exec(String(a.sql));
|
|
4065
|
+
return { ok: true };
|
|
4066
|
+
}),
|
|
4067
|
+
upsert: (database, table, rows, conflictColumns) => op("warehouse.upsert", {
|
|
4068
|
+
database,
|
|
4069
|
+
table,
|
|
4070
|
+
rows,
|
|
4071
|
+
conflictColumns
|
|
4072
|
+
}, (a) => {
|
|
4073
|
+
const database = String(a.database);
|
|
4074
|
+
const db = checkWriteDestination(SURFACES.warehouse, database, false);
|
|
4075
|
+
if (!DIALECTS[db.dialect].parsesOnConflict) throw refuse(SURFACES.warehouse, REFUSALS.upsertUnsupported, { dialect: DIALECTS[db.dialect].name });
|
|
4076
|
+
if (db.kind === "airhouse" || db.kind === "airhouse_managed") throw contextError(`@oxy-hq/sdk/testing: ctx.warehouse.upsert on "${database}" would be refused by the ENGINE, not the host. Airhouse is DuckLake, and a DuckLake table cannot carry the primary key or unique constraint that INSERT … ON CONFLICT needs, so the statement the host lets through fails at runtime. Use ctx.airhouse.append and keep one row per id on read. (These are the test context's words, not a host refusal — assert that the call was refused, not on this text, which may be reworded.)`);
|
|
4077
|
+
state.warehouse(database).append(String(a.table), a.rows);
|
|
4078
|
+
return { ok: true };
|
|
4079
|
+
}),
|
|
4080
|
+
query: (database, sql) => op("warehouse.query", {
|
|
4081
|
+
database,
|
|
4082
|
+
sql
|
|
4083
|
+
}, (a) => {
|
|
4084
|
+
const database = String(a.database);
|
|
4085
|
+
if (!databases[database]) throw refuse(SURFACES.warehouse, REFUSALS.databaseNotConfigured, { database });
|
|
4086
|
+
return read(state.warehouse(database), String(a.sql));
|
|
4087
|
+
})
|
|
4088
|
+
},
|
|
4089
|
+
tx: (database, fn) => runTx("ctx.tx(database, fn)", SURFACES.tx, "tx.begin", { database: String(database) }, fn),
|
|
4090
|
+
oltp: {
|
|
4091
|
+
tx: (fn) => runTx("ctx.oltp.tx(fn)", "ctx.oltp.tx", "tx.begin_oltp", {}, fn),
|
|
4092
|
+
query: async (sql, params) => {
|
|
4093
|
+
return (await op("oltp.query", {
|
|
4094
|
+
sql: String(sql),
|
|
4095
|
+
params: params ?? []
|
|
4096
|
+
}, (a) => read(requireOltp(SURFACES.oltp), String(a.sql), oltpParams(SURFACES.oltp, a.params)))).rows;
|
|
4097
|
+
},
|
|
4098
|
+
exec: async (sql, params) => {
|
|
4099
|
+
return (await op("oltp.exec", {
|
|
4100
|
+
sql: String(sql),
|
|
4101
|
+
params: params ?? []
|
|
4102
|
+
}, (a) => ({ rowCount: requireOltp(SURFACES.oltp).exec(String(a.sql), oltpParams(SURFACES.oltp, a.params)) }))).rowCount;
|
|
4103
|
+
}
|
|
4104
|
+
},
|
|
4105
|
+
airhouse: {
|
|
4106
|
+
schema: gates.airhouse ? gates.writerSchema : null,
|
|
4107
|
+
query: (sql) => op("airhouse.query", { sql: String(sql) }, (a) => read(requireAirhouse(), String(a.sql))),
|
|
4108
|
+
exec: async (sql) => {
|
|
4109
|
+
await op("airhouse.exec", { sql: String(sql) }, (a) => {
|
|
4110
|
+
requireAirhouse().exec(String(a.sql));
|
|
4111
|
+
return { ok: true };
|
|
4112
|
+
});
|
|
4113
|
+
},
|
|
4114
|
+
append: async (table, rows) => {
|
|
4115
|
+
return (await op("airhouse.append", {
|
|
4116
|
+
table: String(table),
|
|
4117
|
+
rows
|
|
4118
|
+
}, (a) => ({ rowCount: requireAirhouse().append(String(a.table), a.rows) }))).rowCount;
|
|
4119
|
+
}
|
|
4120
|
+
},
|
|
4121
|
+
secrets: { set: async (key, value) => {
|
|
4122
|
+
await op("secrets.set", {
|
|
4123
|
+
key: String(key),
|
|
4124
|
+
value: String(value)
|
|
4125
|
+
}, (a) => {
|
|
4126
|
+
if (!gates.secretsWrite) throw refuse(SURFACES.secretsSet, REFUSALS.secretsWrite);
|
|
4127
|
+
state.secrets.set(String(a.key), String(a.value));
|
|
4128
|
+
return null;
|
|
4129
|
+
});
|
|
4130
|
+
} },
|
|
4131
|
+
email: { send: (input) => op("email.send", { ...input }, (a) => {
|
|
4132
|
+
if (!gates.emailSend) throw refuse(SURFACES.emailSend, REFUSALS.emailSend);
|
|
4133
|
+
state.emails.push(a);
|
|
4134
|
+
return { messageId: `message-${state.emails.length}` };
|
|
4135
|
+
}) },
|
|
4136
|
+
org: {
|
|
4137
|
+
people: () => op("org.people", {}, () => {
|
|
4138
|
+
if (!gates.orgRead) throw refuse(SURFACES.orgPeople, REFUSALS.orgPeople);
|
|
4139
|
+
return {
|
|
4140
|
+
people: [...state.org.people],
|
|
4141
|
+
total: state.org.people.length
|
|
4142
|
+
};
|
|
4143
|
+
}),
|
|
4144
|
+
places: () => op("org.places", {}, () => {
|
|
4145
|
+
if (!gates.orgRead) throw refuse(SURFACES.orgPlaces, REFUSALS.orgMember, { member: "places" });
|
|
4146
|
+
return {
|
|
4147
|
+
total: state.org.places.length,
|
|
4148
|
+
places: [...state.org.places]
|
|
4149
|
+
};
|
|
4150
|
+
}),
|
|
4151
|
+
assignments: () => op("org.assignments", {}, () => {
|
|
4152
|
+
if (!gates.orgRead) throw refuse(SURFACES.orgAssignments, REFUSALS.orgMember, { member: "assignments" });
|
|
4153
|
+
return {
|
|
4154
|
+
total: state.org.assignments.length,
|
|
4155
|
+
assignments: [...state.org.assignments]
|
|
4156
|
+
};
|
|
4157
|
+
})
|
|
4158
|
+
},
|
|
4159
|
+
storage: storageApi(),
|
|
4160
|
+
semantic: { query: (spec) => op("semantic.query", { spec }, (a) => {
|
|
4161
|
+
if (!semanticAnswer) throw contextError("ctx.semantic.query has no answer registered: the zoo pins no semantic payload, so supply one with t.state.semantic.on((spec) => result)");
|
|
4162
|
+
return semanticAnswer(a.spec);
|
|
4163
|
+
}) },
|
|
4164
|
+
airway: { run: (pipelineRef, variables) => op("airway.run", {
|
|
4165
|
+
pipelineRef: String(pipelineRef),
|
|
4166
|
+
variables: variables ?? null
|
|
4167
|
+
}, (a) => {
|
|
4168
|
+
state.airway.runs.push({
|
|
4169
|
+
pipelineRef: String(a.pipelineRef),
|
|
4170
|
+
variables: a.variables
|
|
4171
|
+
});
|
|
4172
|
+
return { runId: `run-${state.airway.runs.length}` };
|
|
4173
|
+
}) }
|
|
4174
|
+
};
|
|
4175
|
+
function fetchOp(url, init) {
|
|
4176
|
+
let parsed;
|
|
4177
|
+
try {
|
|
4178
|
+
parsed = new URL(url);
|
|
4179
|
+
} catch (e) {
|
|
4180
|
+
throw refuse(SURFACES.fetch, REFUSALS.fetchInvalidUrl, { e: e instanceof Error ? e.message : String(e) });
|
|
4181
|
+
}
|
|
4182
|
+
if (!isSafeOutbound(parsed)) throw refuse(SURFACES.fetch, REFUSALS.fetchBlocked, { url });
|
|
4183
|
+
for (const [k, v] of Object.entries(headersOf(init.headers))) {
|
|
4184
|
+
if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(k)) throw refuse(SURFACES.fetch, REFUSALS.fetchInvalidHeaderName, { k });
|
|
4185
|
+
if (/[\r\n\0]/.test(v)) throw refuse(SURFACES.fetch, REFUSALS.fetchInvalidHeaderValue, { k });
|
|
4186
|
+
}
|
|
4187
|
+
state.fetch.received.push({
|
|
4188
|
+
url,
|
|
4189
|
+
init
|
|
4190
|
+
});
|
|
4191
|
+
const answer = state.fetch.answerFor(url);
|
|
4192
|
+
if (answer === void 0) throw contextError(`ctx.fetch has no answer for ${url}: register one with t.state.fetch.on(url, { status, body }) — nothing here reaches the network`);
|
|
4193
|
+
const reply = typeof answer === "function" ? answer(url, init) : answer;
|
|
4194
|
+
const fixture = reply.body ?? "";
|
|
4195
|
+
const fixtureIsBase64 = reply.encoding === "base64";
|
|
4196
|
+
const max = gates.fetchMaxBytes ?? 10485760;
|
|
4197
|
+
const len = fixtureIsBase64 ? base64ByteLength(fixture) : encoder.encode(fixture).length;
|
|
4198
|
+
if (len > max) throw refuse(SURFACES.fetch, REFUSALS.fetchTooLarge, {
|
|
4199
|
+
len,
|
|
4200
|
+
max_bytes: max
|
|
4201
|
+
});
|
|
4202
|
+
if (init.encoding !== void 0 && init.encoding !== "utf8" && init.encoding !== "base64") throw contextError(`ctx.fetch: unknown encoding '${String(init.encoding)}' (expected 'utf8' or 'base64')`);
|
|
4203
|
+
const wantBase64 = init.encoding === "base64";
|
|
4204
|
+
const body = wantBase64 ? fixtureIsBase64 ? fixture : utf8ToBase64(fixture) : fixtureIsBase64 ? base64ToUtf8(fixture) : fixture;
|
|
4205
|
+
return {
|
|
4206
|
+
status: reply.status ?? 200,
|
|
4207
|
+
body,
|
|
4208
|
+
encoding: wantBase64 ? "base64" : "utf8"
|
|
4209
|
+
};
|
|
4210
|
+
}
|
|
4211
|
+
function storageApi() {
|
|
4212
|
+
const now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
4213
|
+
const put = (key, body, contentType) => {
|
|
4214
|
+
const object = {
|
|
4215
|
+
key,
|
|
4216
|
+
body,
|
|
4217
|
+
contentType,
|
|
4218
|
+
size: base64ByteLength(body),
|
|
4219
|
+
lastModified: now()
|
|
4220
|
+
};
|
|
4221
|
+
state.storage.set(key, object);
|
|
4222
|
+
return object;
|
|
4223
|
+
};
|
|
4224
|
+
const storageOp = (name, args, impl) => op(`storage.${name}`, args, (a) => {
|
|
4225
|
+
requireStorage(name);
|
|
4226
|
+
return impl(a);
|
|
4227
|
+
});
|
|
4228
|
+
return {
|
|
4229
|
+
getUploadUrl: (opts) => storageOp("getUploadUrl", { ...opts }, (a) => {
|
|
4230
|
+
const key = `${String(a.pathname ?? `uploads/${String(a.filename ?? "upload")}`).replace(/(\.[^.]*)?$/, `-${randomSuffix()}$1`)}`;
|
|
4231
|
+
return {
|
|
4232
|
+
url: `https://storage.example.test/put/${key}`,
|
|
4233
|
+
key,
|
|
4234
|
+
expiresAt: now()
|
|
4235
|
+
};
|
|
4236
|
+
}),
|
|
4237
|
+
getDownloadUrl: (key, opts) => storageOp("getDownloadUrl", {
|
|
4238
|
+
key: String(key),
|
|
4239
|
+
...opts
|
|
4240
|
+
}, (a) => ({
|
|
4241
|
+
url: `https://storage.example.test/get/${String(a.key)}`,
|
|
4242
|
+
expiresAt: now()
|
|
4243
|
+
})),
|
|
4244
|
+
put: (pathname, body, opts) => storageOp("put", {
|
|
4245
|
+
pathname: String(pathname),
|
|
4246
|
+
body: String(body),
|
|
4247
|
+
...opts
|
|
4248
|
+
}, (a) => {
|
|
4249
|
+
const key = a.addRandomSuffix ? String(a.pathname).replace(/(\.[^.]*)?$/, `-${randomSuffix()}$1`) : String(a.pathname);
|
|
4250
|
+
if (state.storage.has(key) && !a.allowOverwrite) throw hostError(SURFACES.storage, `put: "${key}" already exists (pass allowOverwrite: true)`);
|
|
4251
|
+
const base64 = a.encoding === "base64" ? String(a.body) : utf8ToBase64(String(a.body));
|
|
4252
|
+
const stored = put(key, base64, a.contentType ?? null);
|
|
4253
|
+
return {
|
|
4254
|
+
key,
|
|
4255
|
+
size: stored.size,
|
|
4256
|
+
contentType: stored.contentType ?? "application/octet-stream"
|
|
4257
|
+
};
|
|
4258
|
+
}),
|
|
4259
|
+
get: (key, opts) => storageOp("get", {
|
|
4260
|
+
key: String(key),
|
|
4261
|
+
...opts
|
|
4262
|
+
}, (a) => {
|
|
4263
|
+
const object = state.storage.get(String(a.key));
|
|
4264
|
+
if (!object) return null;
|
|
4265
|
+
const base64 = a.encoding === "base64";
|
|
4266
|
+
return {
|
|
4267
|
+
body: base64 ? object.body : base64ToUtf8(object.body),
|
|
4268
|
+
contentType: object.contentType,
|
|
4269
|
+
size: object.size,
|
|
4270
|
+
encoding: base64 ? "base64" : "utf8"
|
|
4271
|
+
};
|
|
4272
|
+
}),
|
|
4273
|
+
head: (key) => storageOp("head", { key: String(key) }, (a) => {
|
|
4274
|
+
const object = state.storage.get(String(a.key));
|
|
4275
|
+
return object ? {
|
|
4276
|
+
key: object.key,
|
|
4277
|
+
size: object.size,
|
|
4278
|
+
contentType: object.contentType,
|
|
4279
|
+
lastModified: object.lastModified
|
|
4280
|
+
} : null;
|
|
4281
|
+
}),
|
|
4282
|
+
list: (opts) => storageOp("list", { ...opts }, (a) => {
|
|
4283
|
+
const prefix = String(a.prefix ?? "");
|
|
4284
|
+
const limit = Number(a.limit ?? 1e3);
|
|
4285
|
+
const offset = a.cursor ? Number(a.cursor) : 0;
|
|
4286
|
+
const keys = [...state.storage.keys()].filter((k) => k.startsWith(prefix)).sort();
|
|
4287
|
+
const page = keys.slice(offset, offset + limit).map((k) => {
|
|
4288
|
+
const o = state.storage.get(k);
|
|
4289
|
+
return {
|
|
4290
|
+
key: o.key,
|
|
4291
|
+
size: o.size,
|
|
4292
|
+
contentType: o.contentType,
|
|
4293
|
+
lastModified: o.lastModified
|
|
4294
|
+
};
|
|
4295
|
+
});
|
|
4296
|
+
const hasMore = offset + limit < keys.length;
|
|
4297
|
+
return {
|
|
4298
|
+
objects: page,
|
|
4299
|
+
cursor: hasMore ? String(offset + limit) : null,
|
|
4300
|
+
hasMore
|
|
4301
|
+
};
|
|
4302
|
+
}),
|
|
4303
|
+
delete: (keyOrKeys) => storageOp("delete", Array.isArray(keyOrKeys) ? { keys: keyOrKeys.map(String) } : { key: String(keyOrKeys) }, (a) => {
|
|
4304
|
+
const keys = Array.isArray(a.keys) ? a.keys : [String(a.key)];
|
|
4305
|
+
for (const k of keys) state.storage.delete(k);
|
|
4306
|
+
return { deleted: keys.length };
|
|
4307
|
+
}),
|
|
4308
|
+
copy: (fromKey, toPathname, opts) => storageOp("copy", {
|
|
4309
|
+
fromKey: String(fromKey),
|
|
4310
|
+
toPathname: String(toPathname),
|
|
4311
|
+
...opts
|
|
4312
|
+
}, (a) => {
|
|
4313
|
+
const from = state.storage.get(String(a.fromKey));
|
|
4314
|
+
if (!from) throw hostError(SURFACES.storage, `copy: "${String(a.fromKey)}" does not exist`);
|
|
4315
|
+
const key = String(a.toPathname);
|
|
4316
|
+
if (state.storage.has(key) && !a.allowOverwrite) throw hostError(SURFACES.storage, `copy: "${key}" already exists (pass allowOverwrite: true)`);
|
|
4317
|
+
const stored = put(key, from.body, from.contentType);
|
|
4318
|
+
return {
|
|
4319
|
+
key,
|
|
4320
|
+
size: stored.size,
|
|
4321
|
+
contentType: stored.contentType ?? "application/octet-stream"
|
|
4322
|
+
};
|
|
4323
|
+
})
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
return {
|
|
4327
|
+
ctx,
|
|
4328
|
+
calls,
|
|
4329
|
+
ops: () => [...new Set(calls.map((c) => c.op))],
|
|
4330
|
+
callsTo: (name) => calls.filter((c) => c.op === name),
|
|
4331
|
+
override: (name, impl) => {
|
|
4332
|
+
overrides.set(name, impl);
|
|
4333
|
+
},
|
|
4334
|
+
run: runWithoutIsolateGlobals,
|
|
4335
|
+
state,
|
|
4336
|
+
gates
|
|
4337
|
+
};
|
|
4338
|
+
}
|
|
4339
|
+
function headersOf(headers) {
|
|
4340
|
+
if (!headers) return {};
|
|
4341
|
+
if (Array.isArray(headers)) return Object.fromEntries(headers);
|
|
4342
|
+
if (typeof Headers !== "undefined" && headers instanceof Headers) return Object.fromEntries(headers.entries());
|
|
4343
|
+
return headers;
|
|
4344
|
+
}
|
|
4345
|
+
function randomSuffix() {
|
|
4346
|
+
return Math.random().toString(36).slice(2, 8);
|
|
4347
|
+
}
|
|
4348
|
+
/**
|
|
4349
|
+
* Bytes a base64 string decodes to. `length * 3 / 4` overstates by the padding,
|
|
4350
|
+
* which made `size` wrong by up to two bytes and fired the `ctx.fetch` cap early.
|
|
4351
|
+
*/
|
|
4352
|
+
function base64ByteLength(base64) {
|
|
4353
|
+
const s = base64.trim();
|
|
4354
|
+
if (s === "") return 0;
|
|
4355
|
+
const padding = s.endsWith("==") ? 2 : s.endsWith("=") ? 1 : 0;
|
|
4356
|
+
return Math.floor(s.length * 3 / 4) - padding;
|
|
4357
|
+
}
|
|
4358
|
+
function utf8ToBase64(text) {
|
|
4359
|
+
const bytes = encoder.encode(text);
|
|
4360
|
+
let binary = "";
|
|
4361
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
4362
|
+
return nodeBtoa(binary);
|
|
4363
|
+
}
|
|
4364
|
+
function base64ToUtf8(base64) {
|
|
4365
|
+
const binary = nodeAtob(base64);
|
|
4366
|
+
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
4367
|
+
return decoder.decode(bytes);
|
|
4368
|
+
}
|
|
4369
|
+
/**
|
|
4370
|
+
* `ctx.crypto` over `node:crypto`: the same three synchronous members
|
|
4371
|
+
* `__buildCtx` binds, with the host's argument rules (`key` and `data`
|
|
4372
|
+
* required; `signature` and both sides of `timingSafeEqual` lenient). A digest
|
|
4373
|
+
* is ASCII, so the byte views need no `Buffer` — which `t.run` removes.
|
|
4374
|
+
*/
|
|
4375
|
+
function nodeCrypto() {
|
|
4376
|
+
const lib = () => nodeCryptoModule;
|
|
4377
|
+
const ascii = (s) => Uint8Array.from(s, (c) => c.charCodeAt(0));
|
|
4378
|
+
const digest = (algorithm, key, data, encoding) => lib().createHmac(algorithm, key).update(data).digest(encoding);
|
|
4379
|
+
const same = (a, b) => a !== "" && b !== "" && a.length === b.length && lib().timingSafeEqual(ascii(a), ascii(b));
|
|
4380
|
+
return {
|
|
4381
|
+
hmac: ({ algorithm, key, data, encoding }) => {
|
|
4382
|
+
if (key == null) throw new TypeError("ctx.crypto.hmac: `key` is required — is the secret set?");
|
|
4383
|
+
if (data == null) throw new TypeError("ctx.crypto.hmac: `data` is required");
|
|
4384
|
+
return digest(algorithm ?? "sha256", String(key), String(data), encoding ?? "hex");
|
|
4385
|
+
},
|
|
4386
|
+
verifyHmac: ({ algorithm, key, data, signature, encoding }) => {
|
|
4387
|
+
if (key == null) throw new TypeError("ctx.crypto.verifyHmac: `key` is required — is the secret set?");
|
|
4388
|
+
if (data == null) throw new TypeError("ctx.crypto.verifyHmac: `data` is required");
|
|
4389
|
+
const expected = digest(algorithm ?? "sha256", String(key), String(data), encoding ?? "hex");
|
|
4390
|
+
return same(String(signature ?? ""), expected);
|
|
4391
|
+
},
|
|
4392
|
+
timingSafeEqual: (a, b) => same(a == null ? "" : String(a), b == null ? "" : String(b))
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
//#endregion
|
|
4397
|
+
exports.ABSENT_GLOBALS = ABSENT_GLOBALS;
|
|
4398
|
+
exports.DIALECTS = DIALECTS;
|
|
4399
|
+
exports.FETCH_MAX_BYTES = FETCH_MAX_BYTES;
|
|
4400
|
+
exports.FETCH_RULES = FETCH_RULES;
|
|
4401
|
+
exports.FetchStore = FetchStore;
|
|
4402
|
+
exports.GATES = GATES;
|
|
4403
|
+
exports.HOST_ERROR_NAME = HOST_ERROR_NAME;
|
|
4404
|
+
exports.HOST_OPS = HOST_OPS;
|
|
4405
|
+
exports.MAX_OPEN_TRANSACTIONS = MAX_OPEN_TRANSACTIONS;
|
|
4406
|
+
exports.REFUSALS = REFUSALS;
|
|
4407
|
+
exports.SURFACES = SURFACES;
|
|
4408
|
+
exports.TableStore = TableStore;
|
|
4409
|
+
exports.TypedTable = TypedTable;
|
|
4410
|
+
exports.ZOO = ZOO;
|
|
4411
|
+
exports.ZOO_SHA256 = ZOO_SHA256;
|
|
4412
|
+
exports.ZooRefusal = ZooRefusal;
|
|
4413
|
+
exports.casesFor = casesFor;
|
|
4414
|
+
exports.contextError = contextError;
|
|
4415
|
+
exports.createTestContext = createTestContext;
|
|
4416
|
+
exports.expectation = expectation;
|
|
4417
|
+
exports.hostError = hostError;
|
|
4418
|
+
exports.isHostError = isHostError;
|
|
4419
|
+
exports.isSafeOutbound = isSafeOutbound;
|
|
4420
|
+
exports.isolateAtob = isolateAtob;
|
|
4421
|
+
exports.isolateBtoa = isolateBtoa;
|
|
4422
|
+
exports.knownTypes = knownTypes;
|
|
4423
|
+
exports.readGates = readGates;
|
|
4424
|
+
exports.refuse = refuse;
|
|
4425
|
+
exports.render = render;
|
|
4426
|
+
exports.renderValue = renderValue;
|
|
4427
|
+
exports.runWithoutIsolateGlobals = runWithoutIsolateGlobals;
|
|
4428
|
+
exports.writerSchema = writerSchema;
|
|
4429
|
+
exports.zooColumn = zooColumn;
|
|
4430
|
+
exports.zooTableName = zooTableName;
|
|
4431
|
+
//# sourceMappingURL=testing.cjs.map
|