From f1e25bb50323c0b99f3891d349467e7b637baeea Mon Sep 17 00:00:00 2001 From: Dmitriy L <97888955+wolfeeeg@users.noreply.github.com> Date: Thu, 8 Dec 2022 08:21:21 +0300 Subject: [PATCH] chore(cubejs-api-gateway): added endpoint to run sql query directly (#5723) --- packages/cubejs-api-gateway/src/gateway.ts | 61 +++++++++++++++++++ .../cubejs-api-gateway/test/index.test.ts | 52 ++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/packages/cubejs-api-gateway/src/gateway.ts b/packages/cubejs-api-gateway/src/gateway.ts index 072850474efc8..e7713b56f7fe8 100644 --- a/packages/cubejs-api-gateway/src/gateway.ts +++ b/packages/cubejs-api-gateway/src/gateway.ts @@ -251,6 +251,19 @@ class ApiGateway { } })); + app.post( + `${this.basePath}/v1/sql-runner`, + jsonParser, + userMiddlewares, + async (req: Request, res: Response) => { + await this.sqlRunner({ + query: req.body.query, + context: req.context!, + res: this.resToResultFn(res), + }); + } + ); + app.get(`${this.basePath}/v1/run-scheduled-refresh`, userMiddlewares, (async (req, res) => { await this.runScheduledRefresh({ queryingOptions: req.query.queryingOptions, @@ -1068,6 +1081,54 @@ class ApiGateway { } } + public async sqlRunner({ query, context, res }: QueryRequest) { + const requestStarted = new Date(); + try { + if (!query) { + throw new UserError( + 'A user\'s query must contain a body' + ); + } + + if (!(query as Record).query) { + throw new UserError( + 'A user\'s query must contain at least one query param.' + ); + } + + query = { + ...query, + requestId: context.requestId + }; + + this.log( + { + type: 'Load SQL Runner Request', + query, + }, + context + ); + + const result = await this.getAdapterApi(context).executeQuery(query); + + this.log( + { + type: 'Load SQL Runner Request Success', + query, + duration: this.duration(requestStarted), + dbType: result.dbType, + }, + context + ); + + res(result); + } catch (e) { + this.handleError({ + e, context, query, res, requestStarted + }); + } + } + protected createSecurityContextExtractor(options?: JWTOptions): SecurityContextExtractorFn { if (options?.claimsNamespace) { return (ctx: Readonly) => { diff --git a/packages/cubejs-api-gateway/test/index.test.ts b/packages/cubejs-api-gateway/test/index.test.ts index d3cb48db2b7a4..32ff5bd73d580 100644 --- a/packages/cubejs-api-gateway/test/index.test.ts +++ b/packages/cubejs-api-gateway/test/index.test.ts @@ -339,6 +339,58 @@ describe('API Gateway', () => { expect(res.body.query.measures).toStrictEqual(['Foo.bar']); }); + test('post http method for sql-runner route required params', async () => { + const { app } = createApiGateway(); + + const res = await request(app) + .post('/cubejs-api/v1/sql-runner') + .set('Content-type', 'application/json') + .set( + 'Authorization', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M' + ) + .send({}) + .expect(400); + + expect(res.body && res.body.error).toStrictEqual('A user\'s query must contain a body'); + + const res2 = await request(app) + .post('/cubejs-api/v1/sql-runner') + .set('Content-type', 'application/json') + .set( + 'Authorization', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M' + ) + .send({ query: { query: '' } }) + .expect(400); + + expect(res2.body && res2.body.error).toStrictEqual( + 'A user\'s query must contain at least one query param.' + ); + }); + + test('post http method for sql-runner route', async () => { + const { app } = createApiGateway(); + + const query = { + query: 'SELECT * FROM Foo', + }; + + const res = await request(app) + .post('/cubejs-api/v1/sql-runner') + .set('Content-type', 'application/json') + .set( + 'Authorization', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M' + ) + .send({ query }) + .expect(200); + + expect(res.body.data).toStrictEqual([ + { foo__bar: 42 }, + ]); + }); + test('meta endpoint to get schema information', async () => { const { app } = createApiGateway();