@malloydata/malloy 0.0.222-dev241211235345 → 0.0.222-dev241212021944
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.
|
@@ -55,6 +55,7 @@ export declare class DuckDBDialect extends PostgresBase {
|
|
|
55
55
|
[name: string]: DialectFunctionOverloadDef[];
|
|
56
56
|
};
|
|
57
57
|
malloyTypeToSQLType(malloyType: AtomicTypeDef): string;
|
|
58
|
+
parseDuckDBType(sqlType: string): AtomicTypeDef;
|
|
58
59
|
sqlTypeToMalloyType(sqlType: string): LeafAtomicTypeDef;
|
|
59
60
|
castToString(expression: string): string;
|
|
60
61
|
concat(...values: string[]): string;
|
|
@@ -30,6 +30,7 @@ const dialect_1 = require("../dialect");
|
|
|
30
30
|
const pg_impl_1 = require("../pg_impl");
|
|
31
31
|
const dialect_functions_1 = require("./dialect_functions");
|
|
32
32
|
const function_overrides_1 = require("./function_overrides");
|
|
33
|
+
const tiny_parser_1 = require("../tiny_parser");
|
|
33
34
|
// need to refactor runSQL to take a SQLBlock instead of just a sql string.
|
|
34
35
|
const hackSplitComment = '-- hack: split on this';
|
|
35
36
|
const duckDBToMalloyTypes = {
|
|
@@ -287,6 +288,20 @@ class DuckDBDialect extends pg_impl_1.PostgresBase {
|
|
|
287
288
|
}
|
|
288
289
|
return malloyType.type;
|
|
289
290
|
}
|
|
291
|
+
parseDuckDBType(sqlType) {
|
|
292
|
+
const parser = new DuckDBTypeParser(sqlType);
|
|
293
|
+
try {
|
|
294
|
+
return parser.typeDef();
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
if (e instanceof tiny_parser_1.TinyParseError) {
|
|
298
|
+
return { type: 'sql native', rawType: sqlType };
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
throw e;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
290
305
|
sqlTypeToMalloyType(sqlType) {
|
|
291
306
|
var _a, _b, _c;
|
|
292
307
|
// Remove decimal precision
|
|
@@ -350,4 +365,113 @@ class DuckDBDialect extends pg_impl_1.PostgresBase {
|
|
|
350
365
|
}
|
|
351
366
|
}
|
|
352
367
|
exports.DuckDBDialect = DuckDBDialect;
|
|
368
|
+
class DuckDBTypeParser extends tiny_parser_1.TinyParser {
|
|
369
|
+
constructor(input) {
|
|
370
|
+
super(input, {
|
|
371
|
+
/* whitespace */ space: /^\s+/,
|
|
372
|
+
/* single quoted string */ qsingle: /^'([^']|'')*'/,
|
|
373
|
+
/* double quoted string */ qdouble: /^"([^"]|"")*"/,
|
|
374
|
+
/* (n) size */ size: /^\(\d+\)/,
|
|
375
|
+
/* (n1,n2) precision */ precision: /^\(\d+,\d+\)/,
|
|
376
|
+
/* T[] -> array of T */ arrayOf: /^\[]/,
|
|
377
|
+
/* other punctuation */ char: /^[,:[\]()-]/,
|
|
378
|
+
/* unquoted word */ id: /^\w+/,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
unquoteName(token) {
|
|
382
|
+
if (token.type === 'qsingle') {
|
|
383
|
+
return token.text.replace("''", '');
|
|
384
|
+
}
|
|
385
|
+
else if (token.type === 'qdouble') {
|
|
386
|
+
return token.text.replace('""', '');
|
|
387
|
+
}
|
|
388
|
+
return token.text;
|
|
389
|
+
}
|
|
390
|
+
sqlID(token) {
|
|
391
|
+
return token.text.toUpperCase();
|
|
392
|
+
}
|
|
393
|
+
typeDef() {
|
|
394
|
+
const unknownStart = this.parseCursor;
|
|
395
|
+
const wantID = this.next('id');
|
|
396
|
+
const id = this.sqlID(wantID);
|
|
397
|
+
let baseType;
|
|
398
|
+
if (id === 'VARCHAR') {
|
|
399
|
+
if (this.peek().type === 'size') {
|
|
400
|
+
this.next();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if ((id === 'DECIMAL' || id === 'NUMERIC') &&
|
|
404
|
+
this.peek().type === 'precision') {
|
|
405
|
+
this.next();
|
|
406
|
+
baseType = { type: 'number', numberType: 'float' };
|
|
407
|
+
}
|
|
408
|
+
else if (id === 'TIMESTAMP') {
|
|
409
|
+
if (this.peek().text === 'WITH') {
|
|
410
|
+
this.nextText('WITH', 'TIME', 'ZONE');
|
|
411
|
+
}
|
|
412
|
+
baseType = { type: 'timestamp' };
|
|
413
|
+
}
|
|
414
|
+
else if (duckDBToMalloyTypes[id]) {
|
|
415
|
+
baseType = duckDBToMalloyTypes[id];
|
|
416
|
+
}
|
|
417
|
+
else if (id === 'STRUCT') {
|
|
418
|
+
this.next('(');
|
|
419
|
+
baseType = { type: 'record', fields: [] };
|
|
420
|
+
for (;;) {
|
|
421
|
+
const fieldName = this.next();
|
|
422
|
+
if (fieldName.type === 'qsingle' ||
|
|
423
|
+
fieldName.type === 'qdouble' ||
|
|
424
|
+
fieldName.type === 'id') {
|
|
425
|
+
const fieldType = this.typeDef();
|
|
426
|
+
baseType.fields.push((0, malloy_types_1.mkFieldDef)(fieldType, this.unquoteName(fieldName), 'duckdb'));
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
if (fieldName.type !== ')') {
|
|
430
|
+
throw this.parseError('Expected identifier or ) to end STRUCT');
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
if (this.peek().type === ',') {
|
|
435
|
+
this.next();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
if (wantID.type === 'id') {
|
|
441
|
+
for (;;) {
|
|
442
|
+
const next = this.peek();
|
|
443
|
+
// Might be WEIRDTYP(a,b)[] ... stop at the []
|
|
444
|
+
if (next.type === 'arrayOf' || next.type === 'eof') {
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
this.next();
|
|
448
|
+
}
|
|
449
|
+
baseType = {
|
|
450
|
+
type: 'sql native',
|
|
451
|
+
rawType: this.input.slice(unknownStart, this.parseCursor - unknownStart + 1),
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
throw this.parseError('Could not understand type');
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
while (this.peek().type === 'arrayOf') {
|
|
459
|
+
this.next();
|
|
460
|
+
if (baseType.type === 'record') {
|
|
461
|
+
baseType = {
|
|
462
|
+
type: 'array',
|
|
463
|
+
elementTypeDef: { type: 'record_element' },
|
|
464
|
+
fields: baseType.fields,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
baseType = {
|
|
469
|
+
type: 'array',
|
|
470
|
+
elementTypeDef: baseType,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return baseType;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
353
477
|
//# sourceMappingURL=duckdb.js.map
|
|
@@ -9,10 +9,12 @@ export interface TinyToken {
|
|
|
9
9
|
*
|
|
10
10
|
* NOTE: All parse errors are exceptions.
|
|
11
11
|
*/
|
|
12
|
+
export declare class TinyParseError extends Error {
|
|
13
|
+
}
|
|
12
14
|
export declare class TinyParser {
|
|
13
15
|
readonly input: string;
|
|
14
16
|
private tokens;
|
|
15
|
-
|
|
17
|
+
protected parseCursor: number;
|
|
16
18
|
private lookAhead?;
|
|
17
19
|
private tokenMap;
|
|
18
20
|
/**
|
|
@@ -26,7 +28,7 @@ export declare class TinyParser {
|
|
|
26
28
|
* last characters stripped
|
|
27
29
|
*/
|
|
28
30
|
constructor(input: string, tokenMap?: Record<string, RegExp>);
|
|
29
|
-
parseError(str: string):
|
|
31
|
+
parseError(str: string): TinyParseError;
|
|
30
32
|
peek(): TinyToken;
|
|
31
33
|
private getNext;
|
|
32
34
|
/**
|
|
@@ -36,6 +38,8 @@ export declare class TinyParser {
|
|
|
36
38
|
* @returns The last token read
|
|
37
39
|
*/
|
|
38
40
|
next(...types: string[]): TinyToken;
|
|
41
|
+
nextText(...texts: string[]): TinyToken;
|
|
39
42
|
skipTo(type: string): void;
|
|
43
|
+
dump(): TinyToken[];
|
|
40
44
|
private tokenize;
|
|
41
45
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.TinyParser = void 0;
|
|
9
|
+
exports.TinyParser = exports.TinyParseError = void 0;
|
|
10
10
|
/**
|
|
11
11
|
* Simple framework for writing schema parsers. The parsers using this felt
|
|
12
12
|
* better than the more ad-hoc code they replaced, and are smaller than
|
|
@@ -14,6 +14,9 @@ exports.TinyParser = void 0;
|
|
|
14
14
|
*
|
|
15
15
|
* NOTE: All parse errors are exceptions.
|
|
16
16
|
*/
|
|
17
|
+
class TinyParseError extends Error {
|
|
18
|
+
}
|
|
19
|
+
exports.TinyParseError = TinyParseError;
|
|
17
20
|
class TinyParser {
|
|
18
21
|
/**
|
|
19
22
|
* The token map is tested in order. Return TinyToken
|
|
@@ -28,19 +31,19 @@ class TinyParser {
|
|
|
28
31
|
constructor(input, tokenMap) {
|
|
29
32
|
this.input = input;
|
|
30
33
|
this.parseCursor = 0;
|
|
31
|
-
this.tokens = this.tokenize(input);
|
|
32
34
|
this.tokenMap = tokenMap !== null && tokenMap !== void 0 ? tokenMap : {
|
|
33
35
|
space: /^\s+/,
|
|
34
36
|
char: /^[,:[\]()-]/,
|
|
35
37
|
id: /^\w+/,
|
|
36
38
|
qstr: /^"\w+"/,
|
|
37
39
|
};
|
|
40
|
+
this.tokens = this.tokenize(input);
|
|
38
41
|
}
|
|
39
42
|
parseError(str) {
|
|
40
43
|
const errText = `INTERNAL ERROR parsing schema: ${str}\n` +
|
|
41
44
|
`${this.input}\n` +
|
|
42
45
|
`${' '.repeat(this.parseCursor)}^`;
|
|
43
|
-
return new
|
|
46
|
+
return new TinyParseError(errText);
|
|
44
47
|
}
|
|
45
48
|
peek() {
|
|
46
49
|
if (this.lookAhead) {
|
|
@@ -80,7 +83,24 @@ class TinyParser {
|
|
|
80
83
|
}
|
|
81
84
|
if (next)
|
|
82
85
|
return next;
|
|
83
|
-
throw this.parseError(`Expected ${expected}`);
|
|
86
|
+
throw this.parseError(`Expected token type '${expected}'`);
|
|
87
|
+
}
|
|
88
|
+
nextText(...texts) {
|
|
89
|
+
if (texts.length === 0)
|
|
90
|
+
return this.getNext();
|
|
91
|
+
let next = undefined;
|
|
92
|
+
let expected = texts[0];
|
|
93
|
+
for (const txt of texts) {
|
|
94
|
+
next = this.getNext();
|
|
95
|
+
expected = txt;
|
|
96
|
+
if (next.text !== txt) {
|
|
97
|
+
next = undefined;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (next)
|
|
102
|
+
return next;
|
|
103
|
+
throw this.parseError(`Expected '${expected}'`);
|
|
84
104
|
}
|
|
85
105
|
skipTo(type) {
|
|
86
106
|
for (;;) {
|
|
@@ -93,6 +113,12 @@ class TinyParser {
|
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
115
|
}
|
|
116
|
+
dump() {
|
|
117
|
+
const p = this.parseCursor;
|
|
118
|
+
const parts = [...this.tokenize(this.input)];
|
|
119
|
+
this.parseCursor = p;
|
|
120
|
+
return parts;
|
|
121
|
+
}
|
|
96
122
|
*tokenize(src) {
|
|
97
123
|
const tokenList = this.tokenMap;
|
|
98
124
|
while (this.parseCursor < src.length) {
|
|
@@ -112,6 +138,7 @@ class TinyParser {
|
|
|
112
138
|
type: tokenType === 'char' ? tokenText : tokenType,
|
|
113
139
|
text: tokenText,
|
|
114
140
|
};
|
|
141
|
+
break;
|
|
115
142
|
}
|
|
116
143
|
}
|
|
117
144
|
}
|