diff --git a/test/src/databases/all/db_index.spec.ts b/test/src/databases/all/db_index.spec.ts index 97eaa72b55..4bd426f251 100644 --- a/test/src/databases/all/db_index.spec.ts +++ b/test/src/databases/all/db_index.spec.ts @@ -37,11 +37,8 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => { test.when(runtime.dialect.supportsTempTables)( `basic index - ${databaseName}`, async () => { - const model = await runtime.loadModel( - ` - source: airports is ${databaseName}.table('malloytest.airports') extend { - } - ` + const model = runtime.loadModel( + `source: airports is ${databaseName}.table('malloytest.airports')` ); let result = await model.search('airports', 'SANTA', 10); diff --git a/test/src/databases/all/join.spec.ts b/test/src/databases/all/join.spec.ts index 6ee64b89a8..3da7c2ce21 100644 --- a/test/src/databases/all/join.spec.ts +++ b/test/src/databases/all/join.spec.ts @@ -24,6 +24,7 @@ /* eslint-disable no-console */ import {RuntimeList, allDatabases} from '../../runtimes'; +import {TestSelect} from '../../test-select'; import {databasesFromEnvironmentOr} from '../../util'; import '../../util/db-jest-matchers'; @@ -318,4 +319,80 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => { `).matchesRows(runtime, {pick_a1: [1]}); } ); + + // Issue 2486, tried to resolve, but could not arrrive at a solution + // which works on all dialects. + test('2486 -- join on joined column', async () => { + await expect(` + source: usr is ${databaseName}.sql("""select 1 as id, 'email' as email""") + source: res is ${databaseName}.sql("""select 1 as id, 1 as user_id""") extend { + join_one: usr is usr on usr.id = user_id + // dimension: usr_email is usr.email + } + source: msg is ${databaseName}.sql("""select 1 as id, 'email' as msg_email""") extend { + join_many: res is res on msg_email = res.usr.email + } + run: msg -> { + select: *, res.usr.email + } + `).malloyResultMatches(runtime, { + id: 1, + usr_email: 'email', + }); + }); + + function randomBase36Id(): string { + // Generate two random 32-bit integers + const high = Math.floor(Math.random() * 0x100000000); // 2^32 + const low = Math.floor(Math.random() * 0x100000000); + + // Combine into a 64-bit BigInt + const random64 = (BigInt(high) << BigInt(32)) | BigInt(low); + + // Convert to base36 and pad to 13 characters + return random64.toString(36).padStart(13, '0'); + } + + test('join through repeated record problem', async () => { + const ts = new TestSelect(runtime.dialect); + const events = ts.generate( + { + event_name: 'page_view', + event_params: [ + {key: 'page_location', value: '/home'}, + {key: 'page_title', value: 'Home Page'}, + ], + }, + { + event_name: 'user_engagement', + event_params: [ + {key: 'engagement_time', value: '1000'}, + {key: 'page_view', value: 'true'}, + ], + }, + {event_name: 'scroll', event_params: [{key: 'percent', value: '50'}]} + ); + let eventTable: string; + if (databaseName === 'duckdb') { + const tableName = `event_${randomBase36Id()}`; + await runtime.connection.runSQL( + `CREATE TEMPORARY TABLE ${tableName} AS ${events}` + ); + eventTable = `${databaseName}.table('${tableName}')`; + } else { + eventTable = `${databaseName}.sql("""${events}""")`; + } + await expect(` + source: ga4_s1 is ${eventTable} extend { dimension: event_param is event_params.key } + source: ga4_s2 is ${eventTable} extend { join_one: ga4_j1 is ga4_s1 on event_name = ga4_j1.event_param } + run: ga4_s2 -> { + select: event_name, ga4_event is ga4_j1.event_name + where: ga4_j1.event_param is not null + } + `).malloyResultMatches(runtime, { + event_name: 'page_view', + ga4_event: 'user_engagement', + cnt: 1, + }); + }); }); diff --git a/test/src/test-select.ts b/test/src/test-select.ts index fbc3cfac5a..9695e738e7 100644 --- a/test/src/test-select.ts +++ b/test/src/test-select.ts @@ -544,59 +544,3 @@ export class TestSelect { ); } } - -/* -Example usage: - -const testSelect = new TestSelect(dialect); - -const userSQL = testSelect.generate([ - { - id: testSelect.mk_int(1), - name: "bob", - email: "bob@example.com", - score: testSelect.mk_float(85.5), - is_active: testSelect.mk_bool(true), - created_at: testSelect.mk_timestamp('2024-01-15 14:30:00'), - signup_date: testSelect.mk_date('2024-01-15'), - tags: ["admin", "user"], // Inferred as array - settings: { // Inferred as record - theme: "dark", - notifications: true - } - }, - { - id: testSelect.mk_int(2), - name: "alice", - email: testSelect.mk_string(null), // Typed NULL - score: testSelect.mk_float(92.0), - is_active: testSelect.mk_bool(false), - created_at: testSelect.mk_timestamp('2024-01-16 09:00:00'), - signup_date: testSelect.mk_date('2024-01-16'), - tags: ["user"], - settings: { - theme: "light", - notifications: false - } - } -]); - -const orderSQL = testSelect.generate([ - { - id: 1, // Inferred as integer - user_id: testSelect.mk_int(1), - amount: 99.99, // Inferred as float - status: "pending", - items: [ - {sku: "ABC", qty: 2, price: 10.00}, - {sku: "DEF", qty: 1, price: 20.00} - ] - } -]); - -// Use in Malloy: -const malloySource = ` - source: users is duckdb.sql("""${userSQL}""") - source: orders is duckdb.sql("""${orderSQL}""") -`; -*/