@malloydata/malloy 0.0.424 → 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/dist/api/foundation/compile.js +5 -1
- package/dist/api/foundation/runtime.d.ts +1 -3
- package/dist/api/foundation/runtime.js +23 -19
- package/dist/connection/base_connection.d.ts +23 -0
- package/dist/connection/base_connection.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/query_metadata.d.ts +17 -0
- package/dist/query_metadata.js +60 -0
- package/dist/run_sql_options.d.ts +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -374,7 +374,11 @@ class Malloy {
|
|
|
374
374
|
connection = await connections.lookupConnection(connectionName);
|
|
375
375
|
}
|
|
376
376
|
if (sqlStruct) {
|
|
377
|
-
|
|
377
|
+
// Only the metadata: a SQL block's rowLimit/abortSignal handling is what
|
|
378
|
+
// it was, and changing that is not this feature's business.
|
|
379
|
+
const data = await connection.runSQL(sqlStruct.selectStr, {
|
|
380
|
+
queryMetadata: options === null || options === void 0 ? void 0 : options.queryMetadata,
|
|
381
|
+
});
|
|
378
382
|
return new result_1.Result({
|
|
379
383
|
structs: [sqlStruct],
|
|
380
384
|
sql: sqlStruct.selectStr,
|
|
@@ -546,9 +546,7 @@ export declare class PreparedResultMaterializer extends FluentState<PreparedResu
|
|
|
546
546
|
* @return A promise to the query result data.
|
|
547
547
|
*/
|
|
548
548
|
run(options?: RunSQLOptions): Promise<Result>;
|
|
549
|
-
runStream(options?:
|
|
550
|
-
rowLimit?: number;
|
|
551
|
-
}): AsyncIterableIterator<DataRecord>;
|
|
549
|
+
runStream(options?: RunSQLOptions): AsyncIterableIterator<DataRecord>;
|
|
552
550
|
/**
|
|
553
551
|
* Materialize this loaded prepared result.
|
|
554
552
|
*
|
|
@@ -586,33 +586,37 @@ class ModelMaterializer extends FluentState {
|
|
|
586
586
|
* or loading further related objects.
|
|
587
587
|
*/
|
|
588
588
|
loadQuery(query, options) {
|
|
589
|
-
|
|
589
|
+
// `options` spans three interfaces. The parse/compile half is spent here,
|
|
590
|
+
// compiling the query text; the `CompileQueryOptions` half belongs to the
|
|
591
|
+
// materializer, which applies it when the query is turned into SQL — so it
|
|
592
|
+
// has to reach `makeQueryMaterializer` as well, or a `buildManifest` (or
|
|
593
|
+
// `givens`, or `defaultRowLimit`) passed here is silently ignored.
|
|
594
|
+
//
|
|
595
|
+
// Split by naming the parse/compile keys rather than the query ones, so a
|
|
596
|
+
// query option added later is carried rather than quietly dropped.
|
|
597
|
+
// `restrictedMode` and `method` are named to hold them back: this entry
|
|
598
|
+
// point has never honored them, and `loadRestrictedQuery` is how a caller
|
|
599
|
+
// asks for restricted compilation.
|
|
600
|
+
const { importBaseURL, testEnvironment, refreshSchemaCache, noThrowOnError, restrictedMode: _restrictedMode, method: _method, ...callQueryOptions } = options !== null && options !== void 0 ? options : {};
|
|
601
|
+
const compileQueryOptions = {
|
|
602
|
+
...this.compileQueryOptions,
|
|
603
|
+
...callQueryOptions,
|
|
604
|
+
};
|
|
590
605
|
return this.makeQueryMaterializer(async () => {
|
|
591
|
-
const urlReader = this.runtime.urlReader;
|
|
592
|
-
const connections = this.runtime.connections;
|
|
593
|
-
if (this.runtime.isTestRuntime) {
|
|
594
|
-
if (options === undefined) {
|
|
595
|
-
options = { testEnvironment: true };
|
|
596
|
-
}
|
|
597
|
-
else {
|
|
598
|
-
options = { ...options, testEnvironment: true };
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
606
|
const compilable = query instanceof URL ? { url: query } : { source: query };
|
|
602
|
-
const model = await this.getModel();
|
|
603
607
|
const queryModel = await compile_1.Malloy.compile({
|
|
604
608
|
...compilable,
|
|
605
|
-
urlReader,
|
|
606
|
-
connections,
|
|
607
|
-
model,
|
|
609
|
+
urlReader: this.runtime.urlReader,
|
|
610
|
+
connections: this.runtime.connections,
|
|
611
|
+
model: await this.getModel(),
|
|
608
612
|
refreshSchemaCache,
|
|
609
613
|
noThrowOnError,
|
|
610
|
-
importBaseURL
|
|
611
|
-
testEnvironment:
|
|
612
|
-
...
|
|
614
|
+
importBaseURL,
|
|
615
|
+
testEnvironment: testEnvironment || this.runtime.isTestRuntime,
|
|
616
|
+
...compileQueryOptions,
|
|
613
617
|
});
|
|
614
618
|
return queryModel.preparedQuery;
|
|
615
|
-
});
|
|
619
|
+
}, compileQueryOptions);
|
|
616
620
|
}
|
|
617
621
|
/**
|
|
618
622
|
* Load a Malloy query whose text comes from an untrusted source — an
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SQLSourceRequest } from '../lang/translate-response';
|
|
2
2
|
import type { MalloyQueryData, QueryRunStats, SQLSourceDef, StructDef, TableSourceDef } from '../model/malloy_types';
|
|
3
|
+
import type { QueryMetadata } from '../query_metadata';
|
|
3
4
|
import type { RunSQLOptions } from '../run_sql_options';
|
|
4
5
|
import type { Connection, FetchSchemaOptions, PersistSQLResults, PooledConnection, StreamingConnection } from './types';
|
|
5
6
|
export interface SchemaFound<T extends StructDef> {
|
|
@@ -36,6 +37,28 @@ export declare abstract class BaseConnection implements Connection {
|
|
|
36
37
|
error: string;
|
|
37
38
|
structDef?: undefined;
|
|
38
39
|
}>;
|
|
40
|
+
/**
|
|
41
|
+
* The validated bag, or undefined when there is nothing to apply. For
|
|
42
|
+
* connectors with a native key-value mechanism, e.g. BigQuery job labels.
|
|
43
|
+
*/
|
|
44
|
+
protected queryMetadataBag(meta: QueryMetadata | undefined): QueryMetadata | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The metadata as a single SQL comment line, empty when there is nothing to
|
|
47
|
+
* apply. For a connector which has to place the comment itself rather than
|
|
48
|
+
* simply prepending it — see {@link sqlWithQueryMetadata}.
|
|
49
|
+
*
|
|
50
|
+
* ```
|
|
51
|
+
* -- NAME1="val1" NAME2="val2"
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
protected queryMetadataComment(meta: QueryMetadata | undefined): string;
|
|
55
|
+
/**
|
|
56
|
+
* `sql` with the metadata prepended as a leading comment, or `sql` unchanged
|
|
57
|
+
* when there is nothing to apply. For connectors with no native per-query
|
|
58
|
+
* mechanism. The contract guarantees the rendered comment is safe: no
|
|
59
|
+
* embedded quote, no newline.
|
|
60
|
+
*/
|
|
61
|
+
protected sqlWithQueryMetadata(sql: string, meta: QueryMetadata | undefined): string;
|
|
39
62
|
isPool(): this is PooledConnection;
|
|
40
63
|
canPersist(): this is PersistSQLResults;
|
|
41
64
|
canStream(): this is StreamingConnection;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.BaseConnection = void 0;
|
|
8
8
|
const sql_block_1 = require("../model/sql_block");
|
|
9
|
+
const query_metadata_1 = require("../query_metadata");
|
|
9
10
|
const validate_table_path_1 = require("./validate_table_path");
|
|
10
11
|
class BaseConnection {
|
|
11
12
|
constructor() {
|
|
@@ -72,6 +73,49 @@ class BaseConnection {
|
|
|
72
73
|
}
|
|
73
74
|
return { error: 'Unknown schema fetch error' };
|
|
74
75
|
}
|
|
76
|
+
/*
|
|
77
|
+
* Applying `RunSQLOptions.queryMetadata`. A connector uses whichever of these
|
|
78
|
+
* fits its backend: a native key-value mechanism takes the bag, everything
|
|
79
|
+
* else prepends the comment. They validate, so a bag that violates the
|
|
80
|
+
* contract throws rather than reaching the database in some mangled form.
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* The validated bag, or undefined when there is nothing to apply. For
|
|
84
|
+
* connectors with a native key-value mechanism, e.g. BigQuery job labels.
|
|
85
|
+
*/
|
|
86
|
+
queryMetadataBag(meta) {
|
|
87
|
+
if (meta === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
(0, query_metadata_1.validateQueryMetadata)(meta);
|
|
90
|
+
return Object.keys(meta).length > 0 ? meta : undefined;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The metadata as a single SQL comment line, empty when there is nothing to
|
|
94
|
+
* apply. For a connector which has to place the comment itself rather than
|
|
95
|
+
* simply prepending it — see {@link sqlWithQueryMetadata}.
|
|
96
|
+
*
|
|
97
|
+
* ```
|
|
98
|
+
* -- NAME1="val1" NAME2="val2"
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
queryMetadataComment(meta) {
|
|
102
|
+
const bag = this.queryMetadataBag(meta);
|
|
103
|
+
if (bag === undefined)
|
|
104
|
+
return '';
|
|
105
|
+
const rendered = Object.keys(bag)
|
|
106
|
+
.map(key => `${key}="${bag[key]}"`)
|
|
107
|
+
.join(' ');
|
|
108
|
+
return `-- ${rendered}\n`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `sql` with the metadata prepended as a leading comment, or `sql` unchanged
|
|
112
|
+
* when there is nothing to apply. For connectors with no native per-query
|
|
113
|
+
* mechanism. The contract guarantees the rendered comment is safe: no
|
|
114
|
+
* embedded quote, no newline.
|
|
115
|
+
*/
|
|
116
|
+
sqlWithQueryMetadata(sql, meta) {
|
|
117
|
+
return this.queryMetadataComment(meta) + sql;
|
|
118
|
+
}
|
|
75
119
|
isPool() {
|
|
76
120
|
return false;
|
|
77
121
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type { Overlay, ConfigOverlays } from './api/foundation';
|
|
|
10
10
|
export type { FilesystemContext, MalloyConfigOptions } from './api/foundation';
|
|
11
11
|
export type { RuntimeContext } from './api/foundation';
|
|
12
12
|
export type { QueryOptionsReader, RunSQLOptions } from './run_sql_options';
|
|
13
|
+
export type { QueryMetadata } from './query_metadata';
|
|
13
14
|
export type { EventStream, ModelString, ModelURL, QueryString, QueryURL, URLReader, InvalidationKey, } from './runtime_types';
|
|
14
15
|
export type { Connection, ConnectionConfig, ConnectionParameterValue, FetchSchemaOptions, InfoConnection, LookupConnection, PersistSQLResults, PooledConnection, TestableConnection, StreamingConnection, } from './connection/types';
|
|
15
16
|
export { registerConnectionType, getConnectionProperties, getConnectionTypeDisplayName, getRegisteredConnectionTypes, createConnectionsFromConfig, } from './connection/registry';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialect-agnostic metadata attached per query to the statements a connection
|
|
3
|
+
* issues, for the backend's own bookkeeping (cost attribution, workload
|
|
4
|
+
* classification, tracing): a small bag of string-valued properties.
|
|
5
|
+
*
|
|
6
|
+
* Each connector applies it through a per-query mechanism — Snowflake's
|
|
7
|
+
* per-statement `QUERY_TAG` and BigQuery's per-job labels natively, others as a
|
|
8
|
+
* leading SQL comment. It never affects query results or data identity.
|
|
9
|
+
*
|
|
10
|
+
* This is the only name in this file which is public API; a connector reaches
|
|
11
|
+
* the machinery for applying a bag through `BaseConnection`.
|
|
12
|
+
*/
|
|
13
|
+
export type QueryMetadata = Record<string, string>;
|
|
14
|
+
/** The ways `meta` violates the contract (empty = conforming). */
|
|
15
|
+
export declare function queryMetadataProblems(meta: QueryMetadata): string[];
|
|
16
|
+
/** Validate query metadata, throwing if the bag is invalid. */
|
|
17
|
+
export declare function validateQueryMetadata(meta: QueryMetadata): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.queryMetadataProblems = queryMetadataProblems;
|
|
8
|
+
exports.validateQueryMetadata = validateQueryMetadata;
|
|
9
|
+
// Contract for a bag that is usable across every backend and always safe to
|
|
10
|
+
// render (including into the `-- NAME="value"` comment form):
|
|
11
|
+
// - names: ASCII alphanumerics and underscore
|
|
12
|
+
// - values: printable ASCII, excluding the double-quote
|
|
13
|
+
// - a small number of properties
|
|
14
|
+
// Connectors may transform within these rules (e.g. BigQuery lowercases keys).
|
|
15
|
+
const QUERY_METADATA_MAX_KEY_LENGTH = 128;
|
|
16
|
+
const QUERY_METADATA_MAX_VALUE_LENGTH = 256;
|
|
17
|
+
const QUERY_METADATA_MAX_PROPERTIES = 20;
|
|
18
|
+
const KEY_RE = /^[A-Za-z0-9_]+$/;
|
|
19
|
+
// Printable ASCII (0x20–0x7e), excluding the double-quote (0x22) that would
|
|
20
|
+
// break the `NAME="value"` comment form. Also excludes control characters.
|
|
21
|
+
function isValidValue(s) {
|
|
22
|
+
for (let i = 0; i < s.length; i++) {
|
|
23
|
+
const code = s.charCodeAt(i);
|
|
24
|
+
if (code < 0x20 || code > 0x7e || code === 0x22)
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
/** The ways `meta` violates the contract (empty = conforming). */
|
|
30
|
+
function queryMetadataProblems(meta) {
|
|
31
|
+
const problems = [];
|
|
32
|
+
const keys = Object.keys(meta);
|
|
33
|
+
if (keys.length > QUERY_METADATA_MAX_PROPERTIES) {
|
|
34
|
+
problems.push(`more than ${QUERY_METADATA_MAX_PROPERTIES} properties`);
|
|
35
|
+
}
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
if (!KEY_RE.test(key)) {
|
|
38
|
+
problems.push(`property name '${key}' must be ASCII alphanumerics and underscore`);
|
|
39
|
+
}
|
|
40
|
+
if (key.length > QUERY_METADATA_MAX_KEY_LENGTH) {
|
|
41
|
+
problems.push(`property name '${key}' exceeds ${QUERY_METADATA_MAX_KEY_LENGTH} characters`);
|
|
42
|
+
}
|
|
43
|
+
const value = meta[key];
|
|
44
|
+
if (!isValidValue(value)) {
|
|
45
|
+
problems.push(`property '${key}' value contains characters outside printable ASCII (or ")`);
|
|
46
|
+
}
|
|
47
|
+
if (value.length > QUERY_METADATA_MAX_VALUE_LENGTH) {
|
|
48
|
+
problems.push(`property '${key}' value exceeds ${QUERY_METADATA_MAX_VALUE_LENGTH} characters`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return problems;
|
|
52
|
+
}
|
|
53
|
+
/** Validate query metadata, throwing if the bag is invalid. */
|
|
54
|
+
function validateQueryMetadata(meta) {
|
|
55
|
+
const problems = queryMetadataProblems(meta);
|
|
56
|
+
if (problems.length > 0) {
|
|
57
|
+
throw new Error(`Invalid query metadata: ${problems.join('; ')}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=query_metadata.js.map
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import type { QueryMetadata } from './query_metadata';
|
|
1
2
|
export interface RunSQLOptions {
|
|
2
3
|
rowLimit?: number;
|
|
3
4
|
abortSignal?: AbortSignal;
|
|
5
|
+
/**
|
|
6
|
+
* Optional per-query metadata (a small bag of string-valued properties)
|
|
7
|
+
* attached to the query for the backend's own bookkeeping — applied by the
|
|
8
|
+
* connector according to its capabilities. See {@link QueryMetadata}.
|
|
9
|
+
*/
|
|
10
|
+
queryMetadata?: QueryMetadata;
|
|
4
11
|
}
|
|
5
12
|
export type QueryOptionsReader = RunSQLOptions | (() => RunSQLOptions);
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MALLOY_VERSION = "0.0.
|
|
1
|
+
export declare const MALLOY_VERSION = "0.0.426";
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MALLOY_VERSION = void 0;
|
|
4
4
|
// generated with 'generate-version-file' script; do not edit manually
|
|
5
|
-
exports.MALLOY_VERSION = '0.0.
|
|
5
|
+
exports.MALLOY_VERSION = '0.0.426';
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/malloy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.426",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@malloydata/malloy-filter": "0.0.
|
|
55
|
-
"@malloydata/malloy-interfaces": "0.0.
|
|
56
|
-
"@malloydata/malloy-tag": "0.0.
|
|
54
|
+
"@malloydata/malloy-filter": "0.0.426",
|
|
55
|
+
"@malloydata/malloy-interfaces": "0.0.426",
|
|
56
|
+
"@malloydata/malloy-tag": "0.0.426",
|
|
57
57
|
"@noble/hashes": "^1.8.0",
|
|
58
58
|
"antlr4ts": "^0.5.0-alpha.4",
|
|
59
59
|
"assert": "^2.0.0",
|