From f2eaa9341e029924a21b2247b6c81f5e4c7825b8 Mon Sep 17 00:00:00 2001 From: senthilb-devrev Date: Thu, 20 Nov 2025 18:53:24 +0530 Subject: [PATCH 1/2] avoiding using prefix for check condition --- meerkat-browser/package.json | 2 +- meerkat-core/package.json | 2 +- meerkat-core/src/resolution/steps/resolution-step.ts | 8 +------- meerkat-node/package.json | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/meerkat-browser/package.json b/meerkat-browser/package.json index f4bd8bbd..3a10d9bc 100644 --- a/meerkat-browser/package.json +++ b/meerkat-browser/package.json @@ -1,6 +1,6 @@ { "name": "@devrev/meerkat-browser", - "version": "0.0.108", + "version": "0.0.109", "dependencies": { "tslib": "^2.3.0", "@devrev/meerkat-core": "*", diff --git a/meerkat-core/package.json b/meerkat-core/package.json index 9d1c76ef..6602712a 100644 --- a/meerkat-core/package.json +++ b/meerkat-core/package.json @@ -1,6 +1,6 @@ { "name": "@devrev/meerkat-core", - "version": "0.0.108", + "version": "0.0.109", "dependencies": { "tslib": "^2.3.0" }, diff --git a/meerkat-core/src/resolution/steps/resolution-step.ts b/meerkat-core/src/resolution/steps/resolution-step.ts index 186ad134..58a18ea4 100644 --- a/meerkat-core/src/resolution/steps/resolution-step.ts +++ b/meerkat-core/src/resolution/steps/resolution-step.ts @@ -97,13 +97,7 @@ export const getResolvedTableSchema = async ({ (typeof resolutionSchemas)[0] >(); resolutionSchemas.forEach((resSchema) => { - resolutionConfig.columnConfigs.forEach((config) => { - if ( - resSchema.dimensions.some((dim) => dim.name.startsWith(config.name)) - ) { - resolutionSchemaByConfigName.set(config.name, resSchema); - } - }); + resolutionSchemaByConfigName.set(resSchema.name, resSchema); }); // Build the dimension map using the pre-indexed schemas diff --git a/meerkat-node/package.json b/meerkat-node/package.json index 339ce864..9efc8f3b 100644 --- a/meerkat-node/package.json +++ b/meerkat-node/package.json @@ -1,6 +1,6 @@ { "name": "@devrev/meerkat-node", - "version": "0.0.108", + "version": "0.0.109", "dependencies": { "@swc/helpers": "~0.5.0", "@devrev/meerkat-core": "*", From a671be6bcab20019d9ca507ec75a5d22ffa2d5d1 Mon Sep 17 00:00:00 2001 From: senthilb-devrev Date: Fri, 21 Nov 2025 19:15:21 +0530 Subject: [PATCH 2/2] adding test --- .../src/resolution/steps/resolution-step.ts | 5 +- .../cube-to-sql-with-resolution.spec.ts | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/meerkat-core/src/resolution/steps/resolution-step.ts b/meerkat-core/src/resolution/steps/resolution-step.ts index 58a18ea4..11104c42 100644 --- a/meerkat-core/src/resolution/steps/resolution-step.ts +++ b/meerkat-core/src/resolution/steps/resolution-step.ts @@ -92,10 +92,7 @@ export const getResolvedTableSchema = async ({ const resolutionDimensionsByColumnName = new Map(); // Create a map of resolution schemas by config name for efficient lookup - const resolutionSchemaByConfigName = new Map< - string, - (typeof resolutionSchemas)[0] - >(); + const resolutionSchemaByConfigName = new Map(); resolutionSchemas.forEach((resSchema) => { resolutionSchemaByConfigName.set(resSchema.name, resSchema); }); diff --git a/meerkat-node/src/__tests__/cube-to-sql-with-resolution.spec.ts b/meerkat-node/src/__tests__/cube-to-sql-with-resolution.spec.ts index f640feeb..97cfa20e 100644 --- a/meerkat-node/src/__tests__/cube-to-sql-with-resolution.spec.ts +++ b/meerkat-node/src/__tests__/cube-to-sql-with-resolution.spec.ts @@ -609,4 +609,133 @@ describe('cubeQueryToSQLWithResolution - Array field resolution', () => { expect(ticket3!['Created By - Name']).toBe('User 3'); expect(ticket3!['Owners']).toContain('owner4'); }); + + it('Should handle fields with prefix names correctly (owners vs owners_field1)', async () => { + // Add a new column to the existing tickets table for testing prefix collision + await duckdbExec(` + ALTER TABLE tickets ADD COLUMN owners_field1 VARCHAR[] + `); + + await duckdbExec(` + UPDATE tickets SET owners_field1 = CASE + WHEN id = 1 THEN ['owner1', 'owner3'] + WHEN id = 2 THEN ['owner2', 'owner4'] + WHEN id = 3 THEN ['owner1', 'owner4'] + END + `); + + // Create a table schema that includes both owners and owners_field1 + const ticketsWithPrefixSchema: TableSchema = { + name: 'tickets', + sql: 'select * from tickets', + measures: [ + { + name: 'count', + sql: 'COUNT(*)', + type: 'number', + }, + ], + dimensions: [ + { + alias: 'ID', + name: 'id', + sql: 'id', + type: 'number', + }, + { + alias: 'Owners', + name: 'owners', + sql: 'owners', + type: 'string_array', + }, + { + alias: 'Owners Field 1', + name: 'owners_field1', + sql: 'owners_field1', + type: 'string_array', + }, + ], + }; + + const query: Query = { + measures: ['tickets.count'], + dimensions: ['tickets.id', 'tickets.owners', 'tickets.owners_field1'], + order: { 'tickets.id': 'asc' }, + }; + + const resolutionConfig: ResolutionConfig = { + tableSchemas: [OWNERS_LOOKUP_SCHEMA], + columnConfigs: [ + { + name: 'tickets.owners', + type: 'string_array' as const, + source: 'owners_lookup', + joinColumn: 'id', + resolutionColumns: ['display_name', 'email'], + }, + { + name: 'tickets.owners_field1', + type: 'string_array' as const, + source: 'owners_lookup', + joinColumn: 'id', + resolutionColumns: ['display_name', 'email'], + }, + ], + }; + + const sql = await cubeQueryToSQLWithResolution({ + query, + tableSchemas: [ticketsWithPrefixSchema], + resolutionConfig, + columnProjections: [ + 'tickets.owners', + 'tickets.owners_field1', + 'tickets.count', + 'tickets.id', + ], + }); + + console.log('SQL (prefix test):', sql); + + // Export to CSV using COPY command + const csvPath = '/tmp/test_prefix_fields.csv'; + await duckdbExec(`COPY (${sql}) TO '${csvPath}' (HEADER, DELIMITER ',')`); + + // Read the CSV back + const result = (await duckdbExec( + `SELECT * FROM read_csv_auto('${csvPath}')` + )) as any[]; + console.log('Result from CSV (prefix test):', result); + + // Check that both columns are present and resolved independently + expect(result[0]).toHaveProperty('Owners - Display Name'); + expect(result[0]).toHaveProperty('Owners - Email'); + expect(result[0]).toHaveProperty('Owners Field 1 - Display Name'); + expect(result[0]).toHaveProperty('Owners Field 1 - Email'); + + // Verify the first ticket's data + const ticket1Rows = result.filter((r: any) => Number(r.ID) === 1); + + // Check owners field + const ownersDisplayNames = parseJsonArray( + ticket1Rows[0]['Owners - Display Name'] + ); + expect(Array.isArray(ownersDisplayNames)).toBe(true); + expect(ownersDisplayNames).toContain('Alice Smith'); + expect(ownersDisplayNames).toContain('Bob Jones'); + + // Check owners_field1 field (should have different values) + const ownersField1DisplayNames = parseJsonArray( + ticket1Rows[0]['Owners Field 1 - Display Name'] + ); + expect(Array.isArray(ownersField1DisplayNames)).toBe(true); + expect(ownersField1DisplayNames).toContain('Alice Smith'); + expect(ownersField1DisplayNames).toContain('Charlie Brown'); + + // Verify they are different arrays + expect(ownersDisplayNames).not.toEqual(ownersField1DisplayNames); + + // Clean up + await duckdbExec('ALTER TABLE tickets DROP COLUMN owners_field1'); + }); });