import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { IConnection } from 'src/utils/pagination/interfaces/connection.interface'; import { Record as IRecord, RecordFilter, RecordOrderBy, } from 'src/workspace/workspace-query-builder/interfaces/record.interface'; import { CreateManyResolverArgs, CreateOneResolverArgs, DeleteManyResolverArgs, DeleteOneResolverArgs, FindManyResolverArgs, FindOneResolverArgs, UpdateManyResolverArgs, UpdateOneResolverArgs, } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceQueryBuilderFactory } from 'src/workspace/workspace-query-builder/workspace-query-builder.factory'; import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util'; import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service'; import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-optionts.interface'; import { PGGraphQLMutation, PGGraphQLResult, } from './interfaces/pg-graphql.interface'; @Injectable() export class WorkspaceQueryRunnerService { private readonly logger = new Logger(WorkspaceQueryRunnerService.name); constructor( private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory, private readonly workspaceDataSourceService: WorkspaceDataSourceService, ) {} async findMany< Record extends IRecord = IRecord, Filter extends RecordFilter = RecordFilter, OrderBy extends RecordOrderBy = RecordOrderBy, >( args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise | undefined> { const { workspaceId, targetTableName } = options; const query = await this.workspaceQueryBuilderFactory.findMany( args, options, ); const result = await this.execute(query, workspaceId); return this.parseResult>(result, targetTableName, ''); } async findOne< Record extends IRecord = IRecord, Filter extends RecordFilter = RecordFilter, >( args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { if (!args.filter || Object.keys(args.filter).length === 0) { throw new BadRequestException('Missing filter argument'); } const { workspaceId, targetTableName } = options; const query = await this.workspaceQueryBuilderFactory.findOne( args, options, ); const result = await this.execute(query, workspaceId); const parsedResult = this.parseResult>( result, targetTableName, '', ); return parsedResult?.edges?.[0]?.node; } async createMany( args: CreateManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const { workspaceId, targetTableName } = options; const query = await this.workspaceQueryBuilderFactory.createMany( args, options, ); const result = await this.execute(query, workspaceId); return this.parseResult>( result, targetTableName, 'insertInto', )?.records; } async createOne( args: CreateOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const records = await this.createMany({ data: [args.data] }, options); return records?.[0]; } async updateOne( args: UpdateOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const { workspaceId, targetTableName } = options; const query = await this.workspaceQueryBuilderFactory.updateOne( args, options, ); const result = await this.execute(query, workspaceId); return this.parseResult>( result, targetTableName, 'update', )?.records?.[0]; } async deleteOne( args: DeleteOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const { workspaceId, targetTableName } = options; const query = await this.workspaceQueryBuilderFactory.deleteOne( args, options, ); const result = await this.execute(query, workspaceId); return this.parseResult>( result, targetTableName, 'deleteFrom', )?.records?.[0]; } async updateMany( args: UpdateManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const { workspaceId, targetTableName } = options; const query = await this.workspaceQueryBuilderFactory.updateMany( args, options, ); const result = await this.execute(query, workspaceId); return this.parseResult>( result, targetTableName, 'update', )?.records; } async deleteMany< Record extends IRecord = IRecord, Filter extends RecordFilter = RecordFilter, >( args: DeleteManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const { workspaceId, targetTableName } = options; const query = await this.workspaceQueryBuilderFactory.deleteMany( args, options, ); const result = await this.execute(query, workspaceId); return this.parseResult>( result, targetTableName, 'deleteFrom', )?.records; } private async execute( query: string, workspaceId: string, ): Promise { const workspaceDataSource = await this.workspaceDataSourceService.connectToWorkspaceDataSource( workspaceId, ); await workspaceDataSource?.query(` SET search_path TO ${this.workspaceDataSourceService.getSchemaName( workspaceId, )}; `); const results = await workspaceDataSource?.query(` SELECT graphql.resolve($$ ${query} $$); `); return results; } private parseResult( graphqlResult: PGGraphQLResult | undefined, targetTableName: string, command: string, ): Result { const entityKey = `${command}${targetTableName}Collection`; const result = graphqlResult?.[0]?.resolve?.data?.[entityKey]; const errors = graphqlResult?.[0]?.resolve?.errors; if (Array.isArray(errors) && errors.length > 0) { console.error('GraphQL errors', errors); } if (!result) { throw new BadRequestException('Malformed result from GraphQL query'); } return parseResult(result); } }