arrow-store
Version:
TypeScript DynamoDB ORM
1,361 lines (1,352 loc) • 125 kB
JavaScript
'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