public static (string Collection, (string Script, string[] Functions), bool Revisions) ParseSubscriptionQuery(string query) { var queryParser = new QueryParser(); queryParser.Init(query); var q = queryParser.Parse(); if (q.IsDistinct) { throw new NotSupportedException("Subscription does not support distinct queries"); } if (q.From.Index) { throw new NotSupportedException("Subscription must specify a collection to use"); } if (q.GroupBy != null) { throw new NotSupportedException("Subscription cannot specify a group by clause"); } if (q.OrderBy != null) { throw new NotSupportedException("Subscription cannot specify an order by clause"); } if (q.Include != null) { throw new NotSupportedException("Subscription cannot specify an include clause"); } if (q.UpdateBody != null) { throw new NotSupportedException("Subscription cannot specify an update clause"); } bool revisions = false; if (q.From.Filter is BinaryExpression filter) { switch (filter.Operator) { case OperatorType.Equal: case OperatorType.NotEqual: if (!(filter.Left is FieldExpression fe) || fe.Compound.Count != 1) { throw new NotSupportedException("Subscription collection filter can only specify 'Revisions = true'"); } if (string.Equals(fe.Compound[0], "Revisions", StringComparison.OrdinalIgnoreCase) == false) { throw new NotSupportedException("Subscription collection filter can only specify 'Revisions = true'"); } if (filter.Right is ValueExpression ve) { revisions = filter.Operator == OperatorType.Equal && ve.Value == ValueTokenType.True; if (ve.Value != ValueTokenType.True && ve.Value != ValueTokenType.False) { throw new NotSupportedException("Subscription collection filter can only specify 'Revisions = true'"); } } else { throw new NotSupportedException("Subscription collection filter can only specify 'Revisions = true'"); } break; default: throw new NotSupportedException("Subscription must not specify a collection filter (move it to the where clause)"); } } else if (q.From.Filter != null) { throw new NotSupportedException("Subscription must not specify a collection filter (move it to the where clause)"); } var collectionName = q.From.From.FieldValue; if (q.Where == null && q.Select == null && q.SelectFunctionBody == null) { return(collectionName, (null, null), revisions); } var writer = new StringWriter(); if (q.From.Alias != null) { writer.Write("var "); writer.Write(q.From.Alias); writer.WriteLine(" = this;"); } else if (q.Select != null || q.SelectFunctionBody != null || q.Load != null) { throw new InvalidOperationException("Cannot specify a select or load clauses without an alias on the query"); } if (q.Load != null) { Debug.Assert(q.From.Alias != null); var fromAlias = q.From.Alias.Value; foreach (var tuple in q.Load) { writer.Write("var "); writer.Write(tuple.Alias); writer.Write(" = loadPath(this,'"); var fieldExpression = ((FieldExpression)tuple.Expression); if (fieldExpression.Compound[0] != fromAlias) { throw new InvalidOperationException("Load clause can only load paths starting from the from alias: " + fromAlias); } writer.Write(fieldExpression.FieldValueWithoutAlias); writer.WriteLine("');"); } } if (q.Where != null) { writer.Write("if ("); new JavascriptCodeQueryVisitor(writer.GetStringBuilder(), q).VisitExpression(q.Where); writer.WriteLine(" )"); writer.WriteLine("{"); } if (q.SelectFunctionBody != null) { writer.Write(" return "); writer.Write(q.SelectFunctionBody); writer.WriteLine(";"); } else if (q.Select != null) { if (q.Select.Count != 1 || q.Select[0].Expression is MethodExpression == false) { throw new NotSupportedException("Subscription select clause must specify an object literal"); } writer.WriteLine(); writer.Write(" return "); new JavascriptCodeQueryVisitor(writer.GetStringBuilder(), q).VisitExpression(q.Select[0].Expression); writer.WriteLine(";"); } else { writer.WriteLine(" return true;"); } writer.WriteLine(); if (q.Where != null) { writer.WriteLine("}"); } var script = writer.GetStringBuilder().ToString(); // verify that the JS code parses new Esprima.JavaScriptParser(script).ParseProgram(); return(collectionName, (script, q.DeclaredFunctions?.Values?.ToArray() ?? Array.Empty <string>()), revisions); }
public static (string Collection, (string Script, string[] Functions), bool Revisions) ParseSubscriptionQuery(string query) { var queryParser = new QueryParser(); queryParser.Init(query); var q = queryParser.Parse(); if (q.IsDistinct) { throw new NotSupportedException("Subscription does not support distinct queries"); } if (q.From.Index) { throw new NotSupportedException("Subscription must specify a collection to use"); } if (q.GroupBy != null) { throw new NotSupportedException("Subscription cannot specify a group by clause"); } if (q.OrderBy != null) { throw new NotSupportedException("Subscription cannot specify an order by clause"); } if (q.Include != null) { throw new NotSupportedException("Subscription cannot specify an include clause"); } if (q.UpdateBody != null) { throw new NotSupportedException("Subscription cannot specify an update clause"); } bool revisions = false; if (q.From.Filter != null) { switch (q.From.Filter.Type) { case OperatorType.Equal: case OperatorType.NotEqual: var field = QueryExpression.Extract(query, q.From.Filter.Field); if (string.Equals(field, "Revisions", StringComparison.OrdinalIgnoreCase) == false) { throw new NotSupportedException("Subscription collection filter can only specify 'Revisions = true'"); } if (q.From.Filter.Value.Type != ValueTokenType.True) { throw new NotSupportedException("Subscription collection filter can only specify 'Revisions = true'"); } revisions = q.From.Filter.Type == OperatorType.Equal; break; default: throw new NotSupportedException("Subscription must not specify a collection filter (move it to the where clause)"); } } var collectionName = QueryExpression.Extract(query, q.From.From); if (q.Where == null && q.Select == null && q.SelectFunctionBody == null) { return(collectionName, (null, null), revisions); } var writer = new StringWriter(); if (q.From.Alias != null) { writer.Write("var "); writer.Write(QueryExpression.Extract(query, q.From.Alias)); writer.WriteLine(" = this;"); } else if (q.Select != null || q.SelectFunctionBody != null || q.Load != null) { throw new InvalidOperationException("Cannot specify a select or load clauses without an alias on the query"); } if (q.Load != null) { var fromAlias = QueryExpression.Extract(query, q.From.Alias); foreach (var tuple in q.Load) { writer.Write("var "); writer.Write(QueryExpression.Extract(query, tuple.Alias)); writer.Write(" = loadPath(this,'"); var fullFieldPath = QueryExpression.Extract(query, tuple.Expression.Field); if (fullFieldPath.StartsWith(fromAlias) == false) { throw new InvalidOperationException("Load clause can only load paths starting from the from alias: " + fromAlias); } var indexOfDot = fullFieldPath.IndexOf('.', fromAlias.Length); fullFieldPath = fullFieldPath.Substring(indexOfDot + 1); writer.Write(fullFieldPath.Trim()); writer.WriteLine("');"); } } if (q.Where != null) { writer.Write("if ("); q.Where.ToJavaScript(query, q.From.Alias != null ? null : "this", writer); writer.WriteLine(" )"); writer.WriteLine("{"); } if (q.SelectFunctionBody != null) { writer.Write(" return "); writer.Write(QueryExpression.Extract(query, q.SelectFunctionBody)); writer.WriteLine(";"); } else if (q.Select != null) { if (q.Select.Count != 1 || q.Select[0].Expression.Type != OperatorType.Method) { throw new NotSupportedException("Subscription select clause must specify an object literal"); } writer.WriteLine(); writer.Write(" return "); q.Select[0].Expression.ToJavaScript(query, q.From.Alias != null ? null : "this", writer); writer.WriteLine(";"); } else { writer.WriteLine(" return true;"); } writer.WriteLine(); if (q.Where != null) { writer.WriteLine("}"); } var script = writer.GetStringBuilder().ToString(); // verify that the JS code parses new Esprima.JavaScriptParser(script).ParseProgram(); return(collectionName, (script, q.DeclaredFunctions?.Values?.ToArray() ?? Array.Empty <string>()), revisions); }