diff --git a/packages/cubejs-backend-native/src/orchestrator.rs b/packages/cubejs-backend-native/src/orchestrator.rs index 227e8b9970d76..00ab47b16be06 100644 --- a/packages/cubejs-backend-native/src/orchestrator.rs +++ b/packages/cubejs-backend-native/src/orchestrator.rs @@ -184,6 +184,9 @@ impl ValueObject for ResultWrapper { DBResponsePrimitive::String(s) => FieldValue::String(Cow::Borrowed(s)), DBResponsePrimitive::Number(n) => FieldValue::Number(*n), DBResponsePrimitive::Boolean(b) => FieldValue::Bool(*b), + DBResponsePrimitive::Uncommon(v) => FieldValue::String(Cow::Owned( + serde_json::to_string(&v).unwrap_or_else(|_| v.to_string()), + )), DBResponsePrimitive::Null => FieldValue::Null, }) } diff --git a/packages/cubejs-testing/birdbox-fixtures/postgresql/schema/unusualDataTypes.js b/packages/cubejs-testing/birdbox-fixtures/postgresql/schema/unusualDataTypes.js new file mode 100644 index 0000000000000..42849efcb4397 --- /dev/null +++ b/packages/cubejs-testing/birdbox-fixtures/postgresql/schema/unusualDataTypes.js @@ -0,0 +1,68 @@ +cube('unusualDataTypes', { + sql: `SELECT + 1 AS id, + 100 AS amount, + 'new' AS status, + '{"key": "value1", "number": 42}'::json AS json_column, + '{"key": "value1", "number": 42}'::jsonb AS jsonb_column, + ARRAY[1, 2, 3] AS array_column, + '11:22:33:44:55:66'::macaddr AS mac_address, + '192.168.0.1'::inet AS inet_column, + '192.168.0.0/24'::cidr AS cidr_column, + 't'::boolean AS boolean_column, + 'Hello, world!'::text AS text_column, + '1.0, 1.0'::point AS point_column, + '11111111'::bit(8) AS bit_column, + 'data'::xml AS xml_column + UNION ALL + SELECT + 2 AS id, + 200 AS amount, + 'new' AS status, + '{"key": "value2", "number": 84}'::json AS json_column, + '{"key": "value2", "number": 84}'::jsonb AS jsonb_column, + ARRAY[4, 5, 6] AS array_column, + '00:11:22:33:44:55'::macaddr AS mac_address, + '192.168.0.2'::inet AS inet_column, + '192.168.0.0/24'::cidr AS cidr_column, + 'f'::boolean AS boolean_column, + 'Goodbye, world!'::text AS text_column, + '2.0, 2.0'::point AS point_column, + '00000001'::bit(8) AS bit_column, + 'more data'::xml AS xml_column + UNION ALL + SELECT + 3 AS id, + 300 AS amount, + 'processed' AS status, + '{"key": "value3", "number": 168}'::json AS json_column, + '{"key": "value3", "number": 168}'::jsonb AS jsonb_column, + ARRAY[7, 8, 9] AS array_column, + '22:33:44:55:66:77'::macaddr AS mac_address, + '192.168.0.3'::inet AS inet_column, + '192.168.0.0/24'::cidr AS cidr_column, + 't'::boolean AS boolean_column, + 'PostgreSQL is awesome!'::text AS text_column, + '3.0, 3.0'::point AS point_column, + '11110000'::bit(8) AS bit_column, + 'even more data'::xml AS xml_column`, + measures: { + count: { type: 'count' }, + total_amount: { type: 'sum', sql: 'amount' } + }, + dimensions: { + id: { type: 'number', sql: 'id', primaryKey: true }, + status: { type: 'string', sql: 'status' }, + json: { type: 'string', sql: 'json_column' }, + jsonb: { type: 'string', sql: 'jsonb_column' }, + array: { type: 'string', sql: 'array_column' }, + mac_address: { type: 'string', sql: 'mac_address' }, + inet_column: { type: 'string', sql: 'inet_column' }, + cidr_column: { type: 'string', sql: 'cidr_column' }, + boolean_column: { type: 'string', sql: 'boolean_column' }, + text_column: { type: 'string', sql: 'text_column' }, + point_column: { type: 'string', sql: 'point_column' }, + bit_column: { type: 'string', sql: 'bit_column' }, + xml_column: { type: 'string', sql: 'xml_column' }, + } +}); diff --git a/packages/cubejs-testing/test/__snapshots__/cli-postgresql.test.ts.snap b/packages/cubejs-testing/test/__snapshots__/cli-postgresql.test.ts.snap index ce1dfe7bc3aac..cf99d6660a764 100644 --- a/packages/cubejs-testing/test/__snapshots__/cli-postgresql.test.ts.snap +++ b/packages/cubejs-testing/test/__snapshots__/cli-postgresql.test.ts.snap @@ -81,6 +81,95 @@ Array [ ] `; +exports[`postgresql HTTP Transport Different column data types: Different column data types 1`] = ` +Array [ + Object { + "unusualDataTypes.array": Array [ + 1, + 2, + 3, + ], + "unusualDataTypes.bit_column": "11111111", + "unusualDataTypes.boolean_column": true, + "unusualDataTypes.cidr_column": "192.168.0.0/24", + "unusualDataTypes.id": 1, + "unusualDataTypes.inet_column": "192.168.0.1", + "unusualDataTypes.json": Object { + "key": "value1", + "number": 42, + }, + "unusualDataTypes.jsonb": Object { + "key": "value1", + "number": 42, + }, + "unusualDataTypes.mac_address": "11:22:33:44:55:66", + "unusualDataTypes.point_column": Object { + "x": 1, + "y": 1, + }, + "unusualDataTypes.status": "new", + "unusualDataTypes.text_column": "Hello, world!", + "unusualDataTypes.xml_column": "data", + }, + Object { + "unusualDataTypes.array": Array [ + 4, + 5, + 6, + ], + "unusualDataTypes.bit_column": "00000001", + "unusualDataTypes.boolean_column": false, + "unusualDataTypes.cidr_column": "192.168.0.0/24", + "unusualDataTypes.id": 2, + "unusualDataTypes.inet_column": "192.168.0.2", + "unusualDataTypes.json": Object { + "key": "value2", + "number": 84, + }, + "unusualDataTypes.jsonb": Object { + "key": "value2", + "number": 84, + }, + "unusualDataTypes.mac_address": "00:11:22:33:44:55", + "unusualDataTypes.point_column": Object { + "x": 2, + "y": 2, + }, + "unusualDataTypes.status": "new", + "unusualDataTypes.text_column": "Goodbye, world!", + "unusualDataTypes.xml_column": "more data", + }, + Object { + "unusualDataTypes.array": Array [ + 7, + 8, + 9, + ], + "unusualDataTypes.bit_column": "11110000", + "unusualDataTypes.boolean_column": true, + "unusualDataTypes.cidr_column": "192.168.0.0/24", + "unusualDataTypes.id": 3, + "unusualDataTypes.inet_column": "192.168.0.3", + "unusualDataTypes.json": Object { + "key": "value3", + "number": 168, + }, + "unusualDataTypes.jsonb": Object { + "key": "value3", + "number": 168, + }, + "unusualDataTypes.mac_address": "22:33:44:55:66:77", + "unusualDataTypes.point_column": Object { + "x": 3, + "y": 3, + }, + "unusualDataTypes.status": "processed", + "unusualDataTypes.text_column": "PostgreSQL is awesome!", + "unusualDataTypes.xml_column": "even more data", + }, +] +`; + exports[`postgresql WS Transport #1 Orders.totalAmount: #1 Orders.totalAmount 1`] = ` Array [ Object { diff --git a/packages/cubejs-testing/test/abstract-test-case.ts b/packages/cubejs-testing/test/abstract-test-case.ts index 706335b47481d..af511be97cb75 100644 --- a/packages/cubejs-testing/test/abstract-test-case.ts +++ b/packages/cubejs-testing/test/abstract-test-case.ts @@ -66,6 +66,32 @@ const asserts: [options: QueryTestOptions, query: Query][] = [ ] } ], + [ + { + name: 'Different column data types' + }, + { + dimensions: [ + 'unusualDataTypes.array', + 'unusualDataTypes.bit_column', + 'unusualDataTypes.boolean_column', + 'unusualDataTypes.cidr_column', + 'unusualDataTypes.id', + 'unusualDataTypes.inet_column', + 'unusualDataTypes.json', + 'unusualDataTypes.jsonb', + 'unusualDataTypes.mac_address', + 'unusualDataTypes.point_column', + 'unusualDataTypes.status', + 'unusualDataTypes.text_column', + 'unusualDataTypes.xml_column' + ], + ungrouped: true, + order: { + 'unusualDataTypes.id': 'asc' + } + } + ], ]; // eslint-disable-next-line import/prefer-default-export @@ -152,7 +178,7 @@ export function createBirdBoxTestCase( let transport: WebSocketTransport; let http: CubeApi; let ws: CubeApi; - + beforeAll(async () => { try { transport = new WebSocketTransport({ @@ -170,11 +196,11 @@ export function createBirdBoxTestCase( process.exit(1); } }); - + afterAll(async () => { await transport.close(); }); - + test('http+responseFormat=default', async () => { const response = await http.load({ dimensions: ['Orders.status'], @@ -184,7 +210,7 @@ export function createBirdBoxTestCase( responses.push(response); expect(response.rawData()).toMatchSnapshot('result-type'); }); - + test('http+responseFormat=compact option#1', async () => { const response = await http.load({ dimensions: ['Orders.status'], @@ -195,7 +221,7 @@ export function createBirdBoxTestCase( responses.push(response); expect(response.rawData()).toMatchSnapshot('result-type'); }); - + test('http+responseFormat=compact option#2', async () => { const response = await http.load( { @@ -210,7 +236,7 @@ export function createBirdBoxTestCase( responses.push(response); expect(response.rawData()).toMatchSnapshot('result-type'); }); - + test('http+responseFormat=compact option#1+2', async () => { const response = await http.load( { @@ -226,7 +252,7 @@ export function createBirdBoxTestCase( responses.push(response); expect(response.rawData()).toMatchSnapshot('result-type'); }); - + test('ws+responseFormat=default', async () => { const response = await ws.load({ dimensions: ['Orders.status'], @@ -236,7 +262,7 @@ export function createBirdBoxTestCase( responses.push(response); expect(response.rawData()).toMatchSnapshot('result-type'); }); - + test('ws+responseFormat=compact option#1', async () => { const response = await ws.load({ dimensions: ['Orders.status'], @@ -247,7 +273,7 @@ export function createBirdBoxTestCase( responses.push(response); expect(response.rawData()).toMatchSnapshot('result-type'); }); - + test('ws+responseFormat=compact option#2', async () => { const response = await ws.load( { @@ -262,7 +288,7 @@ export function createBirdBoxTestCase( responses.push(response); expect(response.rawData()).toMatchSnapshot('result-type'); }); - + test('ws+responseFormat=compact option#1+2', async () => { const response = await ws.load( { @@ -278,7 +304,7 @@ export function createBirdBoxTestCase( responses.push(response); expect(response.rawData()).toMatchSnapshot('result-type'); }); - + test('responses', () => { // @ts-ignore expect(responses[0].rawData()).toEqual(responses[1].rawData()); diff --git a/rust/cubeorchestrator/src/query_result_transform.rs b/rust/cubeorchestrator/src/query_result_transform.rs index 22e18a889327b..c1165270965e2 100644 --- a/rust/cubeorchestrator/src/query_result_transform.rs +++ b/rust/cubeorchestrator/src/query_result_transform.rs @@ -621,6 +621,7 @@ pub enum DBResponsePrimitive { Boolean(bool), Number(f64), String(String), + Uncommon(Value), } impl Display for DBResponsePrimitive { @@ -630,6 +631,9 @@ impl Display for DBResponsePrimitive { DBResponsePrimitive::Boolean(b) => b.to_string(), DBResponsePrimitive::Number(n) => n.to_string(), DBResponsePrimitive::String(s) => s.clone(), + DBResponsePrimitive::Uncommon(v) => { + serde_json::to_string(&v).unwrap_or_else(|_| v.to_string()) + } }; write!(f, "{}", str) }