ottoman
Version:
Ottoman Couchbase ODM
496 lines • 19.1 kB
JavaScript
;
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