diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlGrammar.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlGrammar.cs index 319b7a8d4b6..5d0ab6b3ace 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlGrammar.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlGrammar.cs @@ -146,7 +146,7 @@ public SqlGrammar() : base(false) // Create Index. orderList.Rule = MakePlusRule(orderList, comma, orderMember); - orderMember.Rule = Id + orderDirOptional; + orderMember.Rule = Id + (orderDirOptional | "(" + functionArguments + ")"); orderDirOptional.Rule = Empty | "ASC" | "DESC"; // Select stmt. diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlParser.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlParser.cs index 9c0021377cc..d397404352d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlParser.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlParser.cs @@ -246,20 +246,37 @@ private void EvaluateOrderClause(ParseTreeNode parseTreeNode) var idList = parseTreeNode.ChildNodes[2]; _modes.Push(FormattingModes.SelectClause); + for (var i = 0; i < idList.ChildNodes.Count; i++) { - var id = idList.ChildNodes[i].ChildNodes[0]; - if (i > 0) { _builder.Append(", "); } + var id = idList.ChildNodes[i].ChildNodes[0]; + + // RANDOM() is a special case where we need to use the dialect's random function. + if (id.ChildNodes[0].Token != null && id.ChildNodes[0].Token.ValueString.Equals("RANDOM", StringComparison.OrdinalIgnoreCase)) + { + var funArgs = idList.ChildNodes[i].ChildNodes[1].ChildNodes[0]; + + // "RANDOM" + {funArgs} + no arguments? + if (funArgs.Term.Name == "funArgs" && funArgs.ChildNodes.Count == 0) + { + _builder.Append(_dialect.RandomOrderByClause); + + continue; + } + } + EvaluateId(id); - if (idList.ChildNodes[i].ChildNodes[1].ChildNodes.Count > 0) + var orderDirOpt = idList.ChildNodes[i].ChildNodes[1].ChildNodes[0]; + + if (orderDirOpt.Term.Name == "orderDirOpt" && orderDirOpt.ChildNodes.Count > 0) { - _builder.Append(' ').Append(idList.ChildNodes[i].ChildNodes[1].ChildNodes[0].Term.Name); + _builder.Append(' ').Append(orderDirOpt.ChildNodes[0].Term.Name); } } @@ -805,7 +822,7 @@ private void EvaluateOverClauseOptional(ParseTreeNode overClauseOpt) var orderMember = orderList.ChildNodes[i]; var id = orderMember.ChildNodes[0]; EvaluateSelectId(id); - var orderDirOpt = orderMember.ChildNodes[1]; + var orderDirOpt = orderMember.ChildNodes[1].ChildNodes[0]; if (orderDirOpt.ChildNodes.Count > 0) { _builder.Append(' ').Append(orderDirOpt.ChildNodes[0].Term.Name); diff --git a/src/docs/reference/modules/Queries/README.md b/src/docs/reference/modules/Queries/README.md index 8b41a04c54b..c7e4dc5c441 100644 --- a/src/docs/reference/modules/Queries/README.md +++ b/src/docs/reference/modules/Queries/README.md @@ -289,6 +289,12 @@ The SQL parser is also able to convert some specific functions to the intended d | `year(_date_)` | Returns the years part of a date. | | `now()` | Returns current date time (utc). | +Order By clauses can also use the `random()` function (case insensitive) to order results randomly, e.g., + +``` +SELECT * FROM ContentItemIndex ORDER BY random() +``` + ## Scripting The following JavaScript functions are available with this module. diff --git a/test/OrchardCore.Tests/Orchard.Queries/SqlParserTests.cs b/test/OrchardCore.Tests/Orchard.Queries/SqlParserTests.cs index a7f392fe2c2..ec7850a5479 100644 --- a/test/OrchardCore.Tests/Orchard.Queries/SqlParserTests.cs +++ b/test/OrchardCore.Tests/Orchard.Queries/SqlParserTests.cs @@ -243,4 +243,19 @@ public void ShouldParseSubquery(string sql, string expectedSql) Assert.True(result); Assert.Equal(expectedSql, FormatSql(rawQuery)); } + + [Theory] + [InlineData("select a order by RANDOM()", "SELECT [a] ORDER BY newid();")] + [InlineData("select a order by random()", "SELECT [a] ORDER BY newid();")] + [InlineData("select a order by RANDOM", "SELECT [a] ORDER BY [RANDOM];")] + [InlineData("select a order by random", "SELECT [a] ORDER BY [random];")] + public void ShouldOrderByRandom(string sql, string expectedSql) + { + // Arrange & Act + var result = SqlParser.TryParse(sql, _schema, _defaultDialect, _defaultTablePrefix, null, out var rawQuery, out var errors); + + // Assert + Assert.True(result); + Assert.Equal(expectedSql, FormatSql(rawQuery)); + } }