UNPKG

arrow-store

Version:
1,361 lines (1,352 loc) 125 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); class ObjectTokenVisitor { visit(query, index, tokens) { if (query.charCodeAt(index) >= 0 && query.charCodeAt(index) <= 32) { return index + 1; } let positionOffset = 0; for (let i = 0; i < 3; i++) { if (index + i >= query.length || query[index + i] !== '.') { positionOffset = 0; break; } positionOffset++; } if (ObjectTokenVisitor._literalStartRegex.test(query[index + positionOffset])) { let endIndex = positionOffset + index + 1; while (endIndex < query.length && ObjectTokenVisitor._literalRegex.test(query[endIndex])) { endIndex++; } const value = query.slice(positionOffset + index, endIndex); switch (value) { case "null": { if (positionOffset !== 0) { throw Error('Spread operator is not supported with the null-value'); } tokens.push({ tokenType: "NullValue", index: index, length: endIndex - index }); break; } case "undefined": { if (positionOffset !== 0) { throw Error('Spread operator is not supported with the undefined-value'); } tokens.push({ tokenType: "Undefined", index: index, length: endIndex - index }); break; } default: { tokens.push({ tokenType: "Object", index: index, length: endIndex - index }); break; } } index = endIndex; } return index; } } ObjectTokenVisitor._literalStartRegex = /[a-zA-Z_$]/; ObjectTokenVisitor._literalRegex = /[a-zA-Z_$0-9.?!]/; class CommaTokenVisitor { visit(query, index, tokens) { if (query[index] === ',') { tokens.push({ tokenType: "CommaSeparator", index: index++, length: 1 }); } return index; } } class BooleanValueTokenVisitor { visit(query, index, tokens) { let endIndex = index; let length = 0; if (index + 'true'.length <= query.length && query.slice(index, index + 'true'.length) === 'true') { length = 'true'.length; } else if (index + 'false'.length <= query.length && query.slice(index, index + 'false'.length) === 'false') { length = 'false'.length; } if (length != 0) { tokens.push({ tokenType: "ConstantValue", index: index, length: length }); endIndex = index + length; } return endIndex; } } class StringTokenVisitor { visit(query, index, tokens) { let endIndex = index; let isEnclosed = false; if (query[index] === `'` || query[index] === '`' || query[index] === '"') { const closeStringChar = query[index]; let escape = false; while (endIndex < query.length) { const nextChar = query[++endIndex]; if (nextChar === '\\') { escape = true; continue; } if (escape) { escape = false; continue; } if (nextChar === closeStringChar) { isEnclosed = true; break; } } if (!isEnclosed) { throw Error(`Enclosing quote was expected`); } tokens.push({ tokenType: "ConstantValue", index: index + 1, length: endIndex - index - 1 }); endIndex++; } return endIndex; } } class NumberTokenVisitor { visit(query, index, tokens) { let endIndex = index; if (/[0-9]/.test(query[endIndex])) { for (; endIndex < query.length; endIndex++) { if (!NumberTokenVisitor._NumberRegex.test(query[endIndex])) { break; } } tokens.push({ tokenType: "ConstantValue", index: index, length: endIndex - index }); } return endIndex; } } NumberTokenVisitor._NumberRegex = /[0-9e.]/; class GroupTokenVisitor { visit(query, index, tokens) { if (query[index] === '(') { tokens.push({ index: index++, tokenType: 'GroupStart', length: 1 }); } else if (query[index] === ')') { tokens.push({ index: index++, tokenType: 'GroupEnd', length: 1 }); } return index; } } class LogicalOperatorTokenVisitor { visit(query, index, tokens) { if (query.slice(index, index + 2) === '||' || query.slice(index, index + 2) === '&&') { tokens.push({ tokenType: query[index] === '|' ? 'OR' : 'AND', index: index, length: 2 }); index += 2; } return index; } } class CompareOperatorVisitor { visit(query, index, tokens) { let tokenType; let nextIndex = index; switch (query[index]) { case '!': { if (query[nextIndex + 1] === '=') { nextIndex++; tokenType = 'NotEquals'; if (query[nextIndex + 1] === '=') { nextIndex++; } } break; } case '=': { if (query[nextIndex + 1] === '>') { tokenType = 'LambdaInitializer'; nextIndex++; } else if (query[nextIndex + 1] === '=') { nextIndex++; tokenType = "Equals"; if (query[nextIndex + 1] === '=') { nextIndex++; } } else { tokenType = 'Assign'; } break; } case '>': { if (query[nextIndex + 1] === '=') { nextIndex++; tokenType = 'GreaterThanOrEquals'; } else { tokenType = 'GreaterThan'; } break; } case '<': { if (query[nextIndex + 1] === '=') { nextIndex++; tokenType = 'LessThanOrEquals'; } else { tokenType = 'LessThanOrEquals'; } break; } } if (!!tokenType) { nextIndex++; tokens.push({ tokenType: tokenType, index: index, length: nextIndex - index }); } return nextIndex; } } class NotTokenVisitor { visit(query, index, tokens) { if (query[index] === '!' && query[index + 1] !== '=') { tokens.push({ index: index++, tokenType: "Inverse", length: 1 }); } return index; } } class MathOperatorTokenVisitor { visit(query, index, tokens) { if (MathOperatorTokenVisitor._MathOperators.includes(query[index])) { tokens.push({ tokenType: "MathOperator", index: index++, length: 1 }); } return index; } } MathOperatorTokenVisitor._MathOperators = ["/", "*", "-", "+"]; class ArrowFunctionLexer { constructor() { } tokenize(query) { if (!query) { return []; } query = query.trim(); if (query.length === 0) { return []; } const tokens = []; for (let i = 0; i < query.length;) { const next = ArrowFunctionLexer._visit(query, i, tokens); if (next <= i) { throw Error(`Infinite loop detected`); } i = next; } return tokens; } static _visit(query, currentIndex, tokens) { if (!query || currentIndex > query.length) { return query.length; } for (let i = 0; i < this._TokenVisitors.length; i++) { const processedUpTo = this._TokenVisitors[i].visit(query, currentIndex, tokens); if (processedUpTo === currentIndex) { continue; } if (processedUpTo > currentIndex) { return processedUpTo; } throw Error(`The token visitor moved the index to the back. Query: "${query}", at ${currentIndex}`); } throw Error(`Failed to process the query ..."${query.slice(currentIndex, query.length)}" at ${currentIndex}-position`); } } ArrowFunctionLexer._TokenVisitors = [ new CompareOperatorVisitor(), new MathOperatorTokenVisitor(), new GroupTokenVisitor(), new LogicalOperatorTokenVisitor(), new NotTokenVisitor(), new NumberTokenVisitor(), new StringTokenVisitor(), new BooleanValueTokenVisitor(), new CommaTokenVisitor(), new ObjectTokenVisitor() ]; ArrowFunctionLexer.Instance = new ArrowFunctionLexer(); class ParserNode { } class ObjectAccessorNode extends ParserNode { constructor(value) { super(); this._value = value; } get value() { return this._value; } get nodeType() { return "ObjectAccessor"; } } class FunctionExpressionNode extends ParserNode { constructor(functionName, instance, args) { super(); this._functionName = functionName; this._instance = instance; this._args = args; } get functionName() { return this._functionName; } get instance() { return this._instance; } get args() { if (this._args.nodeType === "Arguments") { return this._args.args; } return [this._args]; } get nodeType() { return "Function"; } } class BooleanExpressionNode extends ParserNode { constructor(operator, left, right) { super(); this._booleanOperator = operator; this._leftOperand = left; this._rightOperand = right; } get operator() { return this._booleanOperator; } get left() { return this._leftOperand; } get right() { return this._rightOperand; } get nodeType() { return "BooleanOperation"; } } class CompareExpressionNode extends ParserNode { constructor(comparisonOperator, left, right) { super(); this._leftOperand = left; this._rightOperand = right; this._comparisonOperator = comparisonOperator; } get operator() { return this._comparisonOperator; } get left() { return this._leftOperand; } get right() { return this._rightOperand; } get nodeType() { return "CompareOperation"; } } class LambdaExpressionNode extends ParserNode { constructor(parameter, body) { super(); this._parameter = parameter; this._body = body; } get parameter() { return this._parameter; } get body() { return this._body; } get nodeType() { return "LambdaExpression"; } } class AssignExpressionNode extends ParserNode { constructor(member, value) { super(); this._member = member; this._value = value; } get member() { if (this._member.nodeType !== "ObjectAccessor") { throw Error(`The left expression must be an object accessor`); } return this._member; } get value() { return this._value; } get nodeType() { return "Assign"; } } class IncrementExpressionNode extends ParserNode { constructor(target, incrementValue) { super(); this._target = target; this._incrementValue = incrementValue; } get member() { return this._target; } get incrementValue() { return this._incrementValue; } get nodeType() { return "Increment"; } } class MathExpressionNode extends ParserNode { constructor(left, right, operator) { super(); this._left = left; this._right = right; this._operator = operator; } get left() { return this._left; } get right() { return this._right; } get operator() { return this._operator; } get nodeType() { return "MathOperation"; } } class ArgumentsExpressionNode extends ParserNode { constructor(args) { super(); this._args = args; } get args() { return this._args; } get nodeType() { return "Arguments"; } } class InverseExpressionNode extends ParserNode { constructor(body) { super(); this.body = body; } get nodeType() { return "Inverse"; } } class GroupExpressionNode extends ParserNode { constructor(bodyNode) { super(); this._bodyNode = bodyNode; } get body() { return this._bodyNode; } get nodeType() { return "GroupExpression"; } } class NullValueNode extends ParserNode { get nodeType() { return "NullValue"; } } class UndefinedValueNode extends ParserNode { get nodeType() { return "UndefinedValue"; } } class ConstantValueNode extends ParserNode { constructor(value) { super(); this._value = value; } get value() { return this._value; } get nodeType() { return "ConstantValue"; } } class AttributeExistsNode extends ParserNode { constructor(accessorExpression) { super(); this._attribute = accessorExpression; } get attribute() { return this._attribute; } get nodeType() { return "AttributeExists"; } } class AttributeNotExistsNode extends ParserNode { constructor(accessorExpression) { super(); this._attribute = accessorExpression; } get attribute() { return this._attribute; } get nodeType() { return "AttributeNotExists"; } } class SizeExpressionNode extends ParserNode { constructor(instanceAccessor) { super(); this._instanceAccessor = instanceAccessor; } get instanceAccessor() { return this._instanceAccessor; } get nodeType() { return "Size"; } } class SetWhenNotExistsExpression extends ParserNode { constructor(memberExistExpr, updateExpr) { super(); this._memberExistExpr = memberExistExpr; this._updateExpr = updateExpr; } get conditionExpression() { return this._memberExistExpr; } get updateExpression() { return this._updateExpr; } get nodeType() { return "SetWhenNotExists"; } } class NodeExpressionIterator { constructor(query, tokens) { this.index = 0; this.query = query; this.tokens = tokens; } getCurrentToken() { if (this.index > this.tokens.length - 1) { return { tokenType: "Terminator", index: this.index, length: 0 }; } if (this.index === this.tokens.length) { throw Error(`Never reachable`); } return this.tokens[this.index]; } stringify(token) { if (!token) { throw Error(`The token parameter is missing`); } if (token.index >= this.query.length || token.index + token.length > this.query.length) { throw Error(`The token length is greater than the query itself. Query:${this.query}, Token: ${token.index}-${token.length}`); } if (token.length === 0) { return ''; } return this.query.slice(token.index, token.index + token.length); } } const _comparisonTokens = ['Equals', 'NotEquals', 'GreaterThan', 'GreaterThanOrEquals', 'LessThan', 'LessThanOrEquals']; class WhereCauseExpressionParser { constructor() { } parse(query, tokens) { const iterator = new NodeExpressionIterator(query, tokens); return this._lambda(iterator); } _lambda(iterator) { const left = this._or(iterator); const token = iterator.getCurrentToken(); if (token.tokenType === 'LambdaInitializer') { iterator.index++; const right = this._lambda(iterator); return new LambdaExpressionNode(left, right); } return left; } _or(iterator) { const left = this._and(iterator); const token = iterator.getCurrentToken(); if (token.tokenType === 'OR') { iterator.index++; const right = this._or(iterator); return new BooleanExpressionNode('OR', left, right); } return left; } _and(iterator) { const left = this._compare(iterator); const token = iterator.getCurrentToken(); if (token.tokenType === 'AND') { iterator.index++; const right = this._and(iterator); return new BooleanExpressionNode('AND', left, right); } return left; } _compare(iterator) { const left = this._argument(iterator); const token = iterator.getCurrentToken(); if (_comparisonTokens.findIndex(x => x === token.tokenType) >= 0) { iterator.index++; const right = this._compare(iterator); return new CompareExpressionNode(token.tokenType, left, right); } return left; } _argument(iterator) { const left = this._function(iterator); const token = iterator.getCurrentToken(); if (token.tokenType === "CommaSeparator") { iterator.index++; const rightArgs = this._argument(iterator); const leftArgs = left.nodeType === "Arguments" ? left.args : [left]; return new ArgumentsExpressionNode([...leftArgs, rightArgs]); } return left; } _function(iterator) { const left = this._value(iterator); const token = iterator.getCurrentToken(); if (token.tokenType === "GroupStart") { iterator.index++; const argumentsNode = this._argument(iterator); const closingToken = iterator.getCurrentToken(); if (closingToken.tokenType !== "GroupEnd") { throw Error(`A closing parenthesis token is expected in function's arguments node: ${iterator.stringify(closingToken)}`); } iterator.index++; const memberSegments = left.value.split('.'); const functionName = memberSegments[memberSegments.length - 1]; const instanceAccessorNode = new ObjectAccessorNode(memberSegments.slice(0, memberSegments.length - 1).join('.')); return new FunctionExpressionNode(functionName, instanceAccessorNode, argumentsNode); } return left; } _value(iterator) { const left = this._groupStart(iterator); if (!!left) { return left; } const token = iterator.getCurrentToken(); switch (token.tokenType) { case "Object": { iterator.index++; return new ObjectAccessorNode(iterator.stringify(token)); } case "NullValue": { iterator.index++; return new NullValueNode(); } case "ConstantValue": { iterator.index++; return new ConstantValueNode(iterator.stringify(token)); } case "Undefined": { iterator.index++; return new UndefinedValueNode(); } case "MathOperator": { const operator = iterator.stringify(token); if (operator !== '-') { throw Error(`Only negate operator is supported within the where-predicate: '${operator}'`); } iterator.index++; const value = this._value(iterator); if (value.nodeType !== "ConstantValue") { throw Error(`Only constant numeric value is supported to negate: '${value.nodeType}'`); } const constValue = value; return new ConstantValueNode(`-${constValue.value}`); } } throw Error(`Expected an object accessor or a value token, but received ${iterator.stringify(token)}`); } _groupStart(iterator) { const left = this._inverse(iterator); const token = iterator.getCurrentToken(); if (token.tokenType === "GroupStart") { iterator.index++; const groupNode = new GroupExpressionNode(this._lambda(iterator)); const groupEndToken = iterator.getCurrentToken(); if (groupEndToken.tokenType !== "GroupEnd") { throw Error(`No closing parenthesis was found for the group expression: ${iterator.stringify(groupEndToken)}`); } iterator.index++; return groupNode; } return left; } _inverse(iterator) { const token = iterator.getCurrentToken(); if (token.tokenType === "Inverse") { iterator.index++; return new InverseExpressionNode(this._function(iterator)); } return null; } } WhereCauseExpressionParser.Instance = new WhereCauseExpressionParser(); class ExpressionTransformerBase { constructor(attributeNamePrefix, attributePathSchema) { if (attributeNamePrefix) { this._attributeNamePrefix = attributeNamePrefix; } else { this._attributeNamePrefix = 'attr_name'; } this._attributePathSchema = attributePathSchema; } getOrSetAttributeReference(expression, context) { if (expression.nodeType === "ObjectAccessor") { const accessorPath = expression.value; const segments = accessorPath.split(/[.]/g); while (segments.length !== 0 && !segments[0]) { segments.shift(); } if (segments[0] === context.rootParameterName) { const attributePath = this.getOrSetAttributePath(segments.slice(1, segments.length).join('.'), context); if (!attributePath) { throw Error(`Failed to find the member attribute path: '${accessorPath}'`); } return { isRecordAccessor: true, value: attributePath }; } if (context.contextParameterName && segments[0] === context.contextParameterName) { return { value: this.evaluateContextValue(segments.slice(1, segments.length), context.contextParameters), isRecordAccessor: false }; } throw Error(`The member accessor must be a record member accessor or a context value accessor`); } else if (expression.nodeType === "ConstantValue") { return { value: expression.value, isRecordAccessor: false }; } throw Error(`Failed to get the expression's value: ${expression.nodeType}`); } getAttributeTypeByPath(attributePath, context) { if (!attributePath) { throw Error(`The attribute path is missing`); } const schema = this._attributePathSchema.get(attributePath); if (!schema) { throw Error(`The attribute schema was not found for the given path '${attributePath}'`); } return schema.lastChildAttributeType; } getOrSetAttributeValue(accessorValue, attributeType, context) { if (accessorValue.isRecordAccessor) { throw Error(`Only constant or context values are supported`); } const value = accessorValue.value; if (value === undefined) { throw Error(`The value is undefined`); } let typeRef; let attributeValue = {}; if (value === null) { typeRef = "NULL"; attributeValue.NULL = true; } else { switch (attributeType) { case "SS": { const ss = JSON.parse(value); if (!Array.isArray(ss)) { throw Error(`The String Set was expected: ${value}`); } typeRef = `SS${value}`; attributeValue.SS = ss; break; } case "NS": { const ns = JSON.parse(value); if (!Array.isArray(ns)) { throw Error(`The Numbers Set was expected: ${value}`); } typeRef = `NS${value}`; attributeValue.NS = ns; break; } case "S": { typeRef = `S${value}`; attributeValue.S = value; break; } case "N": { typeRef = `N${value}`; const numberValue = parseFloat(value); if (isNaN(numberValue)) { throw Error(`Not a number ${value}`); } attributeValue.N = value; break; } case "BOOL": { typeRef = `BOOL${value}`; if (value === "true") { attributeValue.BOOL = true; } else if (value === "false") { attributeValue.BOOL = false; } else { throw Error(`Invalid boolean value ${value}`); } break; } default: { throw Error(`Not supported attribute type ${attributeValue}`); } } } let existingAttributeName = context.attributeValueAliases.get(typeRef); if (!existingAttributeName) { existingAttributeName = `:${this._attributeNamePrefix}_${context.attributeValues.size + 1}`; context.attributeValues.set(existingAttributeName, attributeValue); context.attributeValueAliases.set(typeRef, existingAttributeName); } return existingAttributeName; } getOrSetAttributePath(memberPath, context) { let attributeNamePath = context.attributeNameAliases.get(memberPath); if (!attributeNamePath) { const memberSchema = context.recordSchema.get(memberPath); if (!memberSchema) { return null; } const aliases = []; let nestedSchema = memberSchema; while (nestedSchema) { const accessorSegments = nestedSchema.attributeName.split(/[.]/g); for (let i = 0; i < accessorSegments.length; i++) { const attributeName = accessorSegments[i]; let attributeRef = context.attributeNames.get(attributeName); if (!attributeRef) { attributeRef = `#${this._attributeNamePrefix}_${context.attributeNames.size}`; context.attributeNames.set(attributeName, attributeRef); } aliases.push(attributeRef); } nestedSchema = nestedSchema.nested; } attributeNamePath = { accessor: aliases.join('.'), schema: memberSchema }; context.attributeNameAliases.set(memberPath, attributeNamePath); } if (!this._attributePathSchema.get(attributeNamePath.accessor)) { this._attributePathSchema.set(attributeNamePath.accessor, attributeNamePath.schema); } return attributeNamePath.accessor; } evaluateContextValue(accessors, contextParameters) { if (contextParameters === undefined) { throw Error(`Context parameters value is undefined`); } if (accessors.length === 0) { if (Array.isArray(contextParameters)) { const array = []; for (let i in contextParameters) { array.push(contextParameters[i].toString()); } return JSON.stringify(array); } return contextParameters.toString(); } contextParameters = contextParameters[accessors[0]]; return this.evaluateContextValue(accessors.slice(1, accessors.length), contextParameters); } } class WhereCauseExpressionTransformer extends ExpressionTransformerBase { constructor(attributeNamePrefix, attributeNames, attributeNameAliases, attributeValues, attributeValueAliases) { super(attributeNamePrefix, new Map()); this._attributeNames = attributeNames; this._attributeNameAliases = attributeNameAliases; this._attributeValues = attributeValues; this._attributeValueAliases = attributeValueAliases; } transform(recordSchema, expression, context) { const ctx = { stack: [], contextParameters: context, recordSchema: recordSchema, attributeNames: this._attributeNames, attributeNameAliases: this._attributeNameAliases, attributeValues: this._attributeValues, attributeValueAliases: this._attributeValueAliases }; this._visitRootNode(expression, ctx); if (ctx.stack.length !== 1) { throw Error(`Expression parsing failed. Only 1 element must left in the stack after processing: \"${ctx.stack.join(', ')}\"`); } return ctx.stack.pop(); } _visitRootNode(expression, context) { switch (expression.nodeType) { case "LambdaExpression": { this._visitLambda(expression, context); break; } case "Inverse": { this._visitInverse(expression, context); break; } case "GroupExpression": { this._visitGroup(expression, context); break; } case "BooleanOperation": { this._visitBoolean(expression, context); break; } case "CompareOperation": { this._visitCompare(expression, context); break; } case "Function": { this._visitFunction(expression, context); break; } case "AttributeExists": { this._visitAttributeExists(expression.attribute, true, context); break; } case "AttributeNotExists": { this._visitAttributeExists(expression.attribute, false, context); break; } case "ObjectAccessor": { const expandedExpr = this._tryExpandSyntaxSugar(expression, context); if (expandedExpr.nodeType === "ObjectAccessor") { throw Error(`The expression ${expandedExpr.nodeType} is not a boolean value check or attribute_exists: ${expression.value}`); } this._visitRootNode(expandedExpr, context); break; } default: { throw Error(`Unknown expression type ${expression.nodeType}`); } } } _visitLambda(lambdaExp, context) { const args = []; switch (lambdaExp.parameter.nodeType) { case "ObjectAccessor": { args.push(lambdaExp.parameter.value); break; } case "GroupExpression": { const body = lambdaExp.parameter.body; if (body.nodeType === "Arguments") { const argNodes = body.args; for (let i = 0; i < argNodes.length; i++) { if (argNodes[i].nodeType !== "ObjectAccessor") { throw Error(`The arrow function root argument must be an object`); } const argAccessor = argNodes[i]; args.push(argAccessor.value); } } else if (body.nodeType === "ObjectAccessor") { args.push(body.value); } break; } default: { throw Error(`The arrow function must have at least one root element`); } } if (args.length === 2) { context.contextParameterName = args[1]; } else if (args.length === 0) { throw Error(`No context and root parameter names`); } context.rootParameterName = args[0]; this._visitRootNode(lambdaExp.body, context); } _visitBoolean(expression, context) { this._visitRootNode(this._tryExpandSyntaxSugar(expression.left, context), context); if (context.stack.length !== 1) { throw Error(`Failed to process the left boolean argument. One stack element was expected: ${context.stack.join(', ')}`); } const left = context.stack.pop(); this._visitRootNode(this._tryExpandSyntaxSugar(expression.right, context), context); if (context.stack.length !== 1) { throw Error(`Failed to process the right boolean argument. One stack element was expected: ${context.stack.join(', ')}`); } const right = context.stack.pop(); context.stack.push([left, expression.operator, right].join(' ')); } _visitCompare(expression, context) { let operator; switch (expression.operator) { case "GreaterThan": { operator = ">"; break; } case "GreaterThanOrEquals": { operator = ">="; break; } case "LessThan": { operator = "<"; break; } case "LessThanOrEquals": { operator = "<"; break; } case "Equals": { operator = "="; break; } case "NotEquals": { operator = "<>"; break; } default: { throw Error(`Not supported compare operator: ${expression.operator}`); } } const leftExpr = this._tryExpandAccessorFunc(expression.left, context); const rightExpr = this._tryExpandAccessorFunc(expression.right, context); const left = this._evaluateAsAttributeReference(leftExpr, rightExpr, context); const right = this._evaluateAsAttributeReference(rightExpr, leftExpr, context); if (context.stack.length !== 0) { throw Error(`Failed to process the compare expression. No elements must be in the stack`); } context.stack.push([left, operator, right].join(' ')); } _visitFunction(expression, context) { const instanceAccessor = this.getOrSetAttributeReference(expression.instance, context); if (!instanceAccessor.isRecordAccessor) { throw Error(`Could not apply the "contains"-function to a non-record member`); } if (!instanceAccessor.value) { throw Error(`Failed to evaluate the record's member schema`); } const instanceAttrType = this.getAttributeTypeByPath(instanceAccessor.value, context); const argValues = expression.args.map(arg => this.getOrSetAttributeReference(arg, context)); switch (expression.functionName) { case String.prototype.includes.name: { // contains(RECORD_DATA.SomeString, "SomeValue") // contains(RECORD_DATA.SomeSet, {S: "Test"}) if (instanceAttrType !== "SS" && instanceAttrType !== "S" && instanceAttrType !== "NS") { throw Error(`Not supported member schema type for the "contains"-function. String and Sets are supported only`); } let setItemType = instanceAttrType; if (setItemType === "SS") { setItemType = "S"; } else if (setItemType == "NS") { setItemType = "N"; } if (argValues.length !== 1 || argValues[0].isRecordAccessor) { throw Error(`Only one constant argument is allowed in "contains"-operation`); } const argValueRef = this.getOrSetAttributeValue(argValues[0], setItemType, context); context.stack.push(`contains(${instanceAccessor.value}, ${argValueRef})`); break; } case String.prototype.startsWith.name: { // begins_with(RECORD_DATA.SomeString, "SomeValue") // begins_with(RECORD_DATA.SomeSet) if (instanceAttrType !== "S") { throw Error(`Not supported member schema type for the "begins_with"-function. String type is supported only`); } if (argValues.length !== 1 || argValues[0].isRecordAccessor) { throw Error(`Only one constant argument value is allowed in "begins_with"-operation`); } const argValueRef = this.getOrSetAttributeValue(argValues[0], instanceAttrType, context); context.stack.push(`begins_with(${instanceAccessor.value}, ${argValueRef})`); break; } default: { throw Error(`Not supported function: "${expression.functionName}"`); } } } _visitGroup(expression, context) { const bodyNode = this._tryExpandSyntaxSugar(expression.body, context); this._visitRootNode(bodyNode, context); if (context.stack.length !== 1) { throw Error(`Failed to process the group body expression. Only one element must be in the stack after processing: ${context.stack.join(', ')}`); } const body = context.stack.pop(); context.stack.push(`(${body})`); } _visitInverse(expression, context) { const inversed = this._expandInverse(expression, context, 0); if (inversed.nodeType === "Inverse") { this._visitRootNode(inversed.body, context); } else { this._visitRootNode(inversed, context); } if (context.stack.length !== 1) { throw Error(`Failed to process the inverse-expression. One element must be in the stack`); } const body = context.stack.pop(); if (inversed.nodeType === "Inverse") { context.stack.push(`not ${body}`); } else { context.stack.push(body); } } _visitAttributeExists(expression, exists, context) { const segments = expression.value.split(/[.]/g); if (context.rootParameterName !== segments[0]) { throw Error(`An attribute accessor was expected`); } const attributePath = this.getOrSetAttributePath(segments.slice(1, segments.length).join('.'), context); if (exists) { context.stack.push(`attribute_exists(${attributePath})`); } else { context.stack.push(`attribute_not_exists(${attributePath})`); } } _tryExpandSyntaxSugar(expression, context) { if (expression.nodeType === "ObjectAccessor") { const segments = expression.value.split(/[.]/g); if (context.rootParameterName === segments[0]) { const attributePath = this.getOrSetAttributePath(segments.slice(1, segments.length).join('.'), context); if (attributePath === null) { throw Error(`No member schema was found for the ${segments.join('.')}-member`); } const attributeType = this.getAttributeTypeByPath(attributePath, context); if (attributeType === "BOOL") { return new CompareExpressionNode("Equals", expression, new ConstantValueNode('true')); } return new AttributeExistsNode(expression); } throw Error(`The record schema accessor was expected`); } return expression; } _tryExpandAccessorFunc(expression, context) { if (expression.nodeType === 'ObjectAccessor') { const memberPath = expression.value; const accessorSegments = memberPath.split(/[.]/g); if (accessorSegments.length > 1 && accessorSegments[0] === context.rootParameterName) { let attributePath = this.getOrSetAttributePath(accessorSegments.join('.'), context); if (attributePath === null && accessorSegments.pop() === 'length' && accessorSegments.length > 1) { const slicedAccessor = accessorSegments.join('.'); attributePath = this.getOrSetAttributePath(accessorSegments.slice(1, accessorSegments.length).join('.'), context); if (attributePath !== null) { return new SizeExpressionNode(new ObjectAccessorNode(slicedAccessor)); } } } } return expression; } _expandInverse(expression, context, inversedTimes) { if (expression.nodeType === "Inverse") { return this._expandInverse(expression.body, context, inversedTimes + 1); } let adjusted = this._tryExpandSyntaxSugar(expression, context); if (adjusted.nodeType === "AttributeExists") { if (inversedTimes % 2 === 1) { const accessor = adjusted.attribute; adjusted = new AttributeNotExistsNode(accessor); } } else if (inversedTimes > 1 && adjusted.nodeType === "CompareOperation" && expression.nodeType === "ObjectAccessor") { // !!x.clockDetails => attribute_exists //!!!x.clockDetails => attribute_not_exists const accessor = expression; if (inversedTimes % 2 === 0) { adjusted = new AttributeExistsNode(accessor); } else { adjusted = new AttributeNotExistsNode(accessor); } } return adjusted; } _evaluateAsAttributeReference(valueExpression, memberExpression, context) { let value; switch (valueExpression.nodeType) { case "ConstantValue": { value = { value: valueExpression.value, isRecordAccessor: false }; break; } case "ObjectAccessor": { const accessorValue = this.getOrSetAttributeReference(valueExpression, context); if (accessorValue.isRecordAccessor) { return accessorValue.value; } if (accessorValue.value === null) { return this.getOrSetAttributeValue(accessorValue, "NULL", context); } value = accessorValue; break; } case "Size": { const sizeNode = valueExpression; return `size(${this._evaluateAsAttributeReference(sizeNode.instanceAccessor, memberExpression, context)})`; } case "NullValue": { return this.getOrSetAttributeValue({ isRecordAccessor: false, value: null }, "NULL", context); } default: { throw Error(`Not supported expression type ${valueExpression.nodeType}`); } } let attributeType; switch (memberExpression.nodeType) { case "ObjectAccessor": { const accessorValue = this.getOrSetAttributeReference(memberExpression, context); if (!accessorValue.isRecordAccessor) { throw Error(`The member accessor expression must be a record member accessor`); } attributeType = this.getAttributeTypeByPath(accessorValue.value, context); break; } case "Size": { attributeType = "N"; break; } default: { throw Error(`Failed to cast the value to the member type from the given member expression. Value=${value}`); } } return this.getOrSetAttributeValue(value, attributeType, context); } } class WhenExpressionBuilder { constructor(recordId, schemaProvider, recordMapper) { this._recordId = recordId; this._schemaProvider = schemaProvider; this._recordMapper = recordMapper; this._attributeNames = new Map(); this._attributeValues = new Map(); this._attributeNameAliases = new Map(); this._attributeValueAliases = new Map(); this._conditionFilterTransformer = new WhereCauseExpressionTransformer("attr_name", this._attributeNames, this._attributeNameAliases, this._attributeValues, this._attributeValueAliases); } get attributeNames() { return this._attributeNames; } get attributeNameAliases() { return this._attributeNameAliases; } get attributeValueAliases() { return this._attributeValueAliases; } get attributeValues() { return this._attributeValues; } toWhereExpression(predicate, context) { if (!predicate) { throw Error(`The condition expression is missing`); } const whereQuery = predicate.toString(); if (!whereQuery) { throw Error(`The expression string is missing`); } if (!this._recordId || !this._recordId.getRecordTypeId) { throw Error(`The record ID or the getRecordTypeId function is not available`); } const typeId = this._recordId.getRecordTypeId(); if (!typeId) { throw Error(`The record type ID is missing`); } const tokens = ArrowFunctionLexer.Instance.tokenize(whereQuery); const expression = WhereCauseExpressionParser.Instance.parse(whereQuery, tokens); const readingSchema = this._schemaProvider.getReadingSchema(typeId); return this._conditionFilterTransformer.transform(readingSchema, expression, context); } } class DynamoDBBatchWriteBuilder { constructor(recordMapper) { this._recordMapper = recordMapper; this._requests = {}; } buildRequests() { if (Object.getOwnPropertyNames(this._requests).length === 0) { throw Error(`No write requests for the batch write operation`); } return this._requests; } delete(recordId) { const keySchema = this._recordMapper.toKeyAttribute(recordId.getPrimaryKeys()); const tableRequests = this._getTableGroup(recordId.getTableName()); tableRequests.push({ DeleteRequest: { Key: keySchema } }); return this; } put(record) { const recordId = record.getRecordId(); const attributesToSave = this._recordMap