From 61d8d746fdc2072615b7d7780a14d6a3b531d1b7 Mon Sep 17 00:00:00 2001 From: Christopher Swenson Date: Mon, 29 Jul 2024 19:09:24 -0500 Subject: [PATCH] WIP --- .../src/lang/ast/field-space/dynamic-space.ts | 20 ++- .../src/lang/ast/field-space/query-spaces.ts | 2 + .../lang/ast/query-elements/query-arrow.ts | 12 +- .../ast/query-elements/query-head-struct.ts | 5 +- .../src/lang/ast/query-elements/query-raw.ts | 10 +- .../lang/ast/source-elements/named-source.ts | 120 +++++++++++------- .../lang/ast/source-elements/query-source.ts | 4 + .../src/lang/ast/source-elements/source.ts | 8 +- .../lang/ast/source-elements/sql-source.ts | 8 +- .../malloy/src/lang/test/parameters.spec.ts | 52 +++++++- packages/malloy/src/model/malloy_query.ts | 75 ++++++----- packages/malloy/src/model/malloy_types.ts | 6 + test/src/databases/all/parameters.spec.ts | 50 +++++++- 13 files changed, 265 insertions(+), 107 deletions(-) diff --git a/packages/malloy/src/lang/ast/field-space/dynamic-space.ts b/packages/malloy/src/lang/ast/field-space/dynamic-space.ts index e6a10cf0e..ea95895f1 100644 --- a/packages/malloy/src/lang/ast/field-space/dynamic-space.ts +++ b/packages/malloy/src/lang/ast/field-space/dynamic-space.ts @@ -99,11 +99,23 @@ export abstract class DynamicSpace extends StaticSpace { } structDef(): model.StructDef { - const parameters = this.fromStruct.parameters || {}; if (this.final === undefined) { + + // TODO this is kinda weird: we grab all the parameters so that we can + // populate the "final" structDef with parameters immediately so that views + // (or nested views) can see them when they are compiling and need to know + // the struct def of the source while it is still being defined. + const parameters = {}; + for (const [name, entry] of this.entries()) { + if (entry instanceof SpaceParam) { + parameters[name] = entry.parameter(); + } + } + this.final = { ...this.fromStruct, fields: [], + parameters, }; // Need to process the entities in specific order const fields: [string, SpaceField][] = []; @@ -117,8 +129,6 @@ export abstract class DynamicSpace extends StaticSpace { turtles.push([name, spaceEntry]); } else if (spaceEntry instanceof SpaceField) { fields.push([name, spaceEntry]); - } else if (spaceEntry instanceof SpaceParam) { - parameters[name] = spaceEntry.parameter(); } } const reorderFields = [...fields, ...joins, ...turtles]; @@ -142,9 +152,7 @@ export abstract class DynamicSpace extends StaticSpace { // } } } - if (Object.entries(parameters).length > 0) { - this.final.parameters = parameters; - } + // If we have join expressions, we need to now go back and fill them in for (const [join, missingOn] of fixupJoins) { join.fixupJoinOn(this, missingOn); diff --git a/packages/malloy/src/lang/ast/field-space/query-spaces.ts b/packages/malloy/src/lang/ast/field-space/query-spaces.ts index fe8f23def..7f3627569 100644 --- a/packages/malloy/src/lang/ast/field-space/query-spaces.ts +++ b/packages/malloy/src/lang/ast/field-space/query-spaces.ts @@ -56,6 +56,8 @@ export abstract class QueryOperationSpace readonly astEl: MalloyElement ) { super(queryInputSpace.emptyStructDef()); + + // TODO I don't understand at all how QueryInputSpace gets its map? this.exprSpace = new QueryInputSpace(queryInputSpace, this); if (refineThis) this.addRefineFromFields(refineThis); } diff --git a/packages/malloy/src/lang/ast/query-elements/query-arrow.ts b/packages/malloy/src/lang/ast/query-elements/query-arrow.ts index b789d4a74..be57add3d 100644 --- a/packages/malloy/src/lang/ast/query-elements/query-arrow.ts +++ b/packages/malloy/src/lang/ast/query-elements/query-arrow.ts @@ -56,17 +56,17 @@ export class QueryArrow extends QueryBase implements QueryElement { if (this.source instanceof Source) { // We create a fresh query with either the QOPDesc as the head, // the view as the head, or the scalar as the head (if scalar lenses is enabled) - const structRef = isRefOk - ? this.source.structRef(parameterSpace) - : this.source.structDef(parameterSpace); + const invoked = isRefOk + ? this.source.structRef(parameterSpace) + : {structRef: this.source.structDef(parameterSpace)}; queryBase = { type: 'query', - structRef, + ...invoked, pipeline: [], location: this.location, }; - inputStruct = refIsStructDef(structRef) - ? structRef + inputStruct = refIsStructDef(invoked.structRef) + ? invoked.structRef : this.source.structDef(parameterSpace); fieldSpace = new StaticSpace(inputStruct); } else { diff --git a/packages/malloy/src/lang/ast/query-elements/query-head-struct.ts b/packages/malloy/src/lang/ast/query-elements/query-head-struct.ts index c29a38a2e..faad1b5f3 100644 --- a/packages/malloy/src/lang/ast/query-elements/query-head-struct.ts +++ b/packages/malloy/src/lang/ast/query-elements/query-head-struct.ts @@ -22,6 +22,7 @@ */ import { + InvokedStructRef, StructDef, StructRef, refIsStructDef, @@ -37,8 +38,8 @@ export class QueryHeadStruct extends Source { super(); } - structRef(): StructRef { - return this.fromRef; + structRef(): InvokedStructRef { + return {structRef: this.fromRef}; } structDef(parameterSpace: ParameterSpace | undefined): StructDef { diff --git a/packages/malloy/src/lang/ast/query-elements/query-raw.ts b/packages/malloy/src/lang/ast/query-elements/query-raw.ts index 8bbbf2574..98ef5405e 100644 --- a/packages/malloy/src/lang/ast/query-elements/query-raw.ts +++ b/packages/malloy/src/lang/ast/query-elements/query-raw.ts @@ -47,16 +47,16 @@ export class QueryRaw extends MalloyElement implements QueryElement { parameterSpace: ParameterSpace | undefined, isRefOk: boolean ): QueryComp { - const structRef = isRefOk + const invoked = isRefOk ? this.source.structRef(parameterSpace) - : this.source.structDef(parameterSpace); - const structDef = refIsStructDef(structRef) - ? structRef + : {structRef: this.source.structDef(parameterSpace)}; + const structDef = refIsStructDef(invoked.structRef) + ? invoked.structRef : this.source.structDef(parameterSpace); return { query: { type: 'query', - structRef, + ...invoked, pipeline: [{type: 'raw', fields: []}], location: this.location, }, diff --git a/packages/malloy/src/lang/ast/source-elements/named-source.ts b/packages/malloy/src/lang/ast/source-elements/named-source.ts index 736d9e9c3..0fc0a27cf 100644 --- a/packages/malloy/src/lang/ast/source-elements/named-source.ts +++ b/packages/malloy/src/lang/ast/source-elements/named-source.ts @@ -23,12 +23,13 @@ */ import { + InvokedStructRef, isCastType, isSQLBlockStruct, isValueParameter, + Parameter, paramHasValue, StructDef, - StructRef, } from '../../../model/malloy_types'; import {Source} from './source'; @@ -61,19 +62,18 @@ export class NamedSource extends Source { return this.ref instanceof ModelEntryReference ? this.ref.name : this.ref; } - structRef(parameterSpace: ParameterSpace | undefined): StructRef { - if (this.args !== undefined) { - return this.structDef(parameterSpace); - } + structRef(parameterSpace: ParameterSpace | undefined): InvokedStructRef { const modelEnt = this.modelEntry(this.ref); - // TODO right now we just expand the structRef if it has any parameters; but we should really - // evaluate arguments separately and return them here, to be included in the query; - if (modelEnt && (!modelEnt.exported || modelEnt.entry.type === 'struct' && modelEnt.entry.parameters)) { - // If we are not exporting the referenced structdef, don't - // use the reference - return this.structDef(parameterSpace); + // If we are not exporting the referenced structdef, don't use the reference + if (modelEnt && !modelEnt.exported) { + return { + structRef: this.structDef(parameterSpace) + }; } - return this.refName; + return { + structRef: this.refName, + sourceArguments: this.evaluateArgumentsForRef(parameterSpace), + }; } refLog(message: string, severity?: LogSeverity) { @@ -110,42 +110,22 @@ export class NamedSource extends Source { return {...entry}; } - structDef(parameterSpace: ParameterSpace | undefined): StructDef { - return this.withParameters(parameterSpace, []); - } - - withParameters( + private evaluateArgumentsForRef( parameterSpace: ParameterSpace | undefined, - pList: HasParameter[] | undefined - ): StructDef { - /* - Can't really generate the callback list until after all the - things before me are translated, and that kinda screws up - the translation process, so that might be a better place - to start the next step, because how that gets done might - make any code I write which ignores the translation problem - kind of meaningless. - - Maybe the output of a translation is something which describes - all the missing data, and then there is a "link" step where you - can do other translations and link them into a partial translation - which might result in a full translation. - */ - + ): Record { const base = this.modelStruct(); - if (!base) { - const notFound = ErrorFactory.structDef; - const err = `${this.refName}-undefined`; - notFound.name = notFound.name + err; - notFound.dialect = notFound.dialect + err; - return notFound; - } - // Clone parameters to not mutate - const parameters = {}; - for (const paramName in base.parameters) { - parameters[paramName] = {...base.parameters[paramName]}; + if (base === undefined) { + return {}; } + return this.evaluateArguments(parameterSpace, base.parameters, []); + } + + private evaluateArguments( + parameterSpace: ParameterSpace | undefined, + parametersIn: Record | undefined, + parametersOut: HasParameter[] | undefined + ): Record { const outArguments = {}; const passedNames = new Set(); for (const argument of this.args ?? []) { @@ -166,14 +146,14 @@ export class NamedSource extends Source { continue; } passedNames.add(name); - const parameter = parameters[name]; + const parameter = (parametersIn ?? {})[name]; if (!parameter) { id.log( `\`${this.refName}\` has no declared parameter named \`${id.refString}\`` ); } else { if (isValueParameter(parameter)) { - const paramSpace = parameterSpace ?? new ParameterSpace(pList ?? []); + const paramSpace = parameterSpace ?? new ParameterSpace(parametersOut ?? []); const pVal = argument.value.getExpression(paramSpace); let value = pVal.value; if (pVal.dataType !== parameter.type && isCastType(parameter.type)) { @@ -189,10 +169,11 @@ export class NamedSource extends Source { } } - for (const paramName in parameters) { + for (const paramName in parametersIn) { if (!(paramName in outArguments)) { - if (paramHasValue(parameters[paramName])) { - outArguments[paramName] = parameters[paramName]; + if (paramHasValue(parametersIn[paramName])) { + // TODO probably should not do this, and just look in the referenced source to get the default parameter values. + outArguments[paramName] = {...parametersIn[paramName]}; } else { this.refLog( `Argument not provided for required parameter \`${paramName}\`` @@ -201,11 +182,52 @@ export class NamedSource extends Source { } } + return outArguments; + } + + structDef(parameterSpace: ParameterSpace | undefined): StructDef { + return this.withParameters(parameterSpace, []); + } + + withParameters( + parameterSpace: ParameterSpace | undefined, + pList: HasParameter[] | undefined + ): StructDef { + /* + Can't really generate the callback list until after all the + things before me are translated, and that kinda screws up + the translation process, so that might be a better place + to start the next step, because how that gets done might + make any code I write which ignores the translation problem + kind of meaningless. + + Maybe the output of a translation is something which describes + all the missing data, and then there is a "link" step where you + can do other translations and link them into a partial translation + which might result in a full translation. + */ + + const base = this.modelStruct(); + if (!base) { + const notFound = ErrorFactory.structDef; + const err = `${this.refName}-undefined`; + notFound.name = notFound.name + err; + notFound.dialect = notFound.dialect + err; + return notFound; + } + const outParameters = {}; for (const parameter of pList ?? []) { const compiled = parameter.parameter(); outParameters[compiled.name] = compiled; } + + const outArguments = this.evaluateArguments( + parameterSpace, + base.parameters, + pList + ); + const ret = {...base, parameters: outParameters, arguments: outArguments}; this.document()?.rememberToAddModelAnnotations(ret); return ret; diff --git a/packages/malloy/src/lang/ast/source-elements/query-source.ts b/packages/malloy/src/lang/ast/source-elements/query-source.ts index 55435658f..c3dec2150 100644 --- a/packages/malloy/src/lang/ast/source-elements/query-source.ts +++ b/packages/malloy/src/lang/ast/source-elements/query-source.ts @@ -38,6 +38,10 @@ export class QuerySource extends Source { ...comp.outputStruct, structSource: {type: 'query', query: comp.query} as StructSource, }; + // TODO test this + if (comp.query.sourceArguments !== undefined) { + queryStruct.arguments = comp.query.sourceArguments; + } this.document()?.rememberToAddModelAnnotations(queryStruct); return queryStruct; } diff --git a/packages/malloy/src/lang/ast/source-elements/source.ts b/packages/malloy/src/lang/ast/source-elements/source.ts index f7292483d..14767d31a 100644 --- a/packages/malloy/src/lang/ast/source-elements/source.ts +++ b/packages/malloy/src/lang/ast/source-elements/source.ts @@ -21,7 +21,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {StructDef, StructRef} from '../../../model/malloy_types'; +import {InvokedStructRef, StructDef} from '../../../model/malloy_types'; import {MalloyElement} from '../types/malloy-element'; import {HasParameter} from '../parameters/has-parameter'; import {ParameterSpace} from '../field-space/parameter-space'; @@ -33,8 +33,10 @@ import {ParameterSpace} from '../field-space/parameter-space'; export abstract class Source extends MalloyElement { abstract structDef(parameterSpace: ParameterSpace | undefined): StructDef; - structRef(parameterSpace: ParameterSpace | undefined): StructRef { - return this.structDef(parameterSpace); + structRef(parameterSpace: ParameterSpace | undefined): InvokedStructRef { + return { + structRef: this.structDef(parameterSpace) + }; } withParameters( diff --git a/packages/malloy/src/lang/ast/source-elements/sql-source.ts b/packages/malloy/src/lang/ast/source-elements/sql-source.ts index 393c2d0f3..bc767e69e 100644 --- a/packages/malloy/src/lang/ast/source-elements/sql-source.ts +++ b/packages/malloy/src/lang/ast/source-elements/sql-source.ts @@ -23,8 +23,8 @@ import { StructDef, - StructRef, SQLBlockSource, + InvokedStructRef, } from '../../../model/malloy_types'; import {makeSQLBlock} from '../../../model/sql_block'; import {NeedCompileSQL} from '../../translate-response'; @@ -54,8 +54,10 @@ export class SQLSource extends Source { return this.requestBlock; } - structRef(): StructRef { - return this.structDef(); + structRef(): InvokedStructRef { + return { + structRef: this.structDef() + }; } validateConnectionName(): boolean { diff --git a/packages/malloy/src/lang/test/parameters.spec.ts b/packages/malloy/src/lang/test/parameters.spec.ts index bdccda553..e19dfce18 100644 --- a/packages/malloy/src/lang/test/parameters.spec.ts +++ b/packages/malloy/src/lang/test/parameters.spec.ts @@ -102,7 +102,7 @@ describe('parameters', () => { source: ab_new_new(param::number) is ab_new(param) `).toTranslate(); }); - test('can use declared parameter', () => { + test('can use declared parameter in dimension', () => { expect(` ##! experimental.parameters source: ab_new(param::number) is ab extend { @@ -110,6 +110,36 @@ describe('parameters', () => { } `).toTranslate(); }); + test('can use declared parameter in nest extending other', () => { + expect(` + ##! experimental.parameters + source: ab_new(param::number is 10) is ab extend { + dimension: p1 is param + view: my_view is { + group_by: p2 is param + nest: nested is { + group_by: p3 is param + } + } + } + run: ab_new -> my_view + `).toTranslate(); + }); + test('can use declared parameter in nest with table', () => { + expect(` + ##! experimental.parameters + source: ab_new(param::number is 10) is _db_.table('aTable') extend { + dimension: p1 is param + view: my_view is { + group_by: p2 is param + nest: nested is { + group_by: p3 is param + } + } + } + run: ab_new -> my_view + `).toTranslate(); + }); test('can pass argument for param', () => { expect(` ##! experimental.parameters @@ -154,6 +184,13 @@ describe('parameters', () => { run: ab_new(param is 1) -> { select: p is ${'param'} } `).translationToFailWith("'param' is not defined"); }); + test('cannot reference param in query against source', () => { + expect(markSource` + ##! experimental.parameters + source: ab_new(param::number) is ab + run: ab_new(param is 1) -> { select: ${'param'} } + `).translationToFailWith("'param' is not defined"); + }); test('cannot reference param in source extension', () => { expect(markSource` ##! experimental.parameters @@ -374,7 +411,6 @@ describe('parameters', () => { "Experimental flag 'parameters' required to enable this feature" ); }); - // TODO either detect circularity or make parameters constant only test('parameters cannot reference themselves', () => { expect( markSource` @@ -479,4 +515,16 @@ describe('parameters', () => { ` ).translationToFailWith('Only constants allowed in parameter default values'); }); + test.skip('can use param in multi-stage query', () => { + expect(` + ##! experimental.parameters + source: ab_new(param::number) is ab extend { + view: q is { + select: * + } -> { + group_by: x is param + } + } + `).toTranslate(); + }); }); diff --git a/packages/malloy/src/model/malloy_query.ts b/packages/malloy/src/model/malloy_query.ts index b2bd534e2..95aefdb2f 100644 --- a/packages/malloy/src/model/malloy_query.ts +++ b/packages/malloy/src/model/malloy_query.ts @@ -749,22 +749,22 @@ class QueryField extends QueryNode { throw new Error('Parameter paths must be length 1'); } const name = expr.path[0]; - const param = context.parameters()[name]; - if (isValueParameter(param)) { - if (param.value) { + const argument = context.arguments()[name]; + if (isValueParameter(argument)) { + if (argument.value) { return this.generateExpressionFromExpr( resultSet, context, - param.value, + argument.value, state ); } - } else if (param.condition) { + } else if (argument.condition) { // TODO remove? return this.generateExpressionFromExpr( resultSet, context, - param.condition, + argument.condition, state ); } @@ -2090,7 +2090,7 @@ class QueryTurtle extends QueryField {} */ export class Segment { static nextStructDef(structDef: StructDef, segment: PipeSegment): StructDef { - const qs = new QueryStruct(structDef, { + const qs = new QueryStruct(structDef, undefined, { model: new QueryModel(undefined), }); const turtleDef: TurtleDef = { @@ -2174,6 +2174,7 @@ class QueryQuery extends QueryField { ...sourceDef, fields: [...sourceDef.fields, ...firstStage.extendSource], }, + parentStruct.sourceArguments, parent.parent ? {struct: parent} : {model: parent.model} ); turtleWithFilters = { @@ -3678,7 +3679,7 @@ class QueryQuery extends QueryField { sourceSQLExpression ); structDef.structSource = {type: 'sql', method: 'nested'}; - const qs = new QueryStruct(structDef, { + const qs = new QueryStruct(structDef, undefined, { model: this.parent.getModel(), }); const q = QueryQuery.makeQuery( @@ -3750,7 +3751,7 @@ class QueryQuery extends QueryField { }; pipeline.shift(); for (const transform of pipeline) { - const s = new QueryStruct(structDef, { + const s = new QueryStruct(structDef, undefined, { model: this.parent.getModel(), }); const q = QueryQuery.makeQuery( @@ -4101,6 +4102,7 @@ class QueryStruct extends QueryNode { constructor( fieldDef: StructDef, + readonly sourceArguments: Record | undefined, parent: ParentQueryStruct | ParentQueryModel ) { super(fieldDef); @@ -4144,7 +4146,7 @@ class QueryStruct extends QueryNode { ); } // TODO naming of "arguments" vs "parameters" is confusing here - const resolved1 = this.parent.parameters()[frag.path[0]]; + const resolved1 = this.parent.arguments()[frag.path[0]]; const resolved2 = this.parent.resolveParentParameterReferences(resolved1); if ( @@ -4164,25 +4166,36 @@ class QueryStruct extends QueryNode { } } - private resolvedParameters: Record | undefined = undefined; - parameters(): Record { - if (this.resolvedParameters !== undefined) { - return this.resolvedParameters; - } - this.resolvedParameters = {}; - // First, copy over all parameters, to get default values - const params = this.fieldDef.parameters ?? {}; - for (const parameterName in params) { - this.resolvedParameters[parameterName] = params[parameterName]; + /** + * TEST THIS + * source: foo(param::string) .... { + * + * + * join: bar is something(param) -> { ... } + * } + * + * + */ + + private resolvedArguments: Record | undefined = undefined; + arguments(): Record { + if (this.resolvedArguments !== undefined) { + return this.resolvedArguments; } + this.resolvedArguments = {}; + // First, copy over all parameters, to get default values + const params = this.fieldDef.parameters ?? {}; + for (const parameterName in params) { + this.resolvedArguments[parameterName] = params[parameterName]; + } // Then, copy over arguments to override default values - const args = this.fieldDef.arguments ?? {}; + const args = this.sourceArguments ?? this.fieldDef.arguments ?? {}; for (const parameterName in args) { const orig = args[parameterName]; - this.resolvedParameters[parameterName] = + this.resolvedArguments[parameterName] = this.resolveParentParameterReferences(orig); } - return this.resolvedParameters; + return this.resolvedArguments; } addFieldsFromFieldList(fields: FieldDef[]) { @@ -4193,7 +4206,7 @@ class QueryStruct extends QueryNode { case 'struct': { this.addFieldToNameMap( as, - new QueryStruct(field as StructDef, { + new QueryStruct(field as StructDef, undefined, { struct: this, }) ); @@ -4630,7 +4643,7 @@ export class QueryModel { for (const s of Object.values(this.modelDef.contents)) { let qs; if (s.type === 'struct') { - qs = new QueryStruct(s, {model: this}); + qs = new QueryStruct(s, undefined, {model: this}); this.structs.set(getIdentifier(s), qs); qs.resolveQueryFields(); } else if (s.type === 'query') { @@ -4650,16 +4663,20 @@ export class QueryModel { } } - getStructFromRef(structRef: StructRef): QueryStruct { + getStructFromRef(structRef: StructRef, sourceArguments: Record | undefined): QueryStruct { let structDef; if (typeof structRef === 'string') { - return this.getStructByName(structRef); + const ret = this.getStructByName(structRef); + if (sourceArguments !== undefined) { + return new QueryStruct(ret.fieldDef, sourceArguments, ret.parent ?? {model: this}); + } + return ret; } else if (structRef.type === 'struct') { structDef = structRef; } else { throw new Error('Broken for now'); } - return new QueryStruct(structDef, {model: this}); + return new QueryStruct(structDef, sourceArguments, {model: this}); } loadQuery( @@ -4683,7 +4700,7 @@ export class QueryModel { const q = QueryQuery.makeQuery( turtleDef, - this.getStructFromRef(query.structRef), + this.getStructFromRef(query.structRef, query.sourceArguments), stageWriter, isJoinedSubquery ); diff --git a/packages/malloy/src/model/malloy_types.ts b/packages/malloy/src/model/malloy_types.ts index d14647940..e0c32977d 100644 --- a/packages/malloy/src/model/malloy_types.ts +++ b/packages/malloy/src/model/malloy_types.ts @@ -781,6 +781,11 @@ export function refIsStructDef(ref: StructRef): ref is StructDef { return typeof ref !== 'string' && ref.type === 'struct'; } +export type InvokedStructRef = { + structRef: StructRef; + sourceArguments?: Record; +}; + /** join pattern structs is a struct. */ export interface JoinedStruct { structRef: StructRef; @@ -806,6 +811,7 @@ export interface Query extends Pipeline, Filtered, HasLocation { type?: 'query'; name?: string; structRef: StructRef; + sourceArguments?: Record; // TODO "Argument" type? annotation?: Annotation; modelAnnotation?: Annotation; } diff --git a/test/src/databases/all/parameters.spec.ts b/test/src/databases/all/parameters.spec.ts index b28f0476c..db6853368 100644 --- a/test/src/databases/all/parameters.spec.ts +++ b/test/src/databases/all/parameters.spec.ts @@ -9,8 +9,8 @@ import {RuntimeList, allDatabases} from '../../runtimes'; import {databasesFromEnvironmentOr} from '../../util'; import '../../util/db-jest-matchers'; -const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases)); -// const runtimes = new RuntimeList(databasesFromEnvironmentOr(['duckdb'])); +// const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases)); +const runtimes = new RuntimeList(databasesFromEnvironmentOr(['duckdb'])); afterAll(async () => { await runtimes.closeAll(); @@ -172,4 +172,50 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => { ` ).malloyResultMatches(runtime, {param_value: 11}); }); + it(`use parameter in nested view - ${databaseName}`, async () => { + await expect( + ` + ##! experimental.parameters + source: ab_new(param::number is 10) is ${databaseName}.table('malloytest.state_facts') extend { + dimension: param_value_1 is param + view: v is { + group_by: param_value_1 + group_by: param_value_2 is param + nest: n is { + group_by: param_value_1 + group_by: param_value_3 is param + } + } + } + run: ab_new -> v + ` + ).malloyResultMatches(runtime, {param_value_1: 10, param_value_2: 10, 'n.param_value_1': 10, 'n.param_value_3': 10}); + }); + // TODO aparently, the `state_facts(state_filter) -> { select: * }` query in malloy_query + // does not have its parent set to `state_facts2`, so the parameter cannot be resolved + it.skip(`can pass param into joined source from query - ${databaseName}`, async () => { + await expect(` + ##! experimental.parameters + source: state_facts( + state_filter::string + ) is ${databaseName}.table('malloytest.state_facts') extend { + where: state = state_filter + } + + source: state_facts2( + state_filter::string + ) is ${databaseName}.table('malloytest.state_facts') extend { + where: state = state_filter + + join_many: state_facts is (state_facts(state_filter) -> { select: * }) on 1 = 1 + } + + run: state_facts2(state_filter is "CA") -> { + group_by: + s1 is state, + s2 is state_facts.state + aggregate: c is count() + } + `).malloyResultMatches(runtime, {s1: 'CA', s2: 'CA', c: 1}); + }); });