UNPKG

ottoman

Version:
496 lines 19.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildIndexExpr = exports.buildWhereClauseExpr = exports.verifyWhereObjectKey = exports.buildWhereExpr = exports.buildSelectExpr = exports.buildSelectArrayExpr = exports.selectBuilder = void 0; const ottoman_errors_1 = require("../../exceptions/ottoman-errors"); const exceptions_1 = require("../exceptions"); const utils_1 = require("../utils"); const dictionary_1 = require("./dictionary"); // start of SELECT expression functions /** * Build a SELECT N1QL query from user-specified parameters. * {@link https://docs.couchbase.com/server/6.5/n1ql/n1ql-language-reference/select-syntax.html} * @param collection Collection name * @param select SELECT Clause * @param letExpr LET Clause * @param where WHERE Clause * @param orderBy ORDER BY Clause * @param limit LIMIT Clause * @param offset OFFSET Clause * @param useExpr USE Clause * @param groupByExpr GROUP BY Clause * @param lettingExpr LETTING Clause * @param havingExpr HAVING Clause * @param plainJoinExpr PLAIN JOIN string definition * @param ignoreCase boolean to ignore case * * @return N1QL SELECT Query * */ const selectBuilder = (collection, select, letExpr, where, orderBy, limit, offset, useExpr, groupByExpr, lettingExpr, havingExpr, plainJoinExpr, ignoreCase) => { try { let expr = ''; if (typeof select === 'string') { expr = select; } if (Array.isArray(select)) { expr = (0, exports.buildSelectArrayExpr)(select); } const _collection = (0, utils_1.escapeFromClause)(collection); return `SELECT ${expr} FROM ${_collection}${plainJoinExpr ? ` ${plainJoinExpr} ` : ''}${_buildUseKeysExpr(useExpr)}${_buildLetExpr(letExpr)}${(0, exports.buildWhereExpr)(where, undefined, ignoreCase)}${_buildGroupByExpr(groupByExpr, lettingExpr, havingExpr)}${_buildOrderByExpr(orderBy)}${_buildLimitExpr(limit)}${_buildOffsetExpr(offset)}`; } catch (exception) { if (exception instanceof exceptions_1.WhereClauseException) { throw exception; } throw new exceptions_1.SelectClauseException(); } }; exports.selectBuilder = selectBuilder; /** * @ignore * */ const _buildAS = (c) => { return c.hasOwnProperty('as') ? ` AS ${c['as']}` : ''; }; /** * @ignore * */ const _buildField = (clause) => { if (clause.hasOwnProperty('name')) { return `${(0, utils_1.escapeReservedWords)(clause['name'])}${_buildAS(clause)}`; } return `${(0, utils_1.escapeReservedWords)(clause)}`; }; /** * Create N1QL queries from SELECT array params. * @param clause SELECT Clause * * @return N1QL SELECT Query * */ const buildSelectArrayExpr = (clause) => { return `${clause.map((c) => (0, exports.buildSelectExpr)('', c)).join(',')}`; }; exports.buildSelectArrayExpr = buildSelectArrayExpr; /** * Recursive function to create N1QL queries. * @param n1ql N1QL Query String * @param clause SELECT Clause * * @return N1QL SELECT Query * */ const buildSelectExpr = (n1ql, clause) => { try { if (clause.hasOwnProperty('$field')) { return _buildField(clause['$field']); } const key = Object.keys(clause)[0]; if (dictionary_1.ReturnResultDict.hasOwnProperty(key)) { return `${dictionary_1.ReturnResultDict[key]} ${(0, exports.buildSelectExpr)(n1ql, clause[key])}`; } if (dictionary_1.ResultExprDict.hasOwnProperty(key)) { return `${dictionary_1.ResultExprDict[key]} ${(0, exports.buildSelectExpr)(n1ql, clause[key])}`; } if (dictionary_1.AggDict.hasOwnProperty(key)) { // todo: check if have AS expression inside of Agg function. return `${dictionary_1.AggDict[key]}(${_buildAggDictExpr(clause, key)}${(0, exports.buildSelectExpr)(n1ql, clause[key])})${_buildAS(clause[key])}`; } throw new exceptions_1.SelectClauseException(); } catch (_a) { throw new exceptions_1.SelectClauseException(); } }; exports.buildSelectExpr = buildSelectExpr; /** * @ignore * */ const _buildAggDictExpr = (clause, key) => { if (dictionary_1.AggDict.hasOwnProperty(key)) { if (clause[key].hasOwnProperty('ro')) { return `${dictionary_1.ReturnResultDict[clause[key]['ro']]} `; } } return ''; }; /** * @ignore * */ const _buildLetExpr = (letExpr, clause = 'LET') => { const entries = Object.entries(letExpr !== null && letExpr !== void 0 ? letExpr : {}); return entries.length ? ` ${clause} ${entries .map(([key, value]) => { const parsedValue = Array.isArray(value) ? JSON.stringify(value) : value; return `${(0, utils_1.escapeReservedWords)(key)}=${parsedValue}`; }) .join(',')}` : ''; }; /** * @ignore * */ const _buildOrderByExpr = (orderExpr) => { return !!orderExpr ? ` ORDER BY ${Object.keys(orderExpr) .map((value) => `${value.includes('[') ? value : (0, utils_1.escapeReservedWords)(value)} ${orderExpr[value]}`) .join(',')}` : ''; }; /** * @ignore * */ const _buildLimitExpr = (limit) => { return Number.isInteger(limit) ? ` LIMIT ${limit}` : ''; }; /** * @ignore * */ const _buildOffsetExpr = (offset) => { return Number.isInteger(offset) ? ` OFFSET ${offset}` : ''; }; /** * @ignore * */ const _buildUseKeysExpr = (useKeys) => { return Array.isArray(useKeys) ? ` USE KEYS ${stringifyValues(useKeys)}` : ''; }; // end of SELECT expression functions // start of GROUP BY expression functions /** *@ignore */ const _buildGroupByExpr = (groupByExpr, lettingExpr, havingExpr) => { try { if ((lettingExpr || havingExpr) && !groupByExpr) { throw new exceptions_1.QueryGroupByParamsException(); } if (!groupByExpr) { return ''; } return ` ${_buildGroupBy(groupByExpr)}${_buildLetExpr(lettingExpr, 'LETTING')}${(0, exports.buildWhereExpr)(havingExpr, 'HAVING')}`; } catch (_a) { throw new exceptions_1.QueryGroupByParamsException(); } }; /** *@ignore */ const _buildGroupBy = (groupByExpr) => { return `GROUP BY ${groupByExpr .map((value) => { return `${(0, utils_1.escapeReservedWords)(value.expr)}${value.as ? ` AS ${value.as}` : ''}`; }) .join(',')}`; }; // end of GROUP BY expression functions // start of WHERE expression functions /** * Create WHERE N1QL Expressions. * {@link https://docs.couchbase.com/server/6.5/n1ql/n1ql-language-reference/where.html} * @param clause WHERE Clause * @param ignoreCase Apply ignore case * @return N1QL WHERE Expression * */ const buildWhereExpr = (expr, clause = 'WHERE', ignoreCase = false) => { return expr ? ` ${clause} ${(0, exports.buildWhereClauseExpr)('', expr, ignoreCase)}` : ''; }; exports.buildWhereExpr = buildWhereExpr; /** * Where object keys * @ignore **/ const WHERE_OBJECT_KEYS = ['$and', '$or', '$not', '$any', '$every']; const verifyWhereObjectKey = (clause) => { const keys = Object.keys(clause); let invalid; if (keys.some((key) => { if (!WHERE_OBJECT_KEYS.includes(key) && String(key).match(/^\$.+$/) && key !== '$field') { invalid = { [key]: clause[key] }; return true; } return false; })) { throw new exceptions_1.WhereClauseException(`For clause:\n${JSON.stringify(invalid, null, 2)}\nwe expect { EXPRESSION : { OPERATOR: EXPRESSION } }`); } return keys.some((key) => WHERE_OBJECT_KEYS.includes(key)); }; exports.verifyWhereObjectKey = verifyWhereObjectKey; /** * Recursive function to create WHERE N1QL expressions. * @param n1ql N1QL Query String * @param clause WHERE Clause param * @param ignoreCase Apply ignoreCase * * @return N1QL WHERE expression * */ const buildWhereClauseExpr = (n1ql, clause, ignoreCase = false) => { try { if (!(0, exports.verifyWhereObjectKey)(clause)) { return _buildFieldClauseExpr(clause, ignoreCase); } return Object.keys(clause) .map((key) => { if (dictionary_1.CollectionRangePredicateOperatorDict[key]) { return `${_buildWherePredicateRangeExpression(key, clause[key])}`; } if (Array.isArray(clause[key])) { const prefix = key === '$not' ? `${dictionary_1.LogicalOperatorDict[key]} ` : ''; const joinOp = key === '$not' ? ` AND ` : ` ${dictionary_1.LogicalOperatorDict[key]} `; return `${prefix}(${clause[key] .map((value) => (0, exports.buildWhereClauseExpr)(n1ql, value, ignoreCase)) .join(joinOp)})`; } else { return `${(0, exports.buildWhereClauseExpr)(n1ql, { [key]: clause[key] }, ignoreCase)}`; } }) .join(' AND '); } catch (exception) { if (exception instanceof exceptions_1.WhereClauseException || exception instanceof TypeError) { throw exception; } throw new exceptions_1.WhereClauseException(); } }; exports.buildWhereClauseExpr = buildWhereClauseExpr; /** * @ignore * */ const _buildFieldClauseExpr = (field, ignoreCase = false) => { try { const expr = Object.keys(field).map((value) => { var _a, _b; if (typeof field[value] === 'object' && !Array.isArray(field[value]) && !((_a = field[value]) === null || _a === void 0 ? void 0 : _a['$field'])) { return `${_buildComparisonClauseExpr(value, field[value], ignoreCase)}`; } if (value === '$field') { return (0, utils_1.escapeReservedWords)(String(field[value])); } const fieldExpr = (_b = field[value]) === null || _b === void 0 ? void 0 : _b['$field']; if (fieldExpr && typeof fieldExpr === 'string') { return `${(0, utils_1.escapeReservedWords)(value)}=${fieldExpr}`; } if (!value.includes('$')) { if (typeof field[value] === 'string') { const comparator = (0, utils_1.escapeReservedWords)(value); const toCompare = stringifyValues(field[value]); return ignoreCase ? applyIgnoreCase(ignoreCase, comparator, '=', toCompare, true) : `${comparator}=${toCompare}`; } if (typeof field[value] === 'number' || typeof field[value] === 'boolean' || Array.isArray(field[value])) { return `${(0, utils_1.escapeReservedWords)(value)}=${stringifyValues(field[value])}`; } } throw new exceptions_1.QueryOperatorNotFoundException(value); }); return expr.join(' AND '); } catch (exception) { if (exception instanceof exceptions_1.WhereClauseException || exception instanceof TypeError) { throw exception; } throw new exceptions_1.WhereClauseException(); } }; /** * @ignore * */ const _buildComparisonClauseExpr = (fieldName, comparison, ignoreCase = false) => { try { let ignore = ignoreCase; const keys = Object.keys(comparison).filter((key) => { if (key === '$ignoreCase') { const value = comparison[key]; if (typeof value !== 'boolean') { throw TypeError(`The data type of $ignoreCase must be Boolean`); } ignore = value; return false; } return true; }); const expr = keys .map((key) => { const value = comparison[key]; if (value != null) { const field = (0, utils_1.escapeReservedWords)(fieldName); if (dictionary_1.ComparisonEmptyOperatorDict.hasOwnProperty(key)) { return `${field} ${dictionary_1.ComparisonEmptyOperatorDict[key]}`; } if (dictionary_1.ComparisonSingleOperatorDict.hasOwnProperty(key)) { const operator = dictionary_1.ComparisonSingleOperatorDict[key]; const endValue = _parseEndValue(value); return applyIgnoreCase(ignore, field, operator, endValue, true); } if (dictionary_1.ComparisonSingleStringOperatorDict.hasOwnProperty(key)) { const operator = dictionary_1.ComparisonSingleStringOperatorDict[key]; const endValue = _parseEndValue(value); return applyIgnoreCase(ignore, field, operator, endValue); } if (dictionary_1.ComparisonMultipleOperatorDict.hasOwnProperty(key) && Array.isArray(value)) { return `${field} ${dictionary_1.ComparisonMultipleOperatorDict[key]} ${value .map((v) => stringifyValues(v)) .join(' AND ')}`; } if (dictionary_1.CollectionDeepSearchOperatorDict.hasOwnProperty(key)) { return `${_buildCollectionInWithinOperator(key, field, value)}`; } } throw new exceptions_1.QueryOperatorNotFoundException(key); }) .join(` AND `); return Object.keys(comparison).length > 1 ? `(${expr})` : expr; } catch (exception) { if (exception instanceof exceptions_1.WhereClauseException || exception instanceof TypeError) { throw exception; } throw new exceptions_1.WhereClauseException(); } }; /** * @ignore * */ function _parseEndValue(value) { return typeof value === 'object' && (value === null || value === void 0 ? void 0 : value['$field']) ? (0, utils_1.escapeReservedWords)(value === null || value === void 0 ? void 0 : value['$field']) : stringifyValues(value); } /** * @ignore * */ const _buildCollectionInWithinOperator = (operator, searchExpr, targetExpr, isRangePredicate = false) => { if (!(operator in dictionary_1.CollectionDeepSearchOperatorDict)) { throw new exceptions_1.CollectionInWithinExceptions(); } let target = targetExpr; switch (typeof targetExpr) { case 'object': { if (Array.isArray(target)) { target = JSON.stringify(target); } else { target = (0, exports.buildWhereClauseExpr)('', target); } break; } default: { target = isRangePredicate ? target : stringifyValues(target); } } return `${searchExpr} ${dictionary_1.CollectionDeepSearchOperatorDict[operator]} ${target}`; }; const stringifyValues = (value) => { return JSON.stringify(value).replace(/\\/gi, ''); }; /** * @ignore * */ const _buildCollectionInWithinExpression = (collection, rangePredicate) => { const entries = Object.entries(collection); if (entries.length > 1) { throw new exceptions_1.CollectionInWithinExceptions(`More than one property have been defined for range predicate '${rangePredicate}' as variable name in the same IN/WITHIN expression. You should select only one of the following '${Object.keys(collection).join(`'|'`)}'.`); } const [searchExpr, inWithinExpr] = entries[0]; const [operator, targetExpr] = Object.entries(inWithinExpr)[0]; // TODO check if target expression is required return _buildCollectionInWithinOperator(operator, searchExpr, targetExpr, true); }; /** * @ignore * */ const _buildWherePredicateRangeExpression = (operator, rangePredicate) => { const op = dictionary_1.CollectionRangePredicateOperatorDict[operator]; const keys = Object.keys(rangePredicate); if (keys.some((key) => !['$expr', '$satisfies'].includes(key))) { throw new exceptions_1.CollectionInWithinExceptions(`Range predicate operator '${operator}' only allow required properties '$expr' and '$satisfies'. Properties ['${keys .filter((key) => !['$expr', '$satisfies'].includes(key)) .join(', ')}'] are not valid.`); } const { $expr, $satisfies } = rangePredicate; return `${op} ${$expr.map((value) => _buildCollectionInWithinExpression(value, op)).join(',')} ${dictionary_1.CollectionSatisfiesOperatorDict['$satisfies']} ${(0, exports.buildWhereClauseExpr)('', $satisfies)} END`; }; // end of WHERE expression functions // start of INDEX expression functions /** * Build a INDEX N1QL query from user-specified parameters. * {@link https://docs.couchbase.com/server/6.5/n1ql/n1ql-language-reference/createindex.html} * @param collection Collection name * @param type INDEX clause types ('CREATE' | 'BUILD' | 'DROP' | 'CREATE PRIMARY') * @param on ON Clause * @param where WHERE Clause * @param usingGSI use a Global Secondary Index (GSI) * @param withExpr WITH Clause * * @return N1QL INDEX Query * */ const buildIndexExpr = (collection, type, name, on, where, usingGSI, withExpr) => { if (['BUILD', 'CREATE', 'CREATE PRIMARY'].includes(type) && on) { return `${type} INDEX \`${name}\` ON \`${collection}\`(${buildOnExpr(on)})${(0, exports.buildWhereExpr)(where)} ${usingGSI ? 'USING GSI' : ''} ${buildWithExpr(withExpr)}`; } else { return `${type} INDEX \`${collection}\`.\`${name}\`${usingGSI ? ' USING GSI' : ''}`; } }; exports.buildIndexExpr = buildIndexExpr; /** * @ignore * */ const buildOnExpr = (on) => { return on .map((value) => { return `${(0, utils_1.escapeReservedWords)(value.name)}${buildOnSortExpr(value)}`; }) .join(','); }; /** * @ignore * */ const buildOnSortExpr = (onExpr) => { if (onExpr && onExpr.hasOwnProperty('sort')) { return `["${onExpr.sort}"]`; } return ''; }; /** * @ignore * */ const buildWithExpr = (withExpr) => { let expr = ''; if (withExpr) { const resultExpr = Object.keys(withExpr) .map((value) => { switch (value) { case 'nodes': return buildWithNodesExpr(withExpr[value]); case 'defer_build': case 'num_replica': return `"${value}": ${withExpr[value]}`; default: throw new ottoman_errors_1.BuildQueryError('The WITH clause has an incorrect syntax'); } }) .join(','); expr = !!resultExpr ? `WITH {${resultExpr}}` : ''; } return expr; }; /** * @ignore * */ const buildWithNodesExpr = (withNodesExpr) => { if (withNodesExpr) { return `"nodes": ${stringifyValues(withNodesExpr)}`; } }; /** * @ignore * */ const applyIgnoreCase = (isIgnoreCase, left, operator, right, ignoreSpace) => { const op = ignoreSpace ? `${operator}` : ` ${operator} `; return isIgnoreCase ? `LOWER(${left}) ${operator} LOWER(${right})` : `${left}${op}${right}`; }; // end of INDEX expression functions //# sourceMappingURL=builders.js.map