Repository: OrchardCMS/OrchardCore (2026-07-01T12:16:05Z)

Issue:
`main` `commit 1082d22ca75dfe86b2d69f34afaaaa1a166051e5` - Run SQL query, parse error and js error
### Describe the bug

In OC 2.2.1 I have created this SQL query: 

```
SELECT DocumentId FROM ContentItemIndex WHERE 
{% if ids != null and ids != "" %} ContentItemId NOT IN {{ ids }} AND {% endif %} 
PublishedUtc <= {{ now }} 
GROUP BY DocumentId ORDER BY PublishedUtc DESC LIMIT {{ size }}
```

Now this SQL query doesn't work. If I change the SQL query in this way: 

```
SELECT DocumentId FROM ContentItemIndex WHERE PublishedUtc <= {{ now }} {% if ids != null and ids != "" %} AND ContentItemId NOT IN {{ ids }} {% endif %} GROUP BY DocumentId ORDER BY PublishedUtc DESC LIMIT {{ size }}
```

It start to work again.

The browser console remarks about an issue:

```
Query:690 Uncaught ReferenceError: onDocumentReady is not defined
    at Query:690:5
(anonymous)	@	Query:690
```

Not related to the syntax of the SQL query.

### Orchard Core version

branch main, commit 1082d22ca75dfe86b2d69f34afaaaa1a166051e5

### To Reproduce

1. Start a new tenant with Blog recipe
2. Run SQL query in the "Run SQL Query" view

### Expected behavior



### Logs and screenshots

Exectute the original SQL query:

<img width="1585" height="924" alt="Image" src="https://github.com/user-attachments/assets/3768a18e-0387-4b05-8576-7994840e01b9" />

Executing the new query: 

<img width="1584" height="906" alt="Image" src="https://github.com/user-attachments/assets/724f5e06-ae44-4363-80a1-f6aecd2126ba" />




Discussion:
We triaged this issue and set the milestone according to the priority we think is appropriate (see  [the docs](https://docs.orchardcore.net/en/latest/docs/contributing/managing-issues/) on how we  triage and prioritize issues).

This indicates when the core team may start working on it. However, if you'd like to contribute, we'd  warmly welcome you to do that anytime. See our guide on contributions  [here](https://docs.orchardcore.net/en/latest/docs/contributing/).

We triaged this issue and set the milestone according to the priority we think is appropriate (see  [the docs](https://docs.orchardcore.net/en/latest/docs/contributing/managing-issues/) on how we  triage and prioritize issues).

This indicates when the core team may start working on it. However, if you'd like to contribute, we'd  warmly welcome you to do that anytime. See our guide on contributions  [here](https://docs.orchardcore.net/en/latest/docs/contributing/).




Patch:
diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/ParlotSqlParser.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/ParlotSqlParser.cs
index 3769b389665..9a1b3aa6827 100644
--- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/ParlotSqlParser.cs
+++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/ParlotSqlParser.cs
@@ -183,30 +183,34 @@ static ParlotSqlParser()
             (Terms.Char('|'), (a, b) => new BinaryExpression(a, BinaryOperator.BitwiseOr, b))
         );
 
-        var andExpr = bitwise.LeftAssociative(
-            (AND, (a, b) => new BinaryExpression(a, BinaryOperator.And, b))
-        );
-
-        var orExpr = andExpr.LeftAssociative(
-            (OR, (a, b) => new BinaryExpression(a, BinaryOperator.Or, b))
-        );
-
-        // BETWEEN and IN expressions
-        var betweenExpr = andExpr.And(NOT.Optional()).AndSkip(BETWEEN).And(bitwise).AndSkip(AND).And(bitwise)
+        // BETWEEN and IN are predicates at the comparison level: they must be parsed
+        // BEFORE AND/OR are applied. Otherwise "a IN (...) AND b" would only parse
+        // "a IN (...)" and leave "AND b" dangling, which fails the whole statement.
+        var betweenPredicate = bitwise.And(NOT.Optional()).AndSkip(BETWEEN).And(bitwise).AndSkip(AND).And(bitwise)
             .Then<Expression>(result =>
             {
                 var (expr, notKeyword, lower, upper) = result;
                 return new BetweenExpression(expr, lower, upper, notKeyword.HasValue);
             });
 
-        var inExpr = andExpr.And(NOT.Optional()).AndSkip(IN).AndSkip(LPAREN).And(functionArgs).AndSkip(RPAREN)
+        var inPredicate = bitwise.And(NOT.Optional()).AndSkip(IN).AndSkip(LPAREN).And(functionArgs).AndSkip(RPAREN)
             .Then<Expression>(result =>
             {
                 var (expr, notKeyword, values) = result;
                 return new InExpression(expr, values, notKeyword.HasValue);
             });
 
-        expression.Parser = betweenExpr.Or(inExpr).Or(orExpr);
+        var predicate = betweenPredicate.Or(inPredicate).Or(bitwise);
+
+        var andExpr = predicate.LeftAssociative(
+            (AND, (a, b) => new BinaryExpression(a, BinaryOperator.And, b))
+        );
+
+        var orExpr = andExpr.LeftAssociative(
+            (OR, (a, b) => new BinaryExpression(a, BinaryOperator.Or, b))
+        );
+
+        expression.Parser = orExpr;
 
         // Column source
         var columnSourceId = identifier.Then<ColumnSource>(id => new ColumnSourceIdentifier(id));
diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Views/Admin/Query.cshtml b/src/OrchardCore.Modules/OrchardCore.Queries/Views/Admin/Query.cshtml
index bf569e6725b..3a374d1f692 100644
--- a/src/OrchardCore.Modules/OrchardCore.Queries/Views/Admin/Query.cshtml
+++ b/src/OrchardCore.Modules/OrchardCore.Queries/Views/Admin/Query.cshtml
@@ -1,6 +1,7 @@
 @model OrchardCore.Queries.Sql.ViewModels.AdminQueryViewModel
 @using OrchardCore.ContentManagement
 @using OrchardCore.Infrastructure
+
 @inject IContentManager ContentManager
 
 @{
@@ -8,9 +9,6 @@
 }
 
 <style asp-name="codemirror"></style>
-<script asp-name="codemirror" at="Foot"></script>
-<script asp-name="codemirror-mode-javascript" at="Foot"></script>
-<script asp-name="codemirror-mode-sql" at="Foot"></script>
 
 <zone Name="Title"><h1>@RenderTitleSegments(T["SQL Query"])</h1></zone>
 
@@ -103,7 +101,7 @@
     }
 }
 
-<script at="Foot">
+<script at="Foot" asp-name="jsonresult-editor" depends-on="codemirror-mode-sql, monaco">
     function onDocumentReady(callback) {
         if (document.readyState !== 'loading') {
             callback();
@@ -111,7 +109,6 @@
             document.addEventListener('DOMContentLoaded', callback);
         }
     }
-
     onDocumentReady(function () {
         CodeMirror.fromTextArea(document.getElementById('@Html.IdFor(m => m.DecodedQuery)'), { mode: '@MediaTypeNamesExtended.Text.PostgreSQL', lineNumbers: true, viewportMargin: Infinity });
         CodeMirror.fromTextArea(document.getElementById('@Html.IdFor(m => m.Parameters)'), { mode: "javascript", json: true, lineNumbers: true, viewportMargin: Infinity });
@@ -119,11 +116,7 @@
         if (rawSql) {
             CodeMirror.fromTextArea(rawSql, { mode: "@sqlmode", lineNumbers: true, readonly: true, viewportMargin: Infinity });
         }
-    });
-</script>
-<script asp-name="monaco" at="Foot"></script>
-<script at="Foot" asp-name="jsonrecipe-editor" depends-on="monaco, admin">
-    onDocumentReady(function () {
+        
         require(['vs/editor/editor.main'], function () {
 
             const html = document.documentElement;
diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Views/SqlQuery.Edit.cshtml b/src/OrchardCore.Modules/OrchardCore.Queries/Views/SqlQuery.Edit.cshtml
index 6793ab6681e..cf21173e2dc 100644
--- a/src/OrchardCore.Modules/OrchardCore.Queries/Views/SqlQuery.Edit.cshtml
+++ b/src/OrchardCore.Modules/OrchardCore.Queries/Views/SqlQuery.Edit.cshtml
@@ -3,10 +3,7 @@
 
 <style asp-name="codemirror"></style>
 <style asp-name="codemirror-addon-hint-show-hint"></style>
-<script asp-name="codemirror" at="Foot"></script>
 <script asp-name="codemirror-addon-selection-active-line" at="Foot"></script>
-<script asp-name="codemirror-mode-javascript" at="Foot"></script>
-<script asp-name="codemirror-mode-sql" at="Foot"></script>
 <script asp-name="codemirror-addon-hint-sql-hint" at="Foot"></script>
 
 <div class="ocat-wrapper">
@@ -47,7 +44,7 @@
     </div>
 </div>
 
-<script at="Foot">
+<script at="Foot" depends-on="codemirror-mode-sql">
     function onDocumentReady(callback) {
         if (document.readyState !== 'loading') {
             callback();
diff --git a/src/OrchardCore.Modules/OrchardCore.Resources/ResourceManagementOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Resources/ResourceManagementOptionsConfiguration.cs
index f60b01d1c0f..e1b14667643 100644
--- a/src/OrchardCore.Modules/OrchardCore.Resources/ResourceManagementOptionsConfiguration.cs
+++ b/src/OrchardCore.Modules/OrchardCore.Resources/ResourceManagementOptionsConfiguration.cs
@@ -668,7 +668,7 @@ private ResourceManifest BuildManifest()
 
         manifest
             .DefineScript("codemirror-addon-hint-sql-hint")
-            .SetDependencies("codemirror-addon-hint-show-hint")
+            .SetDependencies("codemirror-mode-sql", "codemirror-addon-hint-show-hint")
             .SetUrl(
                 "~/OrchardCore.Resources/Scripts/codemirror/addon/hint/sql-hint.min.js",
                 "~/OrchardCore.Resources/Scripts/codemirror/addon/hint/sql-hint.js"
@@ -717,6 +717,7 @@ private ResourceManifest BuildManifest()
 
         manifest
             .DefineScript("codemirror-addon-selection-active-line")
+            .SetDependencies("codemirror")
             .SetUrl(
                 "~/OrchardCore.Resources/Scripts/codemirror/addon/selection/active-line.min.js",
                 "~/OrchardCore.Resources/Scripts/codemirror/addon/selection/active-line.js"
@@ -778,6 +779,7 @@ private ResourceManifest BuildManifest()
 
         manifest
             .DefineScript("codemirror-mode-sql")
+            .SetDependencies("codemirror", "codemirror-mode-javascript")
             .SetUrl(
                 "~/OrchardCore.Resources/Scripts/codemirror/mode/sql/sql.min.js",
                 "~/OrchardCore.Resources/Scripts/codemirror/mode/sql/sql.js"
@@ -1110,7 +1112,7 @@ private ResourceManifest BuildManifest()
             )
             .SetVersion("1.0.0");
 
-        // Vue.js 
+        // Vue.js
 
         manifest
             .DefineScript("vuejs")


Test patch:
diff --git a/test/OrchardCore.Tests/Orchard.Queries/SqlParserTests.cs b/test/OrchardCore.Tests/Orchard.Queries/SqlParserTests.cs
index a4975887b2a..eba258f49cb 100644
--- a/test/OrchardCore.Tests/Orchard.Queries/SqlParserTests.cs
+++ b/test/OrchardCore.Tests/Orchard.Queries/SqlParserTests.cs
@@ -90,6 +90,11 @@ public void Parse_WhereClause_Succeeds(string sql, string expectedSql)
     [InlineData("select a where b in (select b)", "SELECT [a] WHERE [b] IN (SELECT [b]);")]
     [InlineData("select a where b not in (1,2,3)", "SELECT [a] WHERE [b] NOT IN (1, 2, 3);")]
     [InlineData("select a where b not in (select b)", "SELECT [a] WHERE [b] NOT IN (SELECT [b]);")]
+    [InlineData("select a where b in (1,2,3) and c = d", "SELECT [a] WHERE [b] IN (1, 2, 3) AND [c] = [d];")]
+    [InlineData("select a where b not in (1,2,3) and c = d", "SELECT [a] WHERE [b] NOT IN (1, 2, 3) AND [c] = [d];")]
+    [InlineData("select a where b in (1,2,3) and c = d or e = f", "SELECT [a] WHERE [b] IN (1, 2, 3) AND [c] = [d] OR [e] = [f];")]
+    [InlineData("select a where a between b and c and d = e", "SELECT [a] WHERE [a] BETWEEN [b] AND [c] AND [d] = [e];")]
+    [InlineData("select a where a not between b and c and d = e", "SELECT [a] WHERE [a] NOT BETWEEN [b] AND [c] AND [d] = [e];")]
     [InlineData("select a where b = (select Avg(c) from d)", "SELECT [a] WHERE [b] = (SELECT Avg([c]) FROM [tp_d]);")]
     public void Parse_Expression_Succeeds(string sql, string expectedSql)
     {