diff --git a/server/src/services/tooljet_db.service.ts b/server/src/services/tooljet_db.service.ts index 445bbaa398..930f34fbc6 100644 --- a/server/src/services/tooljet_db.service.ts +++ b/server/src/services/tooljet_db.service.ts @@ -12,8 +12,9 @@ import { import { InjectEntityManager } from '@nestjs/typeorm'; import { InternalTable } from 'src/entities/internal_table.entity'; import { isString, isEmpty, camelCase } from 'lodash'; -import { TooljetDatabaseError, TooljetDbActions } from 'src/modules/tooljet_db/tooljet-db.types'; +import { PostgrestError, TooljetDatabaseError, TooljetDbActions } from 'src/modules/tooljet_db/tooljet-db.types'; import { v4 as uuidv4 } from 'uuid'; +import { QueryError } from '@tooljet/plugins/packages/common'; export type TableColumnSchema = { column_name: string; @@ -816,8 +817,8 @@ export class TooljetDbService { const queryBuilder = this.buildJoinQuery(joinQueryJson, internalTableIdToNameMap); return await queryBuilder.getRawMany(); } catch (error) { - const errorObj = new QueryFailedError(error, [], error); - throw new TooljetDatabaseError( + const errorObj = new QueryFailedError(error, [], new PostgrestError(error)); + const tjdbErrorObj = new TooljetDatabaseError( error.message, { origin: 'join_tables', @@ -825,6 +826,8 @@ export class TooljetDbService { }, errorObj ); + const alteredErrorMessage = tjdbErrorObj.toString(); + throw new QueryError(alteredErrorMessage, alteredErrorMessage, {}); } } @@ -849,12 +852,16 @@ export class TooljetDbService { if (!isEmpty(queryJson.aggregates)) { Object.entries(queryJson.aggregates).forEach(([_key, aggregateParams]) => { const { aggFx, column, table_id: tableId } = aggregateParams as any; + if (isEmpty(column) || isEmpty(aggFx)) + throw new Error('There are empty values in certain aggregate conditions.'); + const allowedAggFunctions = ['sum', 'count']; if (!allowedAggFunctions.includes(aggFx)) { throw new BadRequestException('Invalid aggregate function'); } + queryBuilder.addSelect( - `${AggregateFunctions[aggFx]}(${internalTableIdToNameMap[tableId]}.${column})`, + `${AggregateFunctions[aggFx]}("${internalTableIdToNameMap[tableId]}"."${column}")`, `${internalTableIdToNameMap[tableId]}_${column}_${aggFx}` ); }); @@ -867,12 +874,12 @@ export class TooljetDbService { groupByColumList.forEach((groupByColum) => { // The 'SELECT' statement needs to have 'GROUP_BY' columns added. queryBuilder.addSelect( - `${internalTableIdToNameMap[groupByTableId]}.${groupByColum}`, + `"${internalTableIdToNameMap[groupByTableId]}"."${groupByColum}"`, `${internalTableIdToNameMap[groupByTableId]}_${groupByColum}` ); // Building `GROUP_BY` statement - queryBuilder.addGroupBy(`${internalTableIdToNameMap[groupByTableId]}.${groupByColum}`); + queryBuilder.addGroupBy(`"${internalTableIdToNameMap[groupByTableId]}"."${groupByColum}"`); }); } }); diff --git a/server/src/services/tooljet_db_operations.service.ts b/server/src/services/tooljet_db_operations.service.ts index 84a738523a..b3944a0e37 100644 --- a/server/src/services/tooljet_db_operations.service.ts +++ b/server/src/services/tooljet_db_operations.service.ts @@ -7,6 +7,7 @@ import { PostgrestProxyService } from './postgrest_proxy.service'; import { maybeSetSubPath } from 'src/helpers/utils.helper'; import { EntityManager } from 'typeorm'; import { InternalTable } from 'src/entities/internal_table.entity'; +import { QueryError } from '@tooljet/plugins/packages/common'; @Injectable() export class TooljetDbOperationsService implements QueryService { @@ -58,59 +59,63 @@ export class TooljetDbOperationsService implements QueryService { data: {}, }; } - const { table_id: tableId, list_rows: listRows, organization_id: organizationId } = queryOptions; - const query = []; + try { + const { table_id: tableId, list_rows: listRows, organization_id: organizationId } = queryOptions; + const query = []; - if (!isEmpty(listRows)) { - const { - limit, - where_filters: whereFilters, - order_filters: orderFilters, - offset, - aggregates = {}, - group_by: groupBy = {}, - } = listRows; + if (!isEmpty(listRows)) { + const { + limit, + where_filters: whereFilters, + order_filters: orderFilters, + offset, + aggregates = {}, + group_by: groupBy = {}, + } = listRows; - const internalTable = await this.manager.findOne(InternalTable, { - where: { - organizationId, - id: tableId, - }, - }); + const internalTable = await this.manager.findOne(InternalTable, { + where: { + organizationId, + id: tableId, + }, + }); - if (!internalTable) throw new NotFoundException('Table not found'); + if (!internalTable) throw new NotFoundException('Table not found'); - if (limit && isNaN(limit)) { - return { - status: 'failed', - errorMessage: 'Limit should be a number.', - data: {}, - }; + if (limit && isNaN(limit)) { + return { + status: 'failed', + errorMessage: 'Limit should be a number.', + data: {}, + }; + } + + const whereQuery = buildPostgrestQuery(whereFilters); + const orderQuery = buildPostgrestQuery(orderFilters); + if (!isEmpty(aggregates) || !isEmpty(groupBy)) { + const groupByAndAggregateQueryList = this.buildAggregateAndGroupByQuery( + internalTable.tableName, + aggregates, + groupBy + ); + if (groupByAndAggregateQueryList.length) query.push(`select=${groupByAndAggregateQueryList.join(',')}`); + } + !isEmpty(whereQuery) && query.push(whereQuery); + !isEmpty(orderQuery) && query.push(orderQuery); + !isEmpty(limit) && query.push(`limit=${limit}`); + !isEmpty(offset) && query.push(`offset=${offset}`); } - const whereQuery = buildPostgrestQuery(whereFilters); - const orderQuery = buildPostgrestQuery(orderFilters); - if (!isEmpty(aggregates) || !isEmpty(groupBy)) { - const groupByAndAggregateQueryList = this.buildAggregateAndGroupByQuery( - internalTable.tableName, - aggregates, - groupBy - ); - if (groupByAndAggregateQueryList.length) query.push(`select=${groupByAndAggregateQueryList.join(',')}`); - } - !isEmpty(whereQuery) && query.push(whereQuery); - !isEmpty(orderQuery) && query.push(orderQuery); - !isEmpty(limit) && query.push(`limit=${limit}`); - !isEmpty(offset) && query.push(`offset=${offset}`); + const headers = { 'data-query-id': queryOptions.id, 'tj-workspace-id': queryOptions.organization_id }; + const url = + query.length > 0 + ? `/api/tooljet-db/proxy/${tableId}` + `?${query.join('&')}` + : `/api/tooljet-db/proxy/${tableId}`; + + return await this.proxyPostgrest(maybeSetSubPath(url), 'GET', headers); + } catch (error) { + throw new QueryError(error.message, error.message, {}); } - - const headers = { 'data-query-id': queryOptions.id, 'tj-workspace-id': queryOptions.organization_id }; - const url = - query.length > 0 - ? `/api/tooljet-db/proxy/${tableId}` + `?${query.join('&')}` - : `/api/tooljet-db/proxy/${tableId}`; - - return await this.proxyPostgrest(maybeSetSubPath(url), 'GET', headers); } async createRow(queryOptions): Promise { @@ -258,6 +263,8 @@ export class TooljetDbOperationsService implements QueryService { if (!isEmpty(aggregates)) { Object.entries(aggregates).forEach(([_key, aggregateDetail]) => { const { aggFx, column } = aggregateDetail; + if (isEmpty(column) || isEmpty(aggFx)) + throw new Error('There are empty values in certain aggregate conditions.'); if (aggFx && column) query.push(`${tableName}_${column}_${aggFx}:${column}.${AggregateFunctions[aggFx]}()`); }); }