11/* eslint-disable no-restricted-syntax */
2+ import * as stream from 'stream' ;
23import jwt , { Algorithm as JWTAlgorithm } from 'jsonwebtoken' ;
34import R from 'ramda' ;
45import bodyParser from 'body-parser' ;
@@ -86,15 +87,6 @@ import {
8687 transformPreAggregations ,
8788} from './helpers/transformMetaExtended' ;
8889
89- // const timeoutPromise = (timeout) => (
90- // new Promise((resolve) => (
91- // setTimeout(
92- // () => resolve(null),
93- // timeout,
94- // )
95- // ))
96- // );
97-
9890/**
9991 * API gateway server class.
10092 */
@@ -1069,10 +1061,11 @@ class ApiGateway {
10691061 protected async getNormalizedQueries (
10701062 query : Record < string , any > | Record < string , any > [ ] ,
10711063 context : RequestContext ,
1064+ persistent = false ,
10721065 ) : Promise < [ QueryType , NormalizedQuery [ ] ] > {
10731066 query = this . parseQueryParam ( query ) ;
1074- let queryType : QueryType = QueryTypeEnum . REGULAR_QUERY ;
10751067
1068+ let queryType : QueryType = QueryTypeEnum . REGULAR_QUERY ;
10761069 if ( ! Array . isArray ( query ) ) {
10771070 query = this . compareDateRangeTransformer ( query ) ;
10781071 if ( Array . isArray ( query ) ) {
@@ -1083,6 +1076,7 @@ class ApiGateway {
10831076 }
10841077
10851078 const queries = Array . isArray ( query ) ? query : [ query ] ;
1079+
10861080 const normalizedQueries : NormalizedQuery [ ] = await Promise . all (
10871081 queries . map (
10881082 async ( currentQuery ) => validatePostRewrite (
@@ -1094,6 +1088,10 @@ class ApiGateway {
10941088 )
10951089 ) ;
10961090
1091+ normalizedQueries . forEach ( ( q ) => {
1092+ this . processQueryLimit ( q , persistent ) ;
1093+ } ) ;
1094+
10971095 if ( normalizedQueries . find ( ( currentQuery ) => ! currentQuery ) ) {
10981096 throw new Error ( 'queryTransformer returned null query. Please check your queryTransformer implementation' ) ;
10991097 }
@@ -1112,6 +1110,29 @@ class ApiGateway {
11121110 return [ queryType , normalizedQueries ] ;
11131111 }
11141112
1113+ /**
1114+ * Asserts query limit, sets the default value if neccessary.
1115+ *
1116+ * @throw {Error }
1117+ */
1118+ public processQueryLimit ( query : NormalizedQuery , persistent = false ) : void {
1119+ const def = getEnv ( 'dbQueryDefaultLimit' ) <= getEnv ( 'dbQueryLimit' )
1120+ ? getEnv ( 'dbQueryDefaultLimit' )
1121+ : getEnv ( 'dbQueryLimit' ) ;
1122+
1123+ if ( ! persistent ) {
1124+ if (
1125+ typeof query . limit === 'number' &&
1126+ query . limit > getEnv ( 'dbQueryLimit' )
1127+ ) {
1128+ throw new Error ( 'The query limit has been exceeded.' ) ;
1129+ }
1130+ query . limit = typeof query . limit === 'number'
1131+ ? query . limit
1132+ : def ;
1133+ }
1134+ }
1135+
11151136 public async sql ( { query, context, res } : QueryRequest ) {
11161137 const requestStarted = new Date ( ) ;
11171138
@@ -1269,7 +1290,6 @@ class ApiGateway {
12691290 context : RequestContext ,
12701291 normalizedQuery : NormalizedQuery ,
12711292 sqlQuery : any ,
1272- apiType : string ,
12731293 ) {
12741294 const queries = [ {
12751295 ...sqlQuery ,
@@ -1279,7 +1299,7 @@ class ApiGateway {
12791299 renewQuery : normalizedQuery . renewQuery ,
12801300 requestId : context . requestId ,
12811301 context,
1282- persistent : false , // apiType === 'sql',
1302+ persistent : false ,
12831303 } ] ;
12841304 if ( normalizedQuery . total ) {
12851305 const normalizedTotal = structuredClone ( normalizedQuery ) ;
@@ -1378,6 +1398,50 @@ class ApiGateway {
13781398 } ;
13791399 }
13801400
1401+ /**
1402+ * Returns stream object which will be used to stream results from
1403+ * the data source if applicable, returns `null` otherwise.
1404+ */
1405+ public async stream ( context : RequestContext , query : Query ) : Promise < null | {
1406+ originalQuery : Query ;
1407+ normalizedQuery : NormalizedQuery ;
1408+ streamingQuery : unknown ;
1409+ stream : stream . Writable ;
1410+ } > {
1411+ const requestStarted = new Date ( ) ;
1412+ try {
1413+ this . log ( { type : 'Streaming Query' , query } , context ) ;
1414+ const [ , normalizedQueries ] = await this . getNormalizedQueries ( query , context , true ) ;
1415+ const sqlQuery = ( await this . getSqlQueriesInternal ( context , normalizedQueries ) ) [ 0 ] ;
1416+ const q = {
1417+ ...sqlQuery ,
1418+ query : sqlQuery . sql [ 0 ] ,
1419+ values : sqlQuery . sql [ 1 ] ,
1420+ continueWait : true ,
1421+ renewQuery : false ,
1422+ requestId : context . requestId ,
1423+ context,
1424+ persistent : true ,
1425+ forceNoCache : true ,
1426+ } ;
1427+ const _stream = {
1428+ originalQuery : query ,
1429+ normalizedQuery : normalizedQueries [ 0 ] ,
1430+ streamingQuery : q ,
1431+ stream : await this . getAdapterApi ( context ) . streamQuery ( q ) ,
1432+ } ;
1433+ return _stream ;
1434+ } catch ( e ) {
1435+ this . log ( {
1436+ type : 'Streaming Error' ,
1437+ query,
1438+ error : ( < Error > e ) . message ,
1439+ duration : this . duration ( requestStarted ) ,
1440+ } , context ) ;
1441+ return null ;
1442+ }
1443+ }
1444+
13811445 /**
13821446 * Data queries APIs (`/load`, `/subscribe`) entry point. Used by
13831447 * `CubejsApi#load` and `CubejsApi#subscribe` methods to fetch the
@@ -1434,7 +1498,6 @@ class ApiGateway {
14341498 context ,
14351499 normalizedQuery ,
14361500 sqlQueries [ index ] ,
1437- apiType ,
14381501 ) ;
14391502
14401503 return this . getResultInternal (
0 commit comments