Skip to content

Commit c31fd85

Browse files
More cleanup of array functions (#2058)
* make wrapDef a util * move repeat out of standard library * cleanup wrapDef a little more * even more wrapDef cleanup * fix oopsie * maybe cleaner still? * fix find/replace mistake * rename arg * make all param names into strings * fix trino reverse * moved todos to docs issue * remove extra test for % operator * put the cast back into trino reverse * go ahead and test reverse for arrays on presto * try again to get reverse with generic correct * shared string_reverse * maybe finally fix reverse? * use defwrap to cleanup some errors * another minor code tweak to wrapDef * -cleverness, +readability * add comment * comment edit * comment explaining dialect split * fix T comment * Extend wrapDef to def and overdef (#2060) * experiment with overdef * fix overdef() * mark bitwise_aggs as symmetric * move all the hll functiosn to ... def * make preto-only all def() * delete overdef * stop using def for hll functions * fix presto funcs * cleanup sequence()
1 parent 74801a0 commit c31fd85

File tree

12 files changed

+356
-433
lines changed

12 files changed

+356
-433
lines changed

packages/malloy/src/dialect/duckdb/dialect_functions.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,9 @@ import {
99
DefinitionBlueprint,
1010
DefinitionBlueprintMap,
1111
OverloadedDefinitionBlueprint,
12+
def,
1213
} from '../functions/util';
1314

14-
const repeat: DefinitionBlueprint = {
15-
takes: {'str': 'string', 'n': 'number'},
16-
returns: 'string',
17-
impl: {function: 'REPEAT'},
18-
};
19-
2015
const list_extract: DefinitionBlueprint = {
2116
takes: {'value': {array: {generic: 'T'}}, 'index': 'number'},
2217
generic: {'T': ['any']},
@@ -103,9 +98,10 @@ export const DUCKDB_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
10398
count_approx,
10499
dayname,
105100
to_timestamp,
106-
repeat,
107101
string_agg,
108102
string_agg_distinct,
109103
to_seconds,
110104
date_part,
105+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
106+
...def('reverse', {'str': 'string'}, 'string'),
111107
};

packages/malloy/src/dialect/functions/README.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ const abs: DefinitionFor<Standard['my_function']> = {
3131
* `${...param}` is expanded to a spread fragment, which is expanded to a comma-separated list of arguments for a variadic parameter
3232
* `${order_by:}` and `${limit:}` expand to `aggregate_order_by` and `aggregate_limit` fragments respectively
3333
* `{ expr: { node: "node_type", ... } }`, for cases where the `function` or `sql` options are insufficiently flexible; generates SQL for an arbitrary node, which may include parameter fragments, spread fragments (optionally with prefix and suffix), and order by or limit fragments.
34-
* `generic` specifies that the overload is generic over some types; it should be an array `['T', types]`, where `'T'` is the name of the generic, and `types` is an array of Malloy types: e.g. `generic: ['T', ['string', 'number', 'timestamp', 'date', 'json']],`. Note that currently we only allow one generic per overload, since that's all we need right now. Under the hood, this creates multiple overloads, one for each type in the generic. Eventually we may make the function system smarter and understand generics more deeply.
34+
* `generic` specifies that the overload is generic over some types; it should be an object `{'T': types}`, where `'T'` is the name of the generic, and `types` is an array of Malloy types: e.g. `generic: {'T', ['string', 'number', 'timestamp', 'date', 'json']},`. Note that currently we only allow one generic per overload, since that's all we need right now. Under the hood, this creates multiple overloads, one for each type in the generic. Eventually we may make the function system smarter and understand generics more deeply.
3535
* `supportsOrderBy` is for aggregate functions (e.g. `string_agg`) which can accept an `{ order_by: value }`. `false` is the default value. `true` indicates that any column may be used in the `order_by`. A value of `'default_only'` means that only the more limited `{ order_by: asc }` syntax is allowed.
3636
* `supportsLimit`: is for aggregate functions (e.g. `string_agg`) which can accept a `{ limit: 10 }`. Default `false`.
3737
* `isSymmetric` is for aggregate functions to indicate whether they are symmetric. Default `false`.
3838

3939
A function with multiple overloads is defined like:
4040

41-
```
41+
```TypeScript
4242
const concat: DefinitionFor<Standard['concat']> = {
4343
'empty': {
4444
takes: {},
@@ -55,7 +55,7 @@ const concat: DefinitionFor<Standard['concat']> = {
5555

5656
Each dialect may override the standard implementations (that is, the `impl` part only). To do so:
5757

58-
```
58+
```TypeScript
5959
import {MalloyStandardFunctionImplementations as OverrideMap} from '../functions/malloy_standard_functions';
6060

6161
export const DIALECT_OVERRIDES: OverrideMap = {
@@ -106,6 +106,35 @@ export const DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
106106
};
107107
```
108108

109+
### The `def()` experiment
110+
There is also an experimental shortcut for simple wrapper definitions where the name of the function in SQL and in Malloy is the same, and the definition is not overloaded. Here's an example if using `def` to define the string length function ...
111+
112+
Instead of writing
113+
114+
```ts
115+
const length: DefinitionBluePrint = {
116+
takes: {str: 'string'},
117+
returns: 'number',
118+
impl: {function: 'LENGTH'},
119+
}
120+
export const DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
121+
my_function,
122+
length
123+
}
124+
```
125+
126+
The shortcut looks like this ...
127+
128+
```ts
129+
export const DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
130+
my_function,
131+
...def('length', {str: 'string'}, 'number')
132+
};
133+
```
134+
135+
We are waiting on user feedback on this experiment. While these are simpler to write, they are not simpler to read for a human scanning the file for the definition of a function.
136+
137+
### Dialect Implementation
109138
The `Dialect` implementation then implements `getDialectFunctions`. You should use `expandBlueprintMap` to expand the function blueprints into `DialectFunctionOverloadDef`s.
110139

111140
```ts

packages/malloy/src/dialect/functions/malloy_standard_functions.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ type Standard = {
6060
regexp_extract: D;
6161
string_repeat: D;
6262
replace: {string: D; regular_expression: D};
63-
reverse: D;
6463
round: {to_integer: D; to_precision: D};
6564
rtrim: {whitespace: D; characters: D};
6665
sign: D;
@@ -339,12 +338,6 @@ const replace: DefinitionFor<Standard['replace']> = {
339338
},
340339
};
341340

342-
const reverse: DefinitionFor<Standard['reverse']> = {
343-
takes: {'value': 'string'},
344-
returns: 'string',
345-
impl: {function: 'REVERSE'},
346-
};
347-
348341
const round: DefinitionFor<Standard['round']> = {
349342
'to_integer': {
350343
takes: {'value': 'number'},
@@ -718,7 +711,6 @@ export const MALLOY_STANDARD_FUNCTIONS: MalloyStandardFunctionDefinitions = {
718711
rand,
719712
regexp_extract,
720713
replace,
721-
reverse,
722714
round,
723715
rtrim,
724716
sign,

packages/malloy/src/dialect/functions/util.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,3 +723,88 @@ export function expandOverrideMapFromBase(
723723
}
724724
return map;
725725
}
726+
727+
/**
728+
* Walks a type and returns all the generic references
729+
* @param tdbp A type
730+
*/
731+
function* findGenerics(
732+
tdbp: TypeDescBlueprint
733+
): IterableIterator<NamedGeneric> {
734+
if (typeof tdbp !== 'string') {
735+
if ('generic' in tdbp) {
736+
yield tdbp;
737+
} else if ('record' in tdbp) {
738+
for (const recType of Object.values(tdbp.record)) {
739+
yield* findGenerics(recType);
740+
}
741+
} else {
742+
for (const leaflet of [
743+
'array',
744+
'literal',
745+
'measure',
746+
'dimension',
747+
'measure',
748+
'constant',
749+
'cacluation',
750+
]) {
751+
if (leaflet in tdbp) {
752+
yield* findGenerics(tdbp[leaflet]);
753+
return;
754+
}
755+
}
756+
}
757+
}
758+
}
759+
760+
/**
761+
* Shortcut for non overloaded functions definitions. Default implementation
762+
* will be the function name turned to upper case. Default type for
763+
* any generics encountered will be `['any']`. Both of these can be over-ridden
764+
* in the `options` parameter.
765+
*
766+
* The two implict defaults (which can be over-ridden) are that the
767+
* impl: will be the upper case version of the function name, and that
768+
* any generic reference will be of type 'any'.
769+
*
770+
* USAGE:
771+
*
772+
* ...def('func_name', {'arg0': 'type0', 'arg1': 'type1'}, 'return-type')
773+
*
774+
* @param name name of function
775+
* @param takes Record<Argument blueprint>
776+
* @param returns Return Blueprint
777+
* @param options Everything from a `DefinitionBlueprint` except `takes` and `returns`
778+
* @returns dot dot dot able blueprint definition
779+
*/
780+
export function def(
781+
name: string,
782+
takes: Record<string, TypeDescBlueprint>,
783+
returns: TypeDescBlueprint,
784+
options: Partial<Omit<DefinitionBlueprint, 'takes' | 'returns'>> = {}
785+
) {
786+
let anyGenerics = false;
787+
const generic: {[name: string]: TypeDescElementBlueprintOrNamedGeneric[]} =
788+
{};
789+
for (const argType of Object.values(takes)) {
790+
for (const genericRef of findGenerics(argType)) {
791+
generic[genericRef.generic] = ['any'];
792+
anyGenerics = true;
793+
}
794+
}
795+
// We have found all the generic references and given them all
796+
// T: ['any']. Use this as a default if the options section
797+
// doesn't provide types for the generics.
798+
if (anyGenerics) {
799+
if (options.generic === undefined) {
800+
options.generic = generic;
801+
}
802+
}
803+
const newDef: DefinitionBlueprint = {
804+
takes,
805+
returns,
806+
impl: {function: name.toUpperCase()},
807+
...options,
808+
};
809+
return {[name]: newDef};
810+
}

packages/malloy/src/dialect/mysql/dialect_functions.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,11 @@
66
*/
77

88
import {
9-
DefinitionBlueprint,
109
DefinitionBlueprintMap,
1110
OverloadedDefinitionBlueprint,
11+
def,
1212
} from '../functions/util';
1313

14-
const repeat: DefinitionBlueprint = {
15-
takes: {'str': 'string', 'n': 'number'},
16-
returns: 'string',
17-
impl: {function: 'REPEAT'},
18-
};
19-
2014
const string_agg: OverloadedDefinitionBlueprint = {
2115
default_separator: {
2216
takes: {'value': {dimension: 'string'}},
@@ -59,5 +53,6 @@ const string_agg_distinct: OverloadedDefinitionBlueprint = {
5953
export const MYSQL_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
6054
string_agg,
6155
string_agg_distinct,
62-
repeat,
56+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
57+
...def('reverse', {'str': 'string'}, 'string'),
6358
};

packages/malloy/src/dialect/postgres/dialect_functions.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,11 @@
66
*/
77

88
import {
9-
DefinitionBlueprint,
109
DefinitionBlueprintMap,
1110
OverloadedDefinitionBlueprint,
11+
def,
1212
} from '../functions/util';
1313

14-
const repeat: DefinitionBlueprint = {
15-
takes: {'str': 'string', 'n': 'number'},
16-
returns: 'string',
17-
impl: {function: 'REPEAT'},
18-
};
19-
2014
const string_agg: OverloadedDefinitionBlueprint = {
2115
default_separator: {
2216
takes: {'value': {dimension: 'string'}},
@@ -59,5 +53,6 @@ const string_agg_distinct: OverloadedDefinitionBlueprint = {
5953
export const POSTGRES_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
6054
string_agg,
6155
string_agg_distinct,
62-
repeat,
56+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
57+
...def('reverse', {'str': 'string'}, 'string'),
6358
};

packages/malloy/src/dialect/snowflake/dialect_functions.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,13 @@
77

88
import {AggregateOrderByNode} from '../../model';
99
import {
10-
DefinitionBlueprint,
10+
def,
1111
DefinitionBlueprintMap,
1212
OverloadedDefinitionBlueprint,
1313
arg as a,
1414
sql,
1515
} from '../functions/util';
1616

17-
const repeat: DefinitionBlueprint = {
18-
takes: {'str': 'string', 'n': 'number'},
19-
returns: 'string',
20-
impl: {function: 'REPEAT'},
21-
};
22-
2317
const order_by: AggregateOrderByNode = {
2418
node: 'aggregate_order_by',
2519
prefix: ' WITHIN GROUP(',
@@ -68,5 +62,6 @@ const string_agg_distinct: OverloadedDefinitionBlueprint = {
6862
export const SNOWFLAKE_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
6963
string_agg,
7064
string_agg_distinct,
71-
repeat,
65+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
66+
...def('reverse', {'str': 'string'}, 'string'),
7267
};

packages/malloy/src/dialect/standardsql/dialect_functions.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,12 @@
66
*/
77

88
import {
9+
def,
910
DefinitionBlueprint,
1011
DefinitionBlueprintMap,
1112
OverloadedDefinitionBlueprint,
1213
} from '../functions/util';
1314

14-
const repeat: DefinitionBlueprint = {
15-
takes: {'str': 'string', 'n': 'number'},
16-
returns: 'string',
17-
impl: {function: 'REPEAT'},
18-
};
19-
2015
const date_from_unix_date: DefinitionBlueprint = {
2116
takes: {'unix_date': 'number'},
2217
returns: 'date',
@@ -74,5 +69,6 @@ export const STANDARDSQL_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
7469
date_from_unix_date,
7570
string_agg,
7671
string_agg_distinct,
77-
repeat,
72+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
73+
...def('reverse', {'str': 'string'}, 'string'),
7874
};

0 commit comments

Comments
 (0)