UNPKG

@synatic/noql

Version:

Convert SQL statements to mongo queries or aggregates

1,393 lines (1,368 loc) 103 kB
const $check = require('check-types'); const $convert = require('@synatic/type-magic'); const {ObjectId} = require('bson'); // https://docs.mongodb.com/manual/meta/aggregation-quick-reference/#arithmetic-expression-operators /** * Manages mapping functions from sql to mongo * */ class AllowableFunctions { // eslint-disable-next-line jsdoc/require-returns /** * The type mapping from sql to mongo for cast */ static get _sqlTypeMapping() { return { double: 'double', string: 'string', bool: 'bool', date: 'date', int: 'int', objectId: 'objectId', long: 'long', decimal: 'decimal', varchar: 'string', datetime: 'date', time: 'date', float: 'number', char: 'string', nchar: 'string', text: 'string', }; } // eslint-disable-next-line jsdoc/require-returns /** * The type mapping from sql to mongo for cast * @returns {import("./types").JsonSchemaTypeMap} */ static get _jsonSchemaTypeMapping() { return { double: 'number', string: 'string', bool: 'boolean', date: 'date', int: 'number', integer: 'number', objectId: 'string', long: 'number', decimal: 'number', varchar: 'string', datetime: 'date', time: 'date', float: 'number', char: 'string', nchar: 'string', text: 'string', object: 'object', number: 'number', }; } /** * Gets an allowed function by name, returns null if not found * @param name * @readonly * @static * @memberof AllowableFunctions * @returns {import("./types").MongoQueryFunction|null} */ static functionByName(name) { if (!name) { return null; } const lowerName = name.toLowerCase(); return AllowableFunctions.functionMappings.find((fn) => filterFunctionsByName(fn, lowerName) ); } static functionByNameAndType(name, type) { if (!name) { return null; } if (!type) { return AllowableFunctions.functionByName(name); } const lowerName = name.toLowerCase(); const lowerType = type.toLowerCase(); return AllowableFunctions.functionMappings.find( (fn) => filterFunctionsByName(fn, lowerName) && filterFunctionsByType(fn, lowerType) ); } static functionByNameAndTypeThatAllowsQuery(name, type) { if (!name) { return null; } if (!type) { return AllowableFunctions.functionByName(name); } const lowerName = name.toLowerCase(); const lowerType = type.toLowerCase(); return AllowableFunctions.functionMappings.find( (fn) => filterFunctionsByName(fn, lowerName) && filterFunctionsByType(fn, lowerType) && fn.allowQuery ); } /** * Gets the list of function mappings between sql and mongo * @returns {import("./types").MongoQueryFunction[]} */ static get functionMappings() { return [ /* #region Columns */ { name: 'field_exists', allowQuery: true, parse: ([fieldName, doesExist]) => { return { [fieldName.$literal ? fieldName.$literal : fieldName]: { $exists: doesExist, }, }; }, jsonSchemaReturnType: 'boolean', }, /* #endregion */ /* #region Object Operators */ { name: 'parse_json', allowQuery: true, parse: (parameters) => { const jsonToParse = AllowableFunctions._getSingleParameter(parameters); if (jsonToParse.$literal) { return {$literal: JSON.parse(jsonToParse.$literal)}; } else { return JSON.parse(jsonToParse); } }, jsonSchemaReturnType: 'object', }, { name: 'merge_objects', allowQuery: true, parse: (parameters) => { return {$mergeObjects: parameters}; }, jsonSchemaReturnType: 'object', }, { name: 'empty_object', allowQuery: true, parse: (parameters) => { return {$literal: {}}; }, jsonSchemaReturnType: 'object', }, /* #endregion */ /* #region Arithmetic Operators */ { name: 'avg', allowQuery: true, type: 'function', parse: (parameters) => { return {$avg: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'abs', allowQuery: true, parse: (parameters) => { return { $abs: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'acos', allowQuery: true, parse: (parameters) => { return { $acos: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'acosh', allowQuery: true, parse: (parameters) => { return { $acosh: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'asin', allowQuery: true, parse: (parameters) => { return { $asin: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'asinh', allowQuery: true, parse: (parameters) => { return { $asinh: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'atan', allowQuery: true, parse: (parameters) => { return { $atan: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'atan2', allowQuery: true, parse: (parameters) => { return {$atan2: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'atanh', allowQuery: true, parse: (parameters) => { return { $atanh: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'binary_size', allowQuery: true, parse: (parameters) => { return { $binarySize: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'number', }, { name: 'ceil', allowQuery: true, parse: (parameters) => { return { $ceil: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'degrees_to_radians', allowQuery: true, parse: (parameters) => { return { $degreesToRadians: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'number', }, { name: 'divide', allowQuery: true, parse: (parameters) => { return {$divide: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'exp', allowQuery: true, parse: (parameters) => { return { $exp: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'floor', allowQuery: true, parse: (parameters) => { return { $floor: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'ln', allowQuery: true, parse: (parameters) => { return { $ln: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'number', }, { name: 'log', allowQuery: true, parse: (parameters) => { return {$log: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'log10', allowQuery: true, parse: (parameters) => { return { $log10: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'max', allowQuery: true, type: 'function', parse: (parameters) => { return {$max: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'min', allowQuery: true, type: 'function', parse: (parameters) => { return {$min: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'mod', allowQuery: true, parse: (parameters) => { return {$mod: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'multiply', allowQuery: true, parse: (parameters) => { return {$multiply: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'pow', allowQuery: true, parse: (parameters) => { return {$pow: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'radians_to_degrees', allowQuery: true, parse: (parameters) => { return { $radiansToDegrees: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'number', }, { name: 'rand', allowQuery: true, parse: (parameters) => { return {$rand: {}}; }, jsonSchemaReturnType: 'number', }, { name: 'round', allowQuery: true, parse: (parameters) => { return {$round: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'sin', allowQuery: true, parse: (parameters) => { return { $sin: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'sinh', allowQuery: true, parse: (parameters) => { return { $sinh: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'sqrt', allowQuery: true, parse: (parameters) => { return { $sqrt: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'subtract', allowQuery: true, type: 'function', parse: (parameters) => { return {$subtract: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'sum', allowQuery: true, type: 'function', parse: (parameters) => { return {$add: parameters}; }, jsonSchemaReturnType: 'number', }, { name: 'tan', allowQuery: true, parse: (parameters) => { return { $tan: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'tanh', allowQuery: true, parse: (parameters) => { return { $tanh: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'trunc', allowQuery: true, parse: (parameters) => { return {$trunc: parameters}; }, jsonSchemaReturnType: 'number', }, /* #endregion */ /* #region Aggregate Functions */ { name: 'sum', type: 'aggr_func', allowQuery: false, forceGroup: true, parse: (parameters) => { const firstParam = AllowableFunctions._getSingleParameter(parameters); const valueToSum = firstParam.$literal ? typeof firstParam.$literal === 'string' ? `$${firstParam.$literal}` : firstParam.$literal : firstParam; return { $sum: valueToSum, }; }, jsonSchemaReturnType: 'number', }, { name: 'avg', allowQuery: false, type: 'aggr_func', forceGroup: true, parse: (parameters) => { return { $avg: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'min', allowQuery: false, type: 'aggr_func', forceGroup: true, parse: (parameters) => { return { $min: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { name: 'max', type: 'aggr_func', allowQuery: false, forceGroup: true, parse: (parameters) => { return { $max: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'number', }, { // todo check count name: 'count', allowQuery: false, forceGroup: true, type: 'function', parse: (parameter) => { return {$sum: 1}; }, jsonSchemaReturnType: 'number', }, { // todo check count name: 'count', allowQuery: false, forceGroup: true, type: 'aggr_func', parse: (parameter) => { return {$sum: 1}; }, jsonSchemaReturnType: 'number', }, { name: 'firstn', allowQuery: false, forceGroup: true, type: 'aggr_func', parse: (parameters) => { if (!$check.array(parameters)) { throw new Error('Invalid parameters for FirstN'); } if (parameters.length < 1) { throw new Error('Invalid parameters for FirstN'); } return { $firstN: { input: $check.assigned(parameters[1]) ? '$' + AllowableFunctions._getLiteral(parameters[1]) : '$$ROOT', n: parameters[0], }, }; }, jsonSchemaReturnType: (parameters) => { if (!$check.array(parameters)) { throw new Error('Invalid parameters for substring'); } if (parameters.length < 1) { throw new Error('Invalid parameters for FirstN'); } if ($check.assigned(parameters[1])) { return { type: 'fieldName', fieldName: parameters[1].value, }; } return { type: 'jsonSchemaValue', jsonSchemaValue: 'object', isArray: true, }; }, }, { name: 'lastn', allowQuery: false, forceGroup: true, type: 'aggr_func', parse: (parameters) => { if (!$check.array(parameters)) { throw new Error('Invalid parameters for substring'); } if (parameters.length < 1) { throw new Error('Invalid parameters for LastN'); } return { $lastN: { input: $check.assigned(parameters[1]) ? '$' + AllowableFunctions._getLiteral(parameters[1]) : '$$ROOT', n: parameters[0], }, }; }, jsonSchemaReturnType: (parameters) => { if (!$check.array(parameters)) { throw new Error('Invalid parameters for substring'); } if (parameters.length < 1) { throw new Error('Invalid parameters for FirstN'); } if ($check.assigned(parameters[1])) { return { type: 'fieldName', fieldName: parameters[1].value, }; } return { type: 'jsonSchemaValue', jsonSchemaValue: 'object', isArray: true, }; }, }, // ToDo:add aggregate function with // $addToSet /* #endregion */ /* #region String Functions */ /* $strcasecmp */ { name: 'wrapParam', allowQuery: true, parse: (parameters) => { const value = parameters[0]; const forceString = parameters[1] ? parameters[1].$literal : false; let unescaped; if (typeof value === 'object' && value.$literal) { unescaped = value.$literal; } else { unescaped = value.substring(1); } unescaped = unescaped .replace(/\\"/g, '"') .replace(/\\'/g, "'") .replace(/\\\\/g, '\\'); if (forceString) { return unescaped; } return { $literal: unescaped, }; }, jsonSchemaReturnType: 'string', }, { name: 'concat', parsedName: '$concat', allowQuery: true, parse: (parameters) => { return {$concat: parameters}; }, jsonSchemaReturnType: 'string', }, { name: 'join', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for join'); if (parameters.length !== 2) { throw new Error( `Invalid parameter length for join, should be two but was ${parameters.length}` ); } const input = parameters[0]; if ($check.emptyString(input) || !$check.string(input)) { throw new Error( `The first parameter passed to join should be a non empty string but was ${input}` ); } const joinCharacter = parameters[1].$literal; if ( $check.emptyString(joinCharacter) || !$check.string(joinCharacter) ) { throw new Error( `The second parameter passed to join should be a non empty string but was ${joinCharacter}` ); } return { $reduce: { input, initialValue: '', in: { $concat: [ '$$value', { $cond: [ {$eq: ['$$value', '']}, '', joinCharacter, ], }, '$$this', ], }, }, }; }, jsonSchemaReturnType: 'string', }, { name: 'trim', allowQuery: true, parse: (parameters) => { const params = { input: AllowableFunctions._getSingleParameter( parameters ), }; if (parameters[1]) { params.chars = parameters[1]; } return {$trim: params}; }, jsonSchemaReturnType: 'string', }, { name: 'ltrim', allowQuery: true, parse: (parameters) => { const params = { input: AllowableFunctions._getSingleParameter( parameters ), }; if (parameters[1]) { params.chars = parameters[1]; } return {$ltrim: params}; }, jsonSchemaReturnType: 'string', }, { name: 'rtrim', allowQuery: true, parse: (parameters) => { const params = { input: AllowableFunctions._getSingleParameter( parameters ), }; if (parameters[1]) { params.chars = parameters[1]; } return {$rtrim: params}; }, jsonSchemaReturnType: 'string', }, { name: 'substr', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for substring'); if (parameters.length !== 3) { throw new Error( 'Invalid parameters required for substring' ); } return {$substr: parameters}; }, jsonSchemaReturnType: 'string', }, { name: 'left', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for left'); if (parameters.length !== 2) { throw new Error('Invalid parameters required for left'); } return {$substr: [parameters[0], 0, parameters[1]]}; }, jsonSchemaReturnType: 'string', }, { name: 'starts_with', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for substring'); if (parameters.length !== 2) { throw new Error('Invalid parameters starts_with'); } return { $regexMatch: { input: parameters[0], regex: {$concat: [parameters[1], {$literal: '$'}]}, }, }; }, jsonSchemaReturnType: 'boolean', }, { name: 'strpos', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for substring'); if (parameters.length !== 2) { throw new Error('Invalid parameters starts_with'); } return { $add: [{$indexOfCP: parameters}, 1], }; }, jsonSchemaReturnType: 'number', }, { name: 'locate', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for substring'); if (parameters.length !== 2) { throw new Error('Invalid parameters starts_with'); } return { $add: [{$indexOfCP: [parameters[1], parameters[0]]}, 1], }; }, jsonSchemaReturnType: 'number', }, { name: 'substr_bytes', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for substring'); if (parameters.length !== 3) { throw new Error( 'Invalid parameters required for substring' ); } return {$substrBytes: parameters}; }, jsonSchemaReturnType: 'string', }, { name: 'substr_cp', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for substring'); if (parameters.length !== 3) { throw new Error( 'Invalid parameters required for substring' ); } return {$substrCP: parameters}; }, jsonSchemaReturnType: 'string', }, { name: 'to_upper', allowQuery: true, parse: (parameters) => { return { $toUpper: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'string', }, { name: 'upper', allowQuery: true, parse: (parameters) => { return { $toUpper: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'string', }, { name: 'to_lower', allowQuery: true, parse: (parameters) => { return { $toLower: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'string', }, { name: 'lower', allowQuery: true, parse: (parameters) => { return { $toLower: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'string', }, { name: 'replace', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for substring'); if (parameters.length !== 3) { throw new Error( 'Invalid parameters required for substring' ); } return { $replaceOne: { input: parameters[0], find: parameters[1], replacement: parameters[2], }, }; }, jsonSchemaReturnType: 'string', }, { name: 'replace_all', allowQuery: true, parse: (parameters) => { if (!$check.array(parameters)) throw new Error('Invalid parameters for substring'); if (parameters.length !== 3) { throw new Error( 'Invalid parameters required for substring' ); } return { $replaceAll: { input: parameters[0], find: parameters[1], replacement: parameters[2], }, }; }, jsonSchemaReturnType: 'string', }, { name: 'strlen', allowQuery: true, parse: (parameters) => { return { $strLenBytes: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'number', }, { name: 'strlen_cp', allowQuery: true, parse: (parameters) => { return { $strLenCP: AllowableFunctions._getSingleParameter(parameters), }; }, jsonSchemaReturnType: 'number', }, { name: 'split', allowQuery: true, parse: (parameters) => { const params = [ AllowableFunctions._getSingleParameter(parameters), ]; if ( $check.array(parameters) && parameters[1] && parameters[1].$literal !== null ) { params.push(parameters[1]); } return {$split: params}; }, jsonSchemaReturnType: (parameters) => { return { type: 'fieldName', fieldName: parameters[0].column, isArray: true, }; }, }, /* #endregion */ /* #region Conversion Functions */ { name: 'convert', allowQuery: true, type: 'function', parse: (parameters, depth, forceLiteralParse) => { const toSQLType = parameters[1] ? parameters[1].$literal || parameters[1] : null; if (!$check.string(toSQLType)) { throw new Error('Type not specified for convert'); } const toType = AllowableFunctions._sqlTypeMapping[ toSQLType.toLowerCase() ] || toSQLType; if (!toType) { throw new Error(`Invalid type for convert:${toType}`); } return {$convert: {input: parameters[0], to: toType}}; }, jsonSchemaReturnType: (parameters) => { const toSQLType = parameters[1] ? parameters[1].$literal || parameters[1].value : null; if (!$check.string(toSQLType)) { throw new Error('Type not specified for convert'); } const toType = AllowableFunctions._jsonSchemaTypeMapping[ toSQLType.toLowerCase() ] || toSQLType; if (!toType) { throw new Error(`Invalid type for convert:${toType}`); } return { type: 'jsonSchemaValue', jsonSchemaValue: toType, isArray: false, }; }, }, { name: 'to_date', allowQuery: true, parse: (parameters, depth, forceLiteralParse) => { const paramVal = AllowableFunctions._getSingleParameter(parameters); if ( forceLiteralParse && AllowableFunctions._isLiteral(paramVal) ) { try { return $convert.convert( AllowableFunctions._getLiteral(paramVal), 'date' ); } catch (exp) { throw new Error( `Error converting ${AllowableFunctions._getLiteral( paramVal )} to date` ); } } else { return { $toDate: AllowableFunctions._getSingleParameter( parameters ), }; } }, jsonSchemaReturnType: 'date', }, { name: 'to_string', allowQuery: true, parse: (parameters, depth, forceLiteralParse) => { const paramVal = AllowableFunctions._getSingleParameter(parameters); if ( forceLiteralParse && AllowableFunctions._isLiteral(paramVal) ) { try { return $convert.convert( AllowableFunctions._getLiteral(paramVal), 'string' ); } catch (exp) { throw new Error( `Error converting ${AllowableFunctions._getLiteral( paramVal )} to string` ); } } else { return {$toString: paramVal}; } }, jsonSchemaReturnType: 'string', }, { name: 'to_decimal', allowQuery: true, parse: (parameters, depth, forceLiteralParse) => { const paramVal = AllowableFunctions._getSingleParameter(parameters); if ( forceLiteralParse && AllowableFunctions._isLiteral(paramVal) ) { try { return $convert.convert( AllowableFunctions._getLiteral(paramVal), 'number' ); } catch (exp) { throw new Error( `Error converting ${AllowableFunctions._getLiteral( paramVal )} to number` ); } } else { return {$toDecimal: paramVal}; } }, jsonSchemaReturnType: 'number', }, { name: 'to_double', allowQuery: true, parse: (parameters, depth, forceLiteralParse) => { const paramVal = AllowableFunctions._getSingleParameter(parameters); if ( forceLiteralParse && AllowableFunctions._isLiteral(paramVal) ) { try { return $convert.convert( AllowableFunctions._getLiteral(paramVal), 'number' ); } catch (exp) { throw new Error( `Error converting ${AllowableFunctions._getLiteral( paramVal )} to number` ); } } else { return {$toDouble: paramVal}; } }, jsonSchemaReturnType: 'number', }, { name: 'to_int', allowQuery: true, parse: (parameters, depth, forceLiteralParse) => { const paramVal = AllowableFunctions._getSingleParameter(parameters); if ( forceLiteralParse && AllowableFunctions._isLiteral(paramVal) ) { try { return $convert.convert( AllowableFunctions._getLiteral(paramVal), 'integer' ); } catch (exp) { throw new Error( `Error converting ${AllowableFunctions._getLiteral( paramVal )} to integer` ); } } else { return {$toInt: paramVal}; } }, jsonSchemaReturnType: 'number', }, { name: 'to_long', allowQuery: true, parse: (parameters, depth, forceLiteralParse) => { const paramVal = AllowableFunctions._getSingleParameter(parameters); if ( forceLiteralParse && AllowableFunctions._isLiteral(paramVal) ) { try { return $convert.convert( AllowableFunctions._getLiteral(paramVal), 'integer' ); } catch (exp) { throw new Error( `Error converting ${AllowableFunctions._getLiteral( paramVal )} to integer` ); } } else { return {$toLong: paramVal}; } }, jsonSchemaReturnType: 'number', }, { name: 'to_bool', allowQuery: true, parse: (parameters, depth, forceLiteralParse) => { const paramVal = AllowableFunctions._getSingleParameter(parameters); if ( forceLiteralParse && AllowableFunctions._isLiteral(paramVal) ) { try { return $convert.convert( AllowableFunctions._getLiteral(paramVal), 'boolean' ); } catch (exp) { throw new Error( `Error converting ${AllowableFunctions._getLiteral( paramVal )} to boolean` ); } } else { return {$toBool: paramVal}; } }, jsonSchemaReturnType: 'boolean', }, { name: 'to_objectid', allowQuery: true, parse: (parameters, depth, forceLiteralParse) => { const paramVal = AllowableFunctions._getSingleParameter(parameters); if ( forceLiteralParse && AllowableFunctions._isLiteral(paramVal) ) { try { const param = AllowableFunctions._getLiteral(paramVal); return new ObjectId(param); } catch (exp) { throw new Error( `Error converting ${AllowableFunctions._getLiteral( paramVal )} to ObjectId` ); } } else { return {$toObjectId: paramVal}; } }, jsonSchemaReturnType: 'string', }, { name: 'typeof', allowQuery: true, parse: (parameters) => { return { $type: AllowableFunctions._getSingleParameter( parameters ), }; }, jsonSchemaReturnType: 'string', }, { name: 'ifnull', aliases: ['coalesce'], allowQuery: true, parse: (parameters) => { return {$ifNull: parameters}; }, jsonSchemaReturnType: (parameters) => { if (parameters[0].type === 'column_ref') { return { type: 'fieldName', fieldName: parameters[0].column, }; } if (parameters[0].type === 'null') { if ( [ 'single_quote_string', 'string', 'backticks_quote_string', ].includes(parameters[1].type) ) { return { type: 'jsonSchemaValue', jsonSchemaValue: 'string', }; }