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);
        }
Esempio n. 2
0
        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);
        }