Skip to content

Commit 01b7333

Browse files
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 26a921d commit 01b7333

File tree

8 files changed

+249
-286
lines changed

8 files changed

+249
-286
lines changed

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

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

1515
const list_extract: DefinitionBlueprint = {
@@ -102,6 +102,6 @@ export const DUCKDB_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
102102
string_agg_distinct,
103103
to_seconds,
104104
date_part,
105-
...wrapDef('repeat', {'str': 'string', 'n': 'number'}, 'string'),
106-
...wrapDef('reverse', {'str': 'string'}, 'string'),
105+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
106+
...def('reverse', {'str': 'string'}, 'string'),
107107
};

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/util.ts

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -724,45 +724,6 @@ export function expandOverrideMapFromBase(
724724
return map;
725725
}
726726

727-
/**
728-
* Shortcut for functions wrapper definitions. Useful only if ....
729-
* 0) Not an overloaded definition
730-
* 1) The function has the same name in malloy and in the dialect
731-
* 2) Every generic in args or return generics is type ['any']
732-
* USAGE:
733-
*
734-
* ...wrapDef('func_name', {'arg0': 'type0', 'arg1': 'type1'}, 'return-type')
735-
*
736-
* @param name name of function
737-
* @param takes Record<Argument blueprint>
738-
* @param returns Return Blueprint
739-
* @returns dot dot dot able blueprint definition
740-
*/
741-
export function wrapDef(
742-
name: string,
743-
takes: Record<string, TypeDescBlueprint>,
744-
returns: TypeDescBlueprint
745-
): DefinitionBlueprintMap {
746-
let anyGenerics = false;
747-
const generic: {[name: string]: TypeDescElementBlueprintOrNamedGeneric[]} =
748-
{};
749-
for (const argType of Object.values(takes)) {
750-
for (const genericRef of findGenerics(argType)) {
751-
generic[genericRef.generic] = ['any'];
752-
anyGenerics = true;
753-
}
754-
}
755-
const newDef: DefinitionBlueprint = {
756-
takes,
757-
returns,
758-
impl: {function: name.toUpperCase()},
759-
};
760-
if (anyGenerics) {
761-
newDef.generic = generic;
762-
}
763-
return {[name]: newDef};
764-
}
765-
766727
/**
767728
* Walks a type and returns all the generic references
768729
* @param tdbp A type
@@ -795,3 +756,55 @@ function* findGenerics(
795756
}
796757
}
797758
}
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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import {
99
DefinitionBlueprintMap,
1010
OverloadedDefinitionBlueprint,
11-
wrapDef,
11+
def,
1212
} from '../functions/util';
1313

1414
const string_agg: OverloadedDefinitionBlueprint = {
@@ -53,6 +53,6 @@ const string_agg_distinct: OverloadedDefinitionBlueprint = {
5353
export const MYSQL_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
5454
string_agg,
5555
string_agg_distinct,
56-
...wrapDef('repeat', {'str': 'string', 'n': 'number'}, 'string'),
57-
...wrapDef('reverse', {'str': 'string'}, 'string'),
56+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
57+
...def('reverse', {'str': 'string'}, 'string'),
5858
};

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import {
99
DefinitionBlueprintMap,
1010
OverloadedDefinitionBlueprint,
11-
wrapDef,
11+
def,
1212
} from '../functions/util';
1313

1414
const string_agg: OverloadedDefinitionBlueprint = {
@@ -53,6 +53,6 @@ const string_agg_distinct: OverloadedDefinitionBlueprint = {
5353
export const POSTGRES_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
5454
string_agg,
5555
string_agg_distinct,
56-
...wrapDef('repeat', {'str': 'string', 'n': 'number'}, 'string'),
57-
...wrapDef('reverse', {'str': 'string'}, 'string'),
56+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
57+
...def('reverse', {'str': 'string'}, 'string'),
5858
};

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

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

88
import {AggregateOrderByNode} from '../../model';
99
import {
10-
wrapDef,
10+
def,
1111
DefinitionBlueprintMap,
1212
OverloadedDefinitionBlueprint,
1313
arg as a,
@@ -62,6 +62,6 @@ const string_agg_distinct: OverloadedDefinitionBlueprint = {
6262
export const SNOWFLAKE_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
6363
string_agg,
6464
string_agg_distinct,
65-
...wrapDef('repeat', {'str': 'string', 'n': 'number'}, 'string'),
66-
...wrapDef('reverse', {'str': 'string'}, 'string'),
65+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
66+
...def('reverse', {'str': 'string'}, 'string'),
6767
};

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

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

88
import {
9-
wrapDef,
9+
def,
1010
DefinitionBlueprint,
1111
DefinitionBlueprintMap,
1212
OverloadedDefinitionBlueprint,
@@ -69,6 +69,6 @@ export const STANDARDSQL_DIALECT_FUNCTIONS: DefinitionBlueprintMap = {
6969
date_from_unix_date,
7070
string_agg,
7171
string_agg_distinct,
72-
...wrapDef('repeat', {'str': 'string', 'n': 'number'}, 'string'),
73-
...wrapDef('reverse', {'str': 'string'}, 'string'),
72+
...def('repeat', {'str': 'string', 'n': 'number'}, 'string'),
73+
...def('reverse', {'str': 'string'}, 'string'),
7474
};

0 commit comments

Comments
 (0)