You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by dl...@apache.org on 2019/06/06 23:56:51 UTC

[asterixdb] branch master updated: [NO ISSUE][SQLPP] Support SELECT LET

This is an automated email from the ASF dual-hosted git repository.

dlych pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 368092d  [NO ISSUE][SQLPP] Support SELECT LET
368092d is described below

commit 368092db8e4c0c2a9c18159742c6684198a1187a
Author: Dmitry Lychagin <dm...@couchbase.com>
AuthorDate: Thu Jun 6 14:39:38 2019 -0700

    [NO ISSUE][SQLPP] Support SELECT LET
    
    - user model changes: yes
    - storage format changes: no
    - interface changes: no
    
    Details:
    - Support SELECT ... LET ... (no FROM clause)
    - Add CancelUnnestSingletonListRule to the optimizer
    - Add negative testcase for unnamed field value
      expressions in object constructor
    
    Change-Id: I1226dca83e756075608232c642851f646a9bee3b
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/3428
    Sonar-Qube: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Dmitry Lychagin <dm...@couchbase.com>
---
 .../asterix/optimizer/base/RuleCollections.java    |   2 +
 .../rules/CancelUnnestSingletonListRule.java       | 124 +++++++++++++++++++++
 .../optimizerts/queries/flwr/select-let-1.sqlpp    |  25 +++++
 .../optimizerts/results/flwr/select-let-1.plan     |   4 +
 .../results/subquery/query-ASTERIXDB-1572.plan     |  79 +++----------
 .../flwor/select-let/select-let.1.query.sqlpp      |  25 +++++
 .../queries_sqlpp/objects/ObjectsQueries.xml       |   1 +
 .../no_fieldname_constr_negative.2.query.sqlpp     |  29 +++++
 .../results/flwor/select-let/select-let.1.adm      |   1 +
 .../test/resources/runtimets/testsuite_sqlpp.xml   |   5 +
 .../asterix-lang-sqlpp/src/main/javacc/SQLPP.jj    |  31 ++++--
 11 files changed, 256 insertions(+), 70 deletions(-)

diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/RuleCollections.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/RuleCollections.java
index f9aecab..d5f2e00 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/RuleCollections.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/RuleCollections.java
@@ -32,6 +32,7 @@ import org.apache.asterix.optimizer.rules.AsterixInlineVariablesRule;
 import org.apache.asterix.optimizer.rules.AsterixIntroduceGroupByCombinerRule;
 import org.apache.asterix.optimizer.rules.AsterixPushAssignBelowUnionAllRule;
 import org.apache.asterix.optimizer.rules.ByNameToByIndexFieldAccessRule;
+import org.apache.asterix.optimizer.rules.CancelUnnestSingletonListRule;
 import org.apache.asterix.optimizer.rules.CancelUnnestWithNestedListifyRule;
 import org.apache.asterix.optimizer.rules.CheckFilterExpressionTypeRule;
 import org.apache.asterix.optimizer.rules.CheckFullParallelSortRule;
@@ -203,6 +204,7 @@ public final class RuleCollections {
         normalization.add(new PushAggFuncIntoStandaloneAggregateRule());
         normalization.add(new ListifyUnnestingFunctionRule());
         normalization.add(new RemoveRedundantSelectRule());
+        normalization.add(new CancelUnnestSingletonListRule());
         normalization.add(new UnnestToDataScanRule());
         normalization.add(new MetaFunctionToMetaVariableRule());
         normalization.add(new FuzzyEqRule());
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/CancelUnnestSingletonListRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/CancelUnnestSingletonListRule.java
new file mode 100644
index 0000000..ab7abdf
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/CancelUnnestSingletonListRule.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.asterix.optimizer.rules;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.asterix.om.base.IACollection;
+import org.apache.asterix.om.base.IACursor;
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.constants.AsterixConstantValue;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IAlgebricksConstantValue;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
+import org.apache.hyracks.algebricks.core.rewriter.base.IAlgebraicRewriteRule;
+
+/**
+ * This rule removes UNNEST of a constant singleton list and replaces it with ASSIGN as follows:
+ * Before plan:
+ * <ul>
+ * <li>unnest $x <- scan-collection( [ constant ] ) (or {{ constant }})
+ * </ul>
+ * <p>
+ * After plan:
+ * <ul>
+ * <li>assign $x <- constant
+ * </ul>
+ *
+ * This rule must run after {@link ConstantFoldingRule}
+ */
+public final class CancelUnnestSingletonListRule implements IAlgebraicRewriteRule {
+    @Override
+    public boolean rewritePre(Mutable<ILogicalOperator> opRef, IOptimizationContext context)
+            throws AlgebricksException {
+        AbstractLogicalOperator op = (AbstractLogicalOperator) opRef.getValue();
+        if (op.getOperatorTag() != LogicalOperatorTag.UNNEST) {
+            return false;
+        }
+        UnnestOperator unnest = (UnnestOperator) op;
+        if (unnest.getPositionalVariable() != null) {
+            return false;
+        }
+        ILogicalExpression expr = unnest.getExpressionRef().getValue();
+        if (expr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
+            return false;
+        }
+        if (((AbstractFunctionCallExpression) expr).getFunctionIdentifier() != BuiltinFunctions.SCAN_COLLECTION) {
+            return false;
+        }
+        AbstractFunctionCallExpression callExpr = (AbstractFunctionCallExpression) expr;
+        ILogicalExpression argExpr = callExpr.getArguments().get(0).getValue();
+        if (argExpr.getExpressionTag() != LogicalExpressionTag.CONSTANT) {
+            return false;
+        }
+        ConstantExpression cExpr = (ConstantExpression) argExpr;
+        IAlgebricksConstantValue cValue = cExpr.getValue();
+        if (!(cValue instanceof AsterixConstantValue)) {
+            return false;
+        }
+        AsterixConstantValue aValue = (AsterixConstantValue) cValue;
+        IAObject value = aValue.getObject();
+        if (!value.getType().getTypeTag().isListType()) {
+            return false;
+        }
+        IACollection list = (IACollection) value;
+        if (list.size() != 1) {
+            return false;
+        }
+        IACursor cur = list.getCursor();
+        if (!cur.next()) {
+            return false;
+        }
+        IAObject item = cur.get();
+
+        List<LogicalVariable> assignVars = new ArrayList<>(1);
+        assignVars.add(unnest.getVariable());
+        List<Mutable<ILogicalExpression>> assignExprs = new ArrayList<>(1);
+        ConstantExpression itemExpr = new ConstantExpression(new AsterixConstantValue(item));
+        itemExpr.setSourceLocation(cExpr.getSourceLocation());
+        assignExprs.add(new MutableObject<>(itemExpr));
+        AssignOperator assignOp = new AssignOperator(assignVars, assignExprs);
+        assignOp.setSourceLocation(op.getSourceLocation());
+        assignOp.getInputs().addAll(op.getInputs());
+
+        context.computeAndSetTypeEnvironmentForOperator(assignOp);
+        opRef.setValue(assignOp);
+        return true;
+    }
+
+    @Override
+    public boolean rewritePost(Mutable<ILogicalOperator> opRef, IOptimizationContext context) {
+        return false;
+    }
+}
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/flwr/select-let-1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/flwr/select-let-1.sqlpp
new file mode 100644
index 0000000..0d45eee
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/flwr/select-let-1.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Description: Test SELECT ... LET ... (no FROM clause)
+ */
+
+select value x
+let x = 2;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/flwr/select-let-1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/flwr/select-let-1.plan
new file mode 100644
index 0000000..de331ad
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/flwr/select-let-1.plan
@@ -0,0 +1,4 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- ASSIGN  |UNPARTITIONED|
+      -- EMPTY_TUPLE_SOURCE  |UNPARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/subquery/query-ASTERIXDB-1572.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/subquery/query-ASTERIXDB-1572.plan
index 1b0be9d..c6ed7f9 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results/subquery/query-ASTERIXDB-1572.plan
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/subquery/query-ASTERIXDB-1572.plan
@@ -4,73 +4,26 @@
       -- ASSIGN  |PARTITIONED|
         -- STREAM_PROJECT  |PARTITIONED|
           -- STREAM_SELECT  |PARTITIONED|
-            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              -- PRE_CLUSTERED_GROUP_BY[$$64]  |PARTITIONED|
+            -- SUBPLAN  |PARTITIONED|
+                    {
+                      -- AGGREGATE  |LOCAL|
+                        -- STREAM_SELECT  |LOCAL|
+                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                    }
+              -- SUBPLAN  |PARTITIONED|
                       {
                         -- AGGREGATE  |LOCAL|
                           -- STREAM_SELECT  |LOCAL|
                             -- NESTED_TUPLE_SOURCE  |LOCAL|
                       }
-                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  -- STABLE_SORT [$$64(ASC)]  |PARTITIONED|
+                -- SUBPLAN  |PARTITIONED|
+                        {
+                          -- AGGREGATE  |LOCAL|
+                            -- STREAM_SELECT  |LOCAL|
+                              -- NESTED_TUPLE_SOURCE  |LOCAL|
+                        }
+                  -- STREAM_PROJECT  |PARTITIONED|
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      -- STREAM_PROJECT  |PARTITIONED|
+                      -- DATASOURCE_SCAN  |PARTITIONED|
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                          -- HYBRID_HASH_JOIN [$$64][$#4]  |PARTITIONED|
-                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                              -- PRE_CLUSTERED_GROUP_BY[$$62]  |PARTITIONED|
-                                      {
-                                        -- AGGREGATE  |LOCAL|
-                                          -- STREAM_SELECT  |LOCAL|
-                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
-                                      }
-                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                  -- STREAM_PROJECT  |PARTITIONED|
-                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                      -- HYBRID_HASH_JOIN [$$62][$#3]  |PARTITIONED|
-                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                          -- PRE_CLUSTERED_GROUP_BY[$$60]  |PARTITIONED|
-                                                  {
-                                                    -- AGGREGATE  |LOCAL|
-                                                      -- STREAM_SELECT  |LOCAL|
-                                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
-                                                  }
-                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                              -- STABLE_SORT [$$60(ASC)]  |PARTITIONED|
-                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                  -- STREAM_PROJECT  |PARTITIONED|
-                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                      -- HYBRID_HASH_JOIN [$$60][$#2]  |PARTITIONED|
-                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                          -- PRE_CLUSTERED_GROUP_BY[$$53]  |PARTITIONED|
-                                                                  {
-                                                                    -- AGGREGATE  |LOCAL|
-                                                                      -- STREAM_SELECT  |LOCAL|
-                                                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
-                                                                  }
-                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                              -- STREAM_PROJECT  |PARTITIONED|
-                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                                  -- HYBRID_HASH_JOIN [$$53][$#1]  |PARTITIONED|
-                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                                      -- STREAM_PROJECT  |PARTITIONED|
-                                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                                          -- DATASOURCE_SCAN  |PARTITIONED|
-                                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
-                                                                    -- HASH_PARTITION_EXCHANGE [$#1]  |PARTITIONED|
-                                                                      -- ASSIGN  |UNPARTITIONED|
-                                                                        -- UNNEST  |UNPARTITIONED|
-                                                                          -- EMPTY_TUPLE_SOURCE  |UNPARTITIONED|
-                                                        -- HASH_PARTITION_EXCHANGE [$#2]  |PARTITIONED|
-                                                          -- ASSIGN  |UNPARTITIONED|
-                                                            -- UNNEST  |UNPARTITIONED|
-                                                              -- EMPTY_TUPLE_SOURCE  |UNPARTITIONED|
-                                        -- HASH_PARTITION_EXCHANGE [$#3]  |PARTITIONED|
-                                          -- ASSIGN  |UNPARTITIONED|
-                                            -- UNNEST  |UNPARTITIONED|
-                                              -- EMPTY_TUPLE_SOURCE  |UNPARTITIONED|
-                            -- HASH_PARTITION_EXCHANGE [$#4]  |PARTITIONED|
-                              -- ASSIGN  |UNPARTITIONED|
-                                -- UNNEST  |UNPARTITIONED|
-                                  -- EMPTY_TUPLE_SOURCE  |UNPARTITIONED|
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/flwor/select-let/select-let.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/flwor/select-let/select-let.1.query.sqlpp
new file mode 100644
index 0000000..9462429
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/flwor/select-let/select-let.1.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Description: Test SELECT ... LET ... (no FROM clause)
+ */
+
+select value x
+let x = 2
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/ObjectsQueries.xml b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/ObjectsQueries.xml
index e0114fc..150a37f 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/ObjectsQueries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/ObjectsQueries.xml
@@ -111,6 +111,7 @@
     <compilation-unit name="no_fieldname_constr_negative">
       <output-dir compare="Text">no_fieldname_constr</output-dir>
       <expected-error>ASX1001: Syntax error: Cannot infer field name</expected-error>
+      <expected-error>ASX0013: Duplicate field name "a"</expected-error>
     </compilation-unit>
   </test-case>
   <test-case FilePath="objects">
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/no_fieldname_constr_negative/no_fieldname_constr_negative.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/no_fieldname_constr_negative/no_fieldname_constr_negative.2.query.sqlpp
new file mode 100644
index 0000000..c20cc84
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/no_fieldname_constr_negative/no_fieldname_constr_negative.2.query.sqlpp
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Description  : Testing object constructor without field names
+ * Expected Res : Failure: Duplicate field name
+ */
+
+from (
+  from range(1, 2) x
+  select value { "a": x, "b": { "a": x + 1 } }
+) x
+select value { x.a, x.b.a }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/select-let/select-let.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/select-let/select-let.1.adm
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/select-let/select-let.1.adm
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index 2a1dbd3..661e54e 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -119,6 +119,11 @@
         <expected-error>ASX0013: Duplicate field name "g" (in line 27, at column 11)</expected-error>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="flwor">
+      <compilation-unit name="select-let">
+        <output-dir compare="Text">select-let</output-dir>
+      </compilation-unit>
+    </test-case>
   </test-group>
   <test-group name="sorting">
     <test-case FilePath="sorting">
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 73612ab..73c4137 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -3114,18 +3114,36 @@ SelectBlock SelectBlock() throws ParseException:
   (
      selectClause = SelectClause() { startSrcLoc = selectClause.getSourceLocation(); }
      (
-        LOOKAHEAD(1)
-        fromClause = FromClause()
         (
-            LOOKAHEAD(1)
-            fromLetClauses = LetClause()
-        )?
+          fromClause = FromClause()
+          (
+              fromLetClauses = LetClause()
+          )?
+        )
+        |
+        (
+          fromLetClauses = LetClause()
+          {
+            // LET without FROM -> create dummy FROM clause: FROM {{missing}} AS #0
+            SourceLocation sourceLoc = getSourceLocation(token);
+            LiteralExpr missingExpr = new LiteralExpr(MissingLiteral.INSTANCE);
+            missingExpr.setSourceLocation(sourceLoc);
+            List<Expression> list = new ArrayList<Expression>(1);
+            list.add(missingExpr);
+            ListConstructor listExpr = new ListConstructor(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR, list);
+            listExpr.setSourceLocation(sourceLoc);
+            List<FromTerm> fromTerms = new ArrayList<FromTerm>(1);
+            VariableExpr fromVar = new VariableExpr(new VarIdentifier("#0"));
+            fromVar.setSourceLocation(sourceLoc);
+            fromTerms.add(new FromTerm(listExpr, fromVar, null, new ArrayList<AbstractBinaryCorrelateClause>()));
+            fromClause = new FromClause(fromTerms);
+          }
+        )
      )?
      (whereClause = WhereClause())?
      (
         groupbyClause = GroupbyClause()
         (
-            LOOKAHEAD(1)
             gbyLetClauses = LetClause()
         )?
         (havingClause = HavingClause())?
@@ -3133,7 +3151,6 @@ SelectBlock SelectBlock() throws ParseException:
     |
      fromClause = FromClause() { startSrcLoc = fromClause.getSourceLocation(); }
      (
-        LOOKAHEAD(1)
         fromLetClauses = LetClause()
      )?
      (whereClause = WhereClause())?