diff --git a/meerkat-browser/src/browser-cube-to-sql-with-resolution/browser-cube-to-sql-with-resolution.ts b/meerkat-browser/src/browser-cube-to-sql-with-resolution/browser-cube-to-sql-with-resolution.ts index 431d0dcb..f7549f95 100644 --- a/meerkat-browser/src/browser-cube-to-sql-with-resolution/browser-cube-to-sql-with-resolution.ts +++ b/meerkat-browser/src/browser-cube-to-sql-with-resolution/browser-cube-to-sql-with-resolution.ts @@ -20,6 +20,14 @@ import { import { AsyncDuckDBConnection } from '@duckdb/duckdb-wasm'; import { cubeQueryToSQL } from '../browser-cube-to-sql/browser-cube-to-sql'; +const getQueryOutput = async ( + query: string, + connection: AsyncDuckDBConnection +): Promise[]> => { + const queryOutput = await connection.query(query); + return queryOutput.toArray().map((row) => row.toJSON() as Record); +}; + export interface CubeQueryToSQLWithResolutionParams { connection: AsyncDuckDBConnection; query: Query; @@ -165,8 +173,10 @@ export const cubeQueryToSQLWithResolution = async ({ }); // Wrap with row_id ordering and exclusion - return wrapWithRowIdOrderingAndExclusion( + const finalSql = wrapWithRowIdOrderingAndExclusion( sqlWithAliases, ROW_ID_DIMENSION_NAME ); + + return finalSql; }; diff --git a/meerkat-core/src/resolution/generators/__tests__/generate-resolution-schemas.spec.ts b/meerkat-core/src/resolution/generators/__tests__/generate-resolution-schemas.spec.ts index 58d32e89..9e2ee9fb 100644 --- a/meerkat-core/src/resolution/generators/__tests__/generate-resolution-schemas.spec.ts +++ b/meerkat-core/src/resolution/generators/__tests__/generate-resolution-schemas.spec.ts @@ -257,7 +257,9 @@ describe('generate-resolution-schemas', () => { const result = generateResolutionSchemas(config); - expect(result[0].sql).toBe('SELECT * FROM customers WHERE active = true'); + expect(result[0].sql).toBe( + 'SELECT orders__customer_id.id, orders__customer_id.name FROM (SELECT * FROM customers WHERE active = true) AS orders__customer_id' + ); }); it('should have empty measures array in resolution schema', () => { diff --git a/meerkat-core/src/resolution/generators/generate-resolution-schemas.ts b/meerkat-core/src/resolution/generators/generate-resolution-schemas.ts index 2032cd28..30cc5144 100644 --- a/meerkat-core/src/resolution/generators/generate-resolution-schemas.ts +++ b/meerkat-core/src/resolution/generators/generate-resolution-schemas.ts @@ -3,6 +3,25 @@ import { TableSchema } from '../../types/cube-types/table'; import { findInDimensionSchemas } from '../../utils/find-in-table-schema'; import { ResolutionConfig } from '../types'; +/** + * Narrows a lookup table's SQL to project only the columns required by the + * resolution join (`joinColumn` + `resolutionColumns`). This prevents + * unrelated columns (e.g. `devrev_copy`) from leaking through the `SELECT *` + * wrapper that `cubeQueryToSQL` adds, which would cause "Ambiguous reference" + * errors when multiple lookup tables share a raw column name (ISS-301213). + */ +const buildNarrowedLookupSql = ( + baseSql: string, + schemaName: string, + neededColumns: string[] +): string => { + const uniqueCols = [...new Set(neededColumns)]; + const projection = uniqueCols + .map((col) => `${schemaName}.${col}`) + .join(', '); + return `SELECT ${projection} FROM (${baseSql}) AS ${schemaName}`; +}; + export const generateResolutionSchemas = (config: ResolutionConfig) => { const resolutionSchemas: TableSchema[] = []; config.columnConfigs.forEach((colConfig) => { @@ -15,12 +34,19 @@ export const generateResolutionSchemas = (config: ResolutionConfig) => { const baseName = memberKeyToSafeKey(colConfig.name); + const neededColumns = [colConfig.joinColumn, ...colConfig.resolutionColumns]; + const narrowedSql = buildNarrowedLookupSql( + tableSchema.sql, + baseName, + neededColumns + ); + // For each column that needs to be resolved, create a copy of the relevant table schema. // We use the name of the column in the base query as the table schema name // to avoid conflicts. const resolutionSchema: TableSchema = { name: baseName, - sql: tableSchema.sql, + sql: narrowedSql, measures: [], dimensions: colConfig.resolutionColumns.map((col) => { const dimension = findInDimensionSchemas( diff --git a/meerkat-core/src/resolution/resolution.spec.ts b/meerkat-core/src/resolution/resolution.spec.ts index 4bb3cd65..5b1e3694 100644 --- a/meerkat-core/src/resolution/resolution.spec.ts +++ b/meerkat-core/src/resolution/resolution.spec.ts @@ -349,7 +349,7 @@ describe('Generate resolution schemas', () => { expect(schemas).toEqual([ { name: 'base_table__column1', - sql: '', + sql: 'SELECT base_table__column1.id, base_table__column1.display_id FROM () AS base_table__column1', measures: [], dimensions: [ { @@ -362,7 +362,7 @@ describe('Generate resolution schemas', () => { }, { name: 'base_table__column2', - sql: '', + sql: 'SELECT base_table__column2.id, base_table__column2.display_name FROM () AS base_table__column2', measures: [], dimensions: [ { @@ -505,7 +505,7 @@ describe('Generate resolution schemas', () => { expect(schemas).toEqual([ { name: 'base_table__column1', - sql: '', + sql: 'SELECT base_table__column1.id, base_table__column1.display_id FROM () AS base_table__column1', measures: [], dimensions: [ { @@ -573,7 +573,7 @@ describe('Generate resolution schemas', () => { expect(schemas).toEqual([ { name: 'base_table__column1', - sql: '', + sql: 'SELECT base_table__column1.id, base_table__column1.display_id FROM () AS base_table__column1', measures: [], dimensions: [ { diff --git a/meerkat-node/src/__tests__/resolution-shared-column-ambiguity.spec.ts b/meerkat-node/src/__tests__/resolution-shared-column-ambiguity.spec.ts new file mode 100644 index 00000000..d9e2cac7 --- /dev/null +++ b/meerkat-node/src/__tests__/resolution-shared-column-ambiguity.spec.ts @@ -0,0 +1,185 @@ +import { Query, ResolutionConfig, TableSchema } from '@devrev/meerkat-core'; +import { cubeQueryToSQLWithResolution } from '../cube-to-sql-with-resolution/cube-to-sql-with-resolution'; +import { duckdbExec } from '../duckdb-exec'; + +/** + * Regression test for ISS-301213. + * + * CSV export from a dashboard widget fails with + * `Binder Error: Ambiguous reference to column name ""` + * when the widget's SQL resolves two (or more) scalar lookup columns whose + * source lookup tables share a column name beyond the one being resolved. + * + * Real-world shape (from the Razorpay HAR on TKT-65015): a `dim_ticket` base + * query left-joins lookup tables `dim_devu` (owner) and `dim_group`, both of + * which carry an unrelated column `devrev_copy`. The export wrapper produced a + * trailing bare `*` that re-expanded BOTH lookup tables' columns into the same + * scope, so `devrev_copy` became ambiguous and DuckDB's binder rejected the + * COPY statement. + * + * The shared column is NOT projected and NOT resolved — it is pure passthrough + * dragged up by the `*`. The fix narrows each lookup table's SQL to only SELECT + * the columns needed for the join (joinColumn + resolutionColumns), so unrelated + * columns like `devrev_copy` never enter the JOIN scope. + */ + +const CREATE_BASE_TABLE = `CREATE OR REPLACE TABLE iss301213_tickets ( + id INTEGER, + owner_id VARCHAR, + group_id VARCHAR +)`; + +const BASE_DATA = `INSERT INTO iss301213_tickets VALUES +(1, 'owner1', 'group1'), +(2, 'owner2', 'group2'), +(3, 'owner1', 'group2')`; + +// Owner lookup carries an extra `devrev_copy` column that is never resolved. +const CREATE_OWNER_LOOKUP = `CREATE OR REPLACE TABLE iss301213_owner_lookup ( + id VARCHAR, + fullname VARCHAR, + devrev_copy VARCHAR +)`; + +const OWNER_LOOKUP_DATA = `INSERT INTO iss301213_owner_lookup VALUES +('owner1', 'Alice', 'copy-o1'), +('owner2', 'Bob', 'copy-o2')`; + +// Group lookup ALSO carries `devrev_copy` — this is the shared column that +// becomes ambiguous when both lookups are joined and re-exposed via `*`. +const CREATE_GROUP_LOOKUP = `CREATE OR REPLACE TABLE iss301213_group_lookup ( + id VARCHAR, + name VARCHAR, + devrev_copy VARCHAR +)`; + +const GROUP_LOOKUP_DATA = `INSERT INTO iss301213_group_lookup VALUES +('group1', 'Group One', 'copy-g1'), +('group2', 'Group Two', 'copy-g2')`; + +const BASE_SCHEMA: TableSchema = { + name: 'iss301213_tickets', + sql: 'select * from iss301213_tickets', + measures: [{ name: 'count', sql: 'COUNT(*)', type: 'number' }], + dimensions: [ + { alias: 'ID', name: 'id', sql: 'id', type: 'number' }, + { alias: 'Owner', name: 'owner_id', sql: 'owner_id', type: 'string' }, + { alias: 'Group', name: 'group_id', sql: 'group_id', type: 'string' }, + ], +}; + +const OWNER_LOOKUP_SCHEMA: TableSchema = { + name: 'iss301213_owner_lookup', + sql: 'select * from iss301213_owner_lookup', + measures: [], + dimensions: [ + { alias: 'ID', name: 'id', sql: 'id', type: 'string' }, + { alias: 'Full Name', name: 'fullname', sql: 'fullname', type: 'string' }, + { + alias: 'Devrev Copy', + name: 'devrev_copy', + sql: 'devrev_copy', + type: 'string', + }, + ], +}; + +const GROUP_LOOKUP_SCHEMA: TableSchema = { + name: 'iss301213_group_lookup', + sql: 'select * from iss301213_group_lookup', + measures: [], + dimensions: [ + { alias: 'ID', name: 'id', sql: 'id', type: 'string' }, + { alias: 'Name', name: 'name', sql: 'name', type: 'string' }, + { + alias: 'Devrev Copy', + name: 'devrev_copy', + sql: 'devrev_copy', + type: 'string', + }, + ], +}; + +describe('ISS-301213 - shared lookup column ambiguity on CSV export', () => { + beforeAll(async () => { + await duckdbExec(CREATE_BASE_TABLE); + await duckdbExec(BASE_DATA); + await duckdbExec(CREATE_OWNER_LOOKUP); + await duckdbExec(OWNER_LOOKUP_DATA); + await duckdbExec(CREATE_GROUP_LOOKUP); + await duckdbExec(GROUP_LOOKUP_DATA); + }); + + afterAll(async () => { + await duckdbExec('DROP TABLE IF EXISTS iss301213_tickets'); + await duckdbExec('DROP TABLE IF EXISTS iss301213_owner_lookup'); + await duckdbExec('DROP TABLE IF EXISTS iss301213_group_lookup'); + }); + + const query: Query = { + measures: ['iss301213_tickets.count'], + dimensions: [ + 'iss301213_tickets.id', + 'iss301213_tickets.owner_id', + 'iss301213_tickets.group_id', + ], + order: { 'iss301213_tickets.id': 'asc' }, + }; + + const resolutionConfig: ResolutionConfig = { + columnConfigs: [ + { + name: 'iss301213_tickets.owner_id', + type: 'string' as const, + source: 'iss301213_owner_lookup', + joinColumn: 'id', + resolutionColumns: ['fullname'], + }, + { + name: 'iss301213_tickets.group_id', + type: 'string' as const, + source: 'iss301213_group_lookup', + joinColumn: 'id', + resolutionColumns: ['name'], + }, + ], + tableSchemas: [OWNER_LOOKUP_SCHEMA, GROUP_LOOKUP_SCHEMA], + }; + + const columnProjections = [ + 'iss301213_tickets.id', + 'iss301213_tickets.owner_id', + 'iss301213_tickets.group_id', + 'iss301213_tickets.count', + ]; + + it('generates SQL that COPYs to CSV without a Binder ambiguity error', async () => { + const sql = await cubeQueryToSQLWithResolution({ + query, + tableSchemas: [BASE_SCHEMA], + resolutionConfig, + columnProjections, + }); + + // The narrowed lookup SQL ensures `devrev_copy` never enters the JOIN scope, + // so bare `*` over joins is harmless — it only expands to needed columns. + + // The export must still execute and preserve resolved values + row count. + const csvPath = '/tmp/test_iss301213_shared_column.csv'; + await expect( + duckdbExec(`COPY (${sql}) TO '${csvPath}' (HEADER, DELIMITER ',')`) + ).resolves.not.toThrow(); + + const result = (await duckdbExec( + `SELECT * FROM read_csv_auto('${csvPath}')` + )) as any[]; + + expect(result.length).toBe(3); + expect(Number(result[0].ID)).toBe(1); + expect(result[0]).toHaveProperty('Owner'); + expect(result[0]).toHaveProperty('Group'); + // Resolved scalar values land under the original aliases. + expect(result[0]['Owner']).toBe('Alice'); + expect(result[0]['Group']).toBe('Group One'); + }); +}); diff --git a/meerkat-node/src/__tests__/resolution.spec.ts b/meerkat-node/src/__tests__/resolution.spec.ts index f4deff76..2389c9f8 100644 --- a/meerkat-node/src/__tests__/resolution.spec.ts +++ b/meerkat-node/src/__tests__/resolution.spec.ts @@ -281,26 +281,7 @@ describe('Resolution Tests', () => { }); console.info(`SQL: `, sql); const expectedSQL = ` - select * exclude(__row_id) - from (SELECT "__row_id", - "Part ID 1", - "Random Column", - "Work ID - Display ID", - "Work ID - Title", - "Part ID 2" - FROM (SELECT __base_query."__row_id" AS "__row_id", - __base_query."base_table__part_id_1__display_id" AS "Part ID 1", - __base_query."base_table__random_column" AS "Random Column", - __base_query."base_table__work_id__display_id" AS "Work ID - Display ID", - __base_query."base_table__work_id__title" AS "Work ID - Title", - __base_query."base_table__part_id_2__display_id" AS "Part ID 2", * - FROM (SELECT MAX(__base_query."base_table__part_id_1__display_id") AS "base_table__part_id_1__display_id" , - MAX(__base_query."base_table__random_column") AS "base_table__random_column" , - MAX(__base_query."base_table__work_id__display_id") AS "base_table__work_id__display_id" , - MAX(__base_query."base_table__work_id__title") AS "base_table__work_id__title" , - MAX(__base_query."base_table__part_id_2__display_id") AS "base_table__part_id_2__display_id" , - "__row_id" - FROM (SELECT __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1__display_id", "base_table__random_column", "base_table__work_id__display_id", "base_table__work_id__title", "base_table__part_id_2__display_id", "__row_id" FROM (SELECT __base_query."base_table__random_column" AS "base_table__random_column", __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1", "base_table__random_column", "base_table__work_id", "base_table__part_id_2", "__row_id" FROM (SELECT __base_query.base_table__part_id_1 AS "base_table__part_id_1", __base_query.base_table__random_column AS "base_table__random_column", __base_query.base_table__work_id AS "base_table__work_id", __base_query.base_table__part_id_2 AS "base_table__part_id_2", row_number() OVER () AS "__row_id", * FROM (SELECT base_table__part_id_1, base_table__random_column, base_table__work_id, base_table__part_id_2 FROM (SELECT base_table.part_id_1 AS base_table__part_id_1, base_table.random_column AS base_table__random_column, base_table.work_id AS base_table__work_id, base_table.part_id_2 AS base_table__part_id_2, * FROM (select * from base_table) AS base_table) AS base_table) AS __base_query) AS __base_query) AS __base_query LEFT JOIN (SELECT base_table__part_id_1.display_id AS "base_table__part_id_1__display_id", * FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_1) AS base_table__part_id_1 ON __base_query.base_table__part_id_1 = base_table__part_id_1.id LEFT JOIN (SELECT base_table__work_id.display_id AS "base_table__work_id__display_id", base_table__work_id.title AS "base_table__work_id__title", * FROM (select id, display_id, title from system.dim_issue) AS base_table__work_id) AS base_table__work_id ON __base_query.base_table__work_id = base_table__work_id.id LEFT JOIN (SELECT base_table__part_id_2.display_id AS "base_table__part_id_2__display_id", * FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_2) AS base_table__part_id_2 ON __base_query.base_table__part_id_2 = base_table__part_id_2.id) AS MEERKAT_GENERATED_TABLE) AS __base_query) AS __base_query GROUP BY __row_id) AS __base_query) AS __base_query) order by __row_id + select * exclude(__row_id) from (SELECT "__row_id", "Part ID 1", "Random Column", "Work ID - Display ID", "Work ID - Title", "Part ID 2" FROM (SELECT __base_query."__row_id" AS "__row_id", __base_query."base_table__part_id_1__display_id" AS "Part ID 1", __base_query."base_table__random_column" AS "Random Column", __base_query."base_table__work_id__display_id" AS "Work ID - Display ID", __base_query."base_table__work_id__title" AS "Work ID - Title", __base_query."base_table__part_id_2__display_id" AS "Part ID 2", * FROM (SELECT MAX(__base_query."base_table__part_id_1__display_id") AS "base_table__part_id_1__display_id" , MAX(__base_query."base_table__random_column") AS "base_table__random_column" , MAX(__base_query."base_table__work_id__display_id") AS "base_table__work_id__display_id" , MAX(__base_query."base_table__work_id__title") AS "base_table__work_id__title" , MAX(__base_query."base_table__part_id_2__display_id") AS "base_table__part_id_2__display_id" , "__row_id" FROM (SELECT __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1__display_id", "base_table__random_column", "base_table__work_id__display_id", "base_table__work_id__title", "base_table__part_id_2__display_id", "__row_id" FROM (SELECT __base_query."base_table__random_column" AS "base_table__random_column", __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1", "base_table__random_column", "base_table__work_id", "base_table__part_id_2", "__row_id" FROM (SELECT __base_query.base_table__part_id_1 AS "base_table__part_id_1", __base_query.base_table__random_column AS "base_table__random_column", __base_query.base_table__work_id AS "base_table__work_id", __base_query.base_table__part_id_2 AS "base_table__part_id_2", row_number() OVER () AS "__row_id", * FROM (SELECT base_table__part_id_1, base_table__random_column, base_table__work_id, base_table__part_id_2 FROM (SELECT base_table.part_id_1 AS base_table__part_id_1, base_table.random_column AS base_table__random_column, base_table.work_id AS base_table__work_id, base_table.part_id_2 AS base_table__part_id_2, * FROM (select * from base_table) AS base_table) AS base_table) AS __base_query) AS __base_query) AS __base_query LEFT JOIN (SELECT base_table__part_id_1.display_id AS "base_table__part_id_1__display_id", * FROM (SELECT base_table__part_id_1.id, base_table__part_id_1.display_id FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_1) AS base_table__part_id_1) AS base_table__part_id_1 ON __base_query.base_table__part_id_1 = base_table__part_id_1.id LEFT JOIN (SELECT base_table__work_id.display_id AS "base_table__work_id__display_id", base_table__work_id.title AS "base_table__work_id__title", * FROM (SELECT base_table__work_id.id, base_table__work_id.display_id, base_table__work_id.title FROM (select id, display_id, title from system.dim_issue) AS base_table__work_id) AS base_table__work_id) AS base_table__work_id ON __base_query.base_table__work_id = base_table__work_id.id LEFT JOIN (SELECT base_table__part_id_2.display_id AS "base_table__part_id_2__display_id", * FROM (SELECT base_table__part_id_2.id, base_table__part_id_2.display_id FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_2) AS base_table__part_id_2) AS base_table__part_id_2 ON __base_query.base_table__part_id_2 = base_table__part_id_2.id) AS MEERKAT_GENERATED_TABLE) AS __base_query) AS __base_query GROUP BY __row_id) AS __base_query) AS __base_query) order by __row_id `; expect(sql.replace(/\s+/g, ' ').trim()).toBe( expectedSQL.replace(/\s+/g, ' ').trim() @@ -330,7 +311,7 @@ describe('Resolution Tests', () => { }); console.info(`SQL: `, sql); const expectedSQL = ` - select * exclude(__row_id) from (SELECT "__row_id", "Part ID 1", "Count" FROM (SELECT __base_query."__row_id" AS "__row_id", __base_query."base_table__part_id_1__display_id" AS "Part ID 1", __base_query."base_table__count" AS "Count", * FROM (SELECT MAX(__base_query."base_table__part_id_1__display_id") AS "base_table__part_id_1__display_id" , MAX(__base_query."base_table__count") AS "base_table__count" , "__row_id" FROM (SELECT __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1__display_id", "base_table__count", "__row_id" FROM (SELECT __base_query."base_table__count" AS "base_table__count", __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1", "base_table__count", "__row_id" FROM (SELECT __base_query.base_table__part_id_1 AS "base_table__part_id_1", __base_query.base_table__count AS "base_table__count", row_number() OVER () AS "__row_id", * FROM (SELECT count(*) AS base_table__count , base_table__part_id_1 FROM (SELECT base_table.part_id_1 AS base_table__part_id_1, * FROM (select * from base_table) AS base_table) AS base_table GROUP BY base_table__part_id_1) AS __base_query) AS __base_query) AS __base_query LEFT JOIN (SELECT base_table__part_id_1.display_id AS "base_table__part_id_1__display_id", * FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_1) AS base_table__part_id_1 ON __base_query.base_table__part_id_1 = base_table__part_id_1.id) AS MEERKAT_GENERATED_TABLE) AS __base_query) AS __base_query GROUP BY __row_id) AS __base_query) AS __base_query) order by __row_id + select * exclude(__row_id) from (SELECT "__row_id", "Part ID 1", "Count" FROM (SELECT __base_query."__row_id" AS "__row_id", __base_query."base_table__part_id_1__display_id" AS "Part ID 1", __base_query."base_table__count" AS "Count", * FROM (SELECT MAX(__base_query."base_table__part_id_1__display_id") AS "base_table__part_id_1__display_id" , MAX(__base_query."base_table__count") AS "base_table__count" , "__row_id" FROM (SELECT __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1__display_id", "base_table__count", "__row_id" FROM (SELECT __base_query."base_table__count" AS "base_table__count", __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1", "base_table__count", "__row_id" FROM (SELECT __base_query.base_table__part_id_1 AS "base_table__part_id_1", __base_query.base_table__count AS "base_table__count", row_number() OVER () AS "__row_id", * FROM (SELECT count(*) AS base_table__count , base_table__part_id_1 FROM (SELECT base_table.part_id_1 AS base_table__part_id_1, * FROM (select * from base_table) AS base_table) AS base_table GROUP BY base_table__part_id_1) AS __base_query) AS __base_query) AS __base_query LEFT JOIN (SELECT base_table__part_id_1.display_id AS "base_table__part_id_1__display_id", * FROM (SELECT base_table__part_id_1.id, base_table__part_id_1.display_id FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_1) AS base_table__part_id_1) AS base_table__part_id_1 ON __base_query.base_table__part_id_1 = base_table__part_id_1.id) AS MEERKAT_GENERATED_TABLE) AS __base_query) AS __base_query GROUP BY __row_id) AS __base_query) AS __base_query) order by __row_id `; expect(sql.replace(/\s+/g, ' ').trim()).toBe( expectedSQL.replace(/\s+/g, ' ').trim() @@ -372,7 +353,7 @@ describe('Resolution Tests', () => { }); console.info(`SQL: `, sql); const expectedSQL = ` - select * exclude(__row_id) from (SELECT "__row_id", "Part ID 1", "Random Column", "Part ID 2" FROM (SELECT __base_query."__row_id" AS "__row_id", __base_query."base_table__part_id_1__display_id" AS "Part ID 1", __base_query."base_table__random_column" AS "Random Column", __base_query."base_table__part_id_2__display_id" AS "Part ID 2", * FROM (SELECT MAX(__base_query."base_table__part_id_1__display_id") AS "base_table__part_id_1__display_id" , MAX(__base_query."base_table__random_column") AS "base_table__random_column" , MAX(__base_query."base_table__part_id_2__display_id") AS "base_table__part_id_2__display_id" , "__row_id" FROM (SELECT __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1__display_id", "base_table__random_column", "base_table__part_id_2__display_id", "__row_id" FROM (SELECT __base_query."base_table__random_column" AS "base_table__random_column", __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1", "base_table__random_column", "base_table__part_id_2", "__row_id" FROM (SELECT __base_query.base_table__part_id_1 AS "base_table__part_id_1", __base_query.base_table__random_column AS "base_table__random_column", __base_query.base_table__part_id_2 AS "base_table__part_id_2", row_number() OVER () AS "__row_id", * FROM (SELECT base_table__part_id_1, base_table__random_column, base_table__part_id_2 FROM (SELECT base_table.part_id_1 AS base_table__part_id_1, base_table.random_column AS base_table__random_column, base_table.part_id_2 AS base_table__part_id_2, * FROM (select * from base_table) AS base_table) AS base_table) AS __base_query) AS __base_query) AS __base_query LEFT JOIN (SELECT base_table__part_id_1.display_id AS "base_table__part_id_1__display_id", * FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_1) AS base_table__part_id_1 ON __base_query.base_table__part_id_1 = base_table__part_id_1.id LEFT JOIN (SELECT base_table__part_id_2.display_id AS "base_table__part_id_2__display_id", * FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_2) AS base_table__part_id_2 ON __base_query.base_table__part_id_2 = base_table__part_id_2.id) AS MEERKAT_GENERATED_TABLE) AS __base_query) AS __base_query GROUP BY __row_id) AS __base_query) AS __base_query) order by __row_id + select * exclude(__row_id) from (SELECT "__row_id", "Part ID 1", "Random Column", "Part ID 2" FROM (SELECT __base_query."__row_id" AS "__row_id", __base_query."base_table__part_id_1__display_id" AS "Part ID 1", __base_query."base_table__random_column" AS "Random Column", __base_query."base_table__part_id_2__display_id" AS "Part ID 2", * FROM (SELECT MAX(__base_query."base_table__part_id_1__display_id") AS "base_table__part_id_1__display_id" , MAX(__base_query."base_table__random_column") AS "base_table__random_column" , MAX(__base_query."base_table__part_id_2__display_id") AS "base_table__part_id_2__display_id" , "__row_id" FROM (SELECT __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1__display_id", "base_table__random_column", "base_table__part_id_2__display_id", "__row_id" FROM (SELECT __base_query."base_table__random_column" AS "base_table__random_column", __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__part_id_1", "base_table__random_column", "base_table__part_id_2", "__row_id" FROM (SELECT __base_query.base_table__part_id_1 AS "base_table__part_id_1", __base_query.base_table__random_column AS "base_table__random_column", __base_query.base_table__part_id_2 AS "base_table__part_id_2", row_number() OVER () AS "__row_id", * FROM (SELECT base_table__part_id_1, base_table__random_column, base_table__part_id_2 FROM (SELECT base_table.part_id_1 AS base_table__part_id_1, base_table.random_column AS base_table__random_column, base_table.part_id_2 AS base_table__part_id_2, * FROM (select * from base_table) AS base_table) AS base_table) AS __base_query) AS __base_query) AS __base_query LEFT JOIN (SELECT base_table__part_id_1.display_id AS "base_table__part_id_1__display_id", * FROM (SELECT base_table__part_id_1.id, base_table__part_id_1.display_id FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_1) AS base_table__part_id_1) AS base_table__part_id_1 ON __base_query.base_table__part_id_1 = base_table__part_id_1.id LEFT JOIN (SELECT base_table__part_id_2.display_id AS "base_table__part_id_2__display_id", * FROM (SELECT base_table__part_id_2.id, base_table__part_id_2.display_id FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_2) AS base_table__part_id_2) AS base_table__part_id_2 ON __base_query.base_table__part_id_2 = base_table__part_id_2.id) AS MEERKAT_GENERATED_TABLE) AS __base_query) AS __base_query GROUP BY __row_id) AS __base_query) AS __base_query) order by __row_id `; expect(sql.replace(/\s+/g, ' ').trim()).toBe( expectedSQL.replace(/\s+/g, ' ').trim() @@ -409,7 +390,7 @@ describe('Resolution Tests', () => { }); console.info(`SQL: `, sql); const expectedSQL = ` - select * exclude(__row_id) from (SELECT "__row_id", "Random Column", "Part ID 1" FROM (SELECT __base_query."__row_id" AS "__row_id", __base_query."base_table__random_column" AS "Random Column", __base_query."base_table__part_id_1__display_id" AS "Part ID 1", * FROM (SELECT MAX(__base_query."base_table__random_column") AS "base_table__random_column" , MAX(__base_query."base_table__part_id_1__display_id") AS "base_table__part_id_1__display_id" , "__row_id" FROM (SELECT __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__random_column", "base_table__part_id_1__display_id", "__row_id" FROM (SELECT __base_query."base_table__random_column" AS "base_table__random_column", __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__random_column", "base_table__part_id_1", "__row_id" FROM (SELECT __base_query.base_table__random_column AS "base_table__random_column", __base_query.base_table__part_id_1 AS "base_table__part_id_1", row_number() OVER () AS "__row_id", * FROM (SELECT base_table__part_id_1, base_table__random_column, base_table__work_id, base_table__part_id_2 FROM (SELECT base_table.part_id_1 AS base_table__part_id_1, base_table.random_column AS base_table__random_column, base_table.work_id AS base_table__work_id, base_table.part_id_2 AS base_table__part_id_2, * FROM (select * from base_table) AS base_table) AS base_table) AS __base_query) AS __base_query) AS __base_query LEFT JOIN (SELECT base_table__part_id_1.display_id AS "base_table__part_id_1__display_id", * FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_1) AS base_table__part_id_1 ON __base_query.base_table__part_id_1 = base_table__part_id_1.id) AS MEERKAT_GENERATED_TABLE) AS __base_query) AS __base_query GROUP BY __row_id) AS __base_query) AS __base_query) order by __row_id + select * exclude(__row_id) from (SELECT "__row_id", "Random Column", "Part ID 1" FROM (SELECT __base_query."__row_id" AS "__row_id", __base_query."base_table__random_column" AS "Random Column", __base_query."base_table__part_id_1__display_id" AS "Part ID 1", * FROM (SELECT MAX(__base_query."base_table__random_column") AS "base_table__random_column" , MAX(__base_query."base_table__part_id_1__display_id") AS "base_table__part_id_1__display_id" , "__row_id" FROM (SELECT __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__random_column", "base_table__part_id_1__display_id", "__row_id" FROM (SELECT __base_query."base_table__random_column" AS "base_table__random_column", __base_query."__row_id" AS "__row_id", * FROM (SELECT "base_table__random_column", "base_table__part_id_1", "__row_id" FROM (SELECT __base_query.base_table__random_column AS "base_table__random_column", __base_query.base_table__part_id_1 AS "base_table__part_id_1", row_number() OVER () AS "__row_id", * FROM (SELECT base_table__part_id_1, base_table__random_column, base_table__work_id, base_table__part_id_2 FROM (SELECT base_table.part_id_1 AS base_table__part_id_1, base_table.random_column AS base_table__random_column, base_table.work_id AS base_table__work_id, base_table.part_id_2 AS base_table__part_id_2, * FROM (select * from base_table) AS base_table) AS base_table) AS __base_query) AS __base_query) AS __base_query LEFT JOIN (SELECT base_table__part_id_1.display_id AS "base_table__part_id_1__display_id", * FROM (SELECT base_table__part_id_1.id, base_table__part_id_1.display_id FROM (select id, display_id from system.dim_feature UNION ALL select id, display_id from system.dim_product) AS base_table__part_id_1) AS base_table__part_id_1) AS base_table__part_id_1 ON __base_query.base_table__part_id_1 = base_table__part_id_1.id) AS MEERKAT_GENERATED_TABLE) AS __base_query) AS __base_query GROUP BY __row_id) AS __base_query) AS __base_query) order by __row_id `; expect(sql.replace(/\s+/g, ' ').trim()).toBe( expectedSQL.replace(/\s+/g, ' ').trim() diff --git a/meerkat-node/src/cube-to-sql-with-resolution/cube-to-sql-with-resolution.ts b/meerkat-node/src/cube-to-sql-with-resolution/cube-to-sql-with-resolution.ts index 65776ef9..46bf7b9d 100644 --- a/meerkat-node/src/cube-to-sql-with-resolution/cube-to-sql-with-resolution.ts +++ b/meerkat-node/src/cube-to-sql-with-resolution/cube-to-sql-with-resolution.ts @@ -156,8 +156,10 @@ export const cubeQueryToSQLWithResolution = async ({ }); // Wrap with row_id ordering and exclusion - return wrapWithRowIdOrderingAndExclusion( + const finalSql = wrapWithRowIdOrderingAndExclusion( sqlWithAliases, ROW_ID_DIMENSION_NAME ); + + return finalSql; };