@malloydata/malloy-tests 0.0.425 → 0.0.426
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/package.json +10 -10
- package/src/core/persist.spec.ts +190 -0
package/package.json
CHANGED
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
"malloyc": "ts-node ../scripts/malloy-to-json"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@malloydata/db-bigquery": "0.0.
|
|
24
|
-
"@malloydata/db-duckdb": "0.0.
|
|
25
|
-
"@malloydata/db-postgres": "0.0.
|
|
26
|
-
"@malloydata/db-snowflake": "0.0.
|
|
27
|
-
"@malloydata/db-trino": "0.0.
|
|
28
|
-
"@malloydata/malloy": "0.0.
|
|
29
|
-
"@malloydata/malloy-tag": "0.0.
|
|
30
|
-
"@malloydata/render": "0.0.
|
|
31
|
-
"@malloydata/render-validator": "0.0.
|
|
23
|
+
"@malloydata/db-bigquery": "0.0.426",
|
|
24
|
+
"@malloydata/db-duckdb": "0.0.426",
|
|
25
|
+
"@malloydata/db-postgres": "0.0.426",
|
|
26
|
+
"@malloydata/db-snowflake": "0.0.426",
|
|
27
|
+
"@malloydata/db-trino": "0.0.426",
|
|
28
|
+
"@malloydata/malloy": "0.0.426",
|
|
29
|
+
"@malloydata/malloy-tag": "0.0.426",
|
|
30
|
+
"@malloydata/render": "0.0.426",
|
|
31
|
+
"@malloydata/render-validator": "0.0.426",
|
|
32
32
|
"events": "^3.3.0",
|
|
33
33
|
"jsdom": "^22.1.0",
|
|
34
34
|
"luxon": "^3.7.2",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"overrides": {
|
|
43
43
|
"typescript": "^6.0.3"
|
|
44
44
|
},
|
|
45
|
-
"version": "0.0.
|
|
45
|
+
"version": "0.0.426"
|
|
46
46
|
}
|
package/src/core/persist.spec.ts
CHANGED
|
@@ -2179,4 +2179,194 @@ describe('source persistence', () => {
|
|
|
2179
2179
|
expect(sql).toContain('cached.by_carrier');
|
|
2180
2180
|
});
|
|
2181
2181
|
});
|
|
2182
|
+
|
|
2183
|
+
// Reported on #3015. Both describes assert the behavior the documentation
|
|
2184
|
+
// describes, so a failure here is the report reproducing.
|
|
2185
|
+
describe('sql_select substitution', () => {
|
|
2186
|
+
// One model, two persisted sources of the two persistable kinds, plus a
|
|
2187
|
+
// third that reaches the sql_select through interpolation.
|
|
2188
|
+
const TWO_KINDS = `${PERSIST_ANNOTATION}
|
|
2189
|
+
${FLIGHTS_SOURCE}
|
|
2190
|
+
|
|
2191
|
+
#@ persist
|
|
2192
|
+
source: qs is flights -> {
|
|
2193
|
+
group_by: carrier
|
|
2194
|
+
aggregate: flight_count is count()
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
#@ persist
|
|
2198
|
+
source: ss is ${tstDB}.sql("""
|
|
2199
|
+
SELECT carrier, COUNT(*) as flight_count
|
|
2200
|
+
FROM malloytest.flights
|
|
2201
|
+
GROUP BY carrier
|
|
2202
|
+
""")
|
|
2203
|
+
|
|
2204
|
+
#@ persist
|
|
2205
|
+
source: via_interp is ${tstDB}.sql("""
|
|
2206
|
+
SELECT * FROM %{ ss }
|
|
2207
|
+
""")
|
|
2208
|
+
`;
|
|
2209
|
+
|
|
2210
|
+
// A manifest holding a built table for every persist source in the model.
|
|
2211
|
+
async function manifestForAll(): Promise<BuildManifest> {
|
|
2212
|
+
const model = await wrapTestModel(tstRuntime, TWO_KINDS).model.getModel();
|
|
2213
|
+
const digest = await getDigest();
|
|
2214
|
+
const manifest = createManifest();
|
|
2215
|
+
for (const source of Object.values(model.getBuildPlan().sources)) {
|
|
2216
|
+
addManifestEntry(
|
|
2217
|
+
manifest,
|
|
2218
|
+
source.makeBuildId(digest, source.getSQL()),
|
|
2219
|
+
`cached.${source.name}_table`
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
return manifest;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
async function sqlForQuery(query: string): Promise<string> {
|
|
2226
|
+
const manifest = await manifestForAll();
|
|
2227
|
+
return runtimeWithManifest(manifest)
|
|
2228
|
+
.loadQuery(`${TWO_KINDS}\n${query}`)
|
|
2229
|
+
.getSQL();
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
it('query_source reads its built table', async () => {
|
|
2233
|
+
const sql = await sqlForQuery('run: qs -> { select: * }');
|
|
2234
|
+
expect(sql).toContain('cached.qs_table');
|
|
2235
|
+
expect(sql).not.toContain('COUNT(');
|
|
2236
|
+
});
|
|
2237
|
+
|
|
2238
|
+
// Pending #3016 — a directly-referenced sql_select never reaches the
|
|
2239
|
+
// manifest. The two passing tests around these are the controls: the
|
|
2240
|
+
// other persistable kind substitutes, and the same source substitutes
|
|
2241
|
+
// when interpolation reaches it, so the entry and its key are right.
|
|
2242
|
+
it.skip('sql_select reads its built table', async () => {
|
|
2243
|
+
const sql = await sqlForQuery('run: ss -> { select: * }');
|
|
2244
|
+
expect(sql).toContain('cached.ss_table');
|
|
2245
|
+
expect(sql).not.toContain('COUNT(');
|
|
2246
|
+
});
|
|
2247
|
+
|
|
2248
|
+
it('sql_select reached by interpolation reads its built table', async () => {
|
|
2249
|
+
// The manifest key and the entry are right — this path finds them.
|
|
2250
|
+
const sql = await sqlForQuery('run: via_interp -> { select: * }');
|
|
2251
|
+
expect(sql).toContain('cached.ss_table');
|
|
2252
|
+
expect(sql).not.toContain('COUNT(');
|
|
2253
|
+
});
|
|
2254
|
+
|
|
2255
|
+
it.skip('strict mode catches a missing sql_select entry', async () => {
|
|
2256
|
+
// Strict mode's promise is that a persist source with no manifest entry
|
|
2257
|
+
// throws rather than silently compiling to inline SQL.
|
|
2258
|
+
const strict = createManifest();
|
|
2259
|
+
strict.strict = true;
|
|
2260
|
+
await expect(
|
|
2261
|
+
runtimeWithManifest(strict)
|
|
2262
|
+
.loadQuery(`${TWO_KINDS}\nrun: ss -> { select: * }`)
|
|
2263
|
+
.getSQL()
|
|
2264
|
+
).rejects.toThrow(/not found in manifest/);
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
it.skip('a join of both kinds substitutes both', async () => {
|
|
2268
|
+
const sql = await sqlForQuery(`
|
|
2269
|
+
run: flights extend {
|
|
2270
|
+
join_one: q is qs on carrier = q.carrier
|
|
2271
|
+
join_one: s is ss on carrier = s.carrier
|
|
2272
|
+
} -> {
|
|
2273
|
+
group_by: carrier
|
|
2274
|
+
aggregate: a is q.flight_count.sum(), b is s.flight_count.sum()
|
|
2275
|
+
}
|
|
2276
|
+
`);
|
|
2277
|
+
expect(sql).toContain('cached.qs_table');
|
|
2278
|
+
expect(sql).toContain('cached.ss_table');
|
|
2279
|
+
});
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
describe('per-call buildManifest on ModelMaterializer.loadQuery', () => {
|
|
2283
|
+
const MODEL = `${PERSIST_ANNOTATION}
|
|
2284
|
+
${FLIGHTS_SOURCE}
|
|
2285
|
+
|
|
2286
|
+
#@ persist
|
|
2287
|
+
source: by_carrier is flights -> {
|
|
2288
|
+
group_by: carrier
|
|
2289
|
+
aggregate: flight_count is count()
|
|
2290
|
+
}
|
|
2291
|
+
`;
|
|
2292
|
+
const QUERY = 'run: by_carrier -> { select: * }';
|
|
2293
|
+
|
|
2294
|
+
async function manifestForByCarrier(): Promise<BuildManifest> {
|
|
2295
|
+
const model = await wrapTestModel(tstRuntime, MODEL).model.getModel();
|
|
2296
|
+
const {manifest} = await buildManifestFor(
|
|
2297
|
+
model.getBuildPlan(),
|
|
2298
|
+
'by_carrier',
|
|
2299
|
+
'cached.by_carrier'
|
|
2300
|
+
);
|
|
2301
|
+
return manifest;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
function plainRuntime() {
|
|
2305
|
+
return new SingleConnectionRuntime({
|
|
2306
|
+
connection: tstRuntime.connection,
|
|
2307
|
+
urlReader: testFileSpace,
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
it('applies a manifest passed to the call', async () => {
|
|
2312
|
+
const manifest = await manifestForByCarrier();
|
|
2313
|
+
const sql = await plainRuntime()
|
|
2314
|
+
.loadModel(MODEL)
|
|
2315
|
+
.loadQuery(QUERY, {buildManifest: manifest})
|
|
2316
|
+
.getSQL();
|
|
2317
|
+
expect(sql).toContain('cached.by_carrier');
|
|
2318
|
+
});
|
|
2319
|
+
|
|
2320
|
+
it('applies a manifest passed to loadModel', async () => {
|
|
2321
|
+
const manifest = await manifestForByCarrier();
|
|
2322
|
+
const sql = await plainRuntime()
|
|
2323
|
+
.loadModel(MODEL, {buildManifest: manifest})
|
|
2324
|
+
.loadQuery(QUERY)
|
|
2325
|
+
.getSQL();
|
|
2326
|
+
expect(sql).toContain('cached.by_carrier');
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
it('EMPTY_BUILD_MANIFEST suppresses the runtime manifest', async () => {
|
|
2330
|
+
// The dangerous direction: asking for unsubstituted SQL and being
|
|
2331
|
+
// handed the substituted form.
|
|
2332
|
+
const manifest = await manifestForByCarrier();
|
|
2333
|
+
const sql = await runtimeWithManifest(manifest)
|
|
2334
|
+
.loadModel(MODEL)
|
|
2335
|
+
.loadQuery(QUERY, {buildManifest: EMPTY_BUILD_MANIFEST})
|
|
2336
|
+
.getSQL();
|
|
2337
|
+
expect(sql).not.toContain('cached.by_carrier');
|
|
2338
|
+
expect(sql).toContain('COUNT(');
|
|
2339
|
+
});
|
|
2340
|
+
|
|
2341
|
+
it('loadQueryByName applies a manifest passed to the call', async () => {
|
|
2342
|
+
// The sibling loaders merge their options; this is the control.
|
|
2343
|
+
const manifest = await manifestForByCarrier();
|
|
2344
|
+
const sql = await plainRuntime()
|
|
2345
|
+
.loadModel(`${MODEL}\nquery: named is by_carrier -> { select: * }`)
|
|
2346
|
+
.loadQueryByName('named', {buildManifest: manifest})
|
|
2347
|
+
.getSQL();
|
|
2348
|
+
expect(sql).toContain('cached.by_carrier');
|
|
2349
|
+
});
|
|
2350
|
+
|
|
2351
|
+
it('Runtime.loadQuery applies a manifest passed to the call', async () => {
|
|
2352
|
+
// The other control: the same options on the Runtime entry point.
|
|
2353
|
+
const manifest = await manifestForByCarrier();
|
|
2354
|
+
const sql = await plainRuntime()
|
|
2355
|
+
.loadQuery(`${MODEL}\n${QUERY}`, {buildManifest: manifest})
|
|
2356
|
+
.getSQL();
|
|
2357
|
+
expect(sql).toContain('cached.by_carrier');
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
it('applies defaultRowLimit passed to the call', async () => {
|
|
2361
|
+
// buildManifest is not special: nothing in CompileQueryOptions
|
|
2362
|
+
// survives this call.
|
|
2363
|
+
const sql = await plainRuntime()
|
|
2364
|
+
.loadModel(MODEL)
|
|
2365
|
+
.loadQuery('run: flights -> { select: carrier }', {
|
|
2366
|
+
defaultRowLimit: 7,
|
|
2367
|
+
})
|
|
2368
|
+
.getSQL();
|
|
2369
|
+
expect(sql).toContain('LIMIT 7');
|
|
2370
|
+
});
|
|
2371
|
+
});
|
|
2182
2372
|
});
|