From e5ef201e55895dd1451a2da7763b8afb77637eb1 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Sun, 28 Sep 2025 00:14:36 +0300 Subject: [PATCH 1/2] style formatting --- .editorconfig | 112 +- QueryBuilder.Tests/AggregateTests.cs | 174 +- QueryBuilder.Tests/DefineTest.cs | 834 ++++--- QueryBuilder.Tests/DeleteTests.cs | 93 +- QueryBuilder.Tests/ExecutionTests.cs | 66 +- .../Firebird/FirebirdLimitTests.cs | 103 +- QueryBuilder.Tests/GeneralTests.cs | 1169 +++++----- QueryBuilder.Tests/HelperTests.cs | 460 ++-- .../Infrastructure/TestCompiler.cs | 104 +- .../Infrastructure/TestCompilersContainer.cs | 205 +- .../Infrastructure/TestSqlResultContainer.cs | 23 +- .../Infrastructure/TestSupport.cs | 37 +- QueryBuilder.Tests/InfrastructureTests.cs | 101 +- QueryBuilder.Tests/InsertTests.cs | 595 +++-- QueryBuilder.Tests/MySql/MySqlLimitTests.cs | 109 +- QueryBuilder.Tests/MySqlExecutionTest.cs | 520 +++-- QueryBuilder.Tests/OperatorWhitelistTests.cs | 266 ++- .../Oracle/OracleDateConditionTests.cs | 431 ++-- .../Oracle/OracleInsertManyTests.cs | 117 +- .../Oracle/OracleLegacyLimitTests.cs | 157 +- QueryBuilder.Tests/Oracle/OracleLimitTests.cs | 143 +- QueryBuilder.Tests/ParameterTypeTests.cs | 99 +- .../PostgreSql/PostgreSqlLimitTests.cs | 109 +- QueryBuilder.Tests/QueryFactoryExtension.cs | 35 +- QueryBuilder.Tests/SQLiteExecutionTest.cs | 529 +++-- QueryBuilder.Tests/SelectTests.cs | 1854 ++++++++------- .../SqlServer/NestedSelectTests.cs | 121 +- .../SqlServer/SqlServerLegacyLimitTests.cs | 146 +- .../SqlServer/SqlServerLimitTests.cs | 166 +- .../SqlServer/SqlServerTests.cs | 147 +- QueryBuilder.Tests/Sqlite/SqliteLimitTests.cs | 109 +- QueryBuilder.Tests/UpdateTests.cs | 610 +++-- QueryBuilder.Tests/WhereTests.cs | 62 +- QueryBuilder/Base.Where.cs | 1269 +++++------ QueryBuilder/BaseQuery.cs | 585 +++-- QueryBuilder/Clauses/AbstractClause.cs | 41 +- QueryBuilder/Clauses/AggregateClause.cs | 74 +- QueryBuilder/Clauses/ColumnClause.cs | 203 +- QueryBuilder/Clauses/Combine.cs | 124 +- QueryBuilder/Clauses/ConditionClause.cs | 632 +++--- QueryBuilder/Clauses/FromClause.cs | 219 +- QueryBuilder/Clauses/IncrementClause.cs | 33 +- QueryBuilder/Clauses/InsertClause.cs | 82 +- QueryBuilder/Clauses/JoinClause.cs | 92 +- QueryBuilder/Clauses/LimitClause.cs | 62 +- QueryBuilder/Clauses/OffsetClause.cs | 62 +- QueryBuilder/Clauses/OrderClause.cs | 98 +- QueryBuilder/ColumnAttribute.cs | 62 +- QueryBuilder/Compilers/Compiler.Conditions.cs | 498 ++--- QueryBuilder/Compilers/Compiler.cs | 1992 ++++++++--------- .../Compilers/ConditionsCompilerProvider.cs | 99 +- QueryBuilder/Compilers/CteFinder.cs | 105 +- QueryBuilder/Compilers/EngineCodes.cs | 23 +- QueryBuilder/Compilers/FirebirdCompiler.cs | 222 +- QueryBuilder/Compilers/MySqlCompiler.cs | 90 +- QueryBuilder/Compilers/OracleCompiler.cs | 331 ++- QueryBuilder/Compilers/PostgresCompiler.cs | 183 +- QueryBuilder/Compilers/SqlServerCompiler.cs | 354 ++- QueryBuilder/Compilers/SqliteCompiler.cs | 125 +- QueryBuilder/Expressions.cs | 51 +- QueryBuilder/Extensions/QueryForExtensions.cs | 70 +- QueryBuilder/Helper.cs | 327 ++- QueryBuilder/IgnoreAttribute.cs | 52 +- QueryBuilder/Include.cs | 19 +- QueryBuilder/Join.cs | 142 +- QueryBuilder/Properties/AssemblyInfo.cs | 6 +- QueryBuilder/Query.Aggregate.cs | 107 +- QueryBuilder/Query.Combine.cs | 192 +- QueryBuilder/Query.Delete.cs | 20 +- QueryBuilder/Query.Having.cs | 1191 +++++----- QueryBuilder/Query.Insert.cs | 221 +- QueryBuilder/Query.Join.cs | 136 +- QueryBuilder/Query.Select.cs | 222 +- QueryBuilder/Query.Update.cs | 137 +- QueryBuilder/Query.cs | 810 +++---- QueryBuilder/SqlResult.cs | 178 +- QueryBuilder/UnsafeLiteral.cs | 40 +- QueryBuilder/Variable.cs | 22 +- SqlKata.Execution/InsertGetId.cs | 11 +- SqlKata.Execution/PaginationIterator.cs | 55 +- SqlKata.Execution/PaginationResult.cs | 192 +- SqlKata.Execution/Properties/AssemblyInfo.cs | 6 +- SqlKata.Execution/Query.Extensions.cs | 709 +++--- SqlKata.Execution/QueryFactory.cs | 1691 +++++++------- SqlKata.Execution/XQuery.cs | 90 +- 85 files changed, 11311 insertions(+), 12852 deletions(-) diff --git a/.editorconfig b/.editorconfig index b33fe946..1333402c 100755 --- a/.editorconfig +++ b/.editorconfig @@ -1,28 +1,84 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -# Note: the trim_trailing_whitespace option is br0ken in visualstudio, it -# simply does not follow the EditorConfig specification. Therefor you are -# strongly encouraged to not rely on this setting alone, but please install -# the following extension too: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespaceVisualizer -# -# References: -# https://developercommunity.visualstudio.com/t/EditorConfig:-trim_trailing_whitespace-d/1240174?space=8&q=trim_trailing_whitespace -# https://developercommunity.visualstudio.com/t/editorconfig-trim_trailing_whitespace-on/134457?space=8&q=trim_trailing_whitespace -# https://developercommunity.visualstudio.com/t/trim_trailing_whitespace-in-editorconfi/1351034?space=8&q=trim_trailing_whitespace -# https://developercommunity.visualstudio.com/t/BUG:-editorconfig-trim_trailing_whitespa/953937?space=8&q=trim_trailing_whitespace -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = tab -indent_size = 2 - - -[*.cs] # To match existing style -indent_style = space -indent_size = 4 - - -[*.yml] -indent_style = space +# top-most EditorConfig file +root = true + +########################################################### +# Core EditorConfig Settings - Apply to ALL files +########################################################### + +[*] +# Indentation and spacing +indent_style = space +indent_size = 4 +tab_width = 4 + +# Newline preferences +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +########################################################### +# .NET / C# Code Style Rules +########################################################### + +[*.cs] + +#### C# Language Style Rules (csharp_style_) #### + +# Use "var" when the type is obvious or when a tuple/anonymous type +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_obvious = true:suggestion +csharp_style_var_elsewhere = false:suggestion + +# Use file-scoped namespaces (if targeting .NET 6+) +csharp_style_namespace_declarations = file_scoped:warning + +# Use pattern matching for 'is' and 'switch' +csharp_style_pattern_matching_over_is_with_not_null = true:suggestion +csharp_style_pattern_matching_over_as = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion + +# Use explicit type for arrays +csharp_style_explicit_array_type = true:suggestion + +# Use simplified accessors (e.g., private set) +csharp_style_expression_bodied_accessors = false:suggestion +csharp_style_expression_bodied_properties = false:suggestion +csharp_style_expression_bodied_methods = false:suggestion + +# Prefer to use `_` or `this.` for member access +csharp_prefer_static_local_function = true:suggestion +csharp_style_qualification_for_field = false:suggestion +csharp_style_qualification_for_property = false:suggestion +csharp_style_qualification_for_method = false:suggestion +csharp_style_qualification_for_event = false:suggestion + +# Prefer throwing an exception over returning null (if applicable) +csharp_style_throw_expression = true:suggestion + +#### .NET Language Style Rules (dotnet_style_) #### + +# Use 'using' declarations instead of using statements +dotnet_style_prefer_dispose_pattern = true:suggestion + +# Prefer collection expressions for arrays and lists (if targeting C# 12+) +dotnet_style_prefer_collection_expression = true:suggestion + +# Prefer to use C# built-in type names (e.g., 'int' instead of 'Int32') +dotnet_style_predefined_type_names = true:suggestion + +#### Formatting Rules (csharp_space_, csharp_indent_) #### + +# Control spacing around binary operators (e.g., `a = b` vs `a=b`) +csharp_space_around_binary_operators = true:suggestion + +# Control space within parentheses (e.g., `( a )` vs `(a)`) +csharp_space_within_parentheses = false:suggestion + +# Control new line for braces (e.g., `if () {` vs `if () \n {`) +csharp_new_line_before_open_brace = all:suggestion +csharp_new_line_before_members_in_classes = true:suggestion +csharp_new_line_before_members_in_structs = true:suggestion + +# Ensure using directives are sorted alphabetically +csharp_using_directive_placement = inside_namespace:suggestion diff --git a/QueryBuilder.Tests/AggregateTests.cs b/QueryBuilder.Tests/AggregateTests.cs index 68a69842..eab8a9d2 100644 --- a/QueryBuilder.Tests/AggregateTests.cs +++ b/QueryBuilder.Tests/AggregateTests.cs @@ -1,92 +1,82 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests -{ - public class AggregateTests : TestSupport - { - [Fact] - public void Count() - { - var query = new Query("A").AsCount(); - - var c = Compile(query); - - Assert.Equal("SELECT COUNT(*) AS [count] FROM [A]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT COUNT(*) AS `count` FROM `A`", c[EngineCodes.MySql]); - Assert.Equal("SELECT COUNT(*) AS \"count\" FROM \"A\"", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT COUNT(*) AS \"COUNT\" FROM \"A\"", c[EngineCodes.Firebird]); - } - - [Fact] - public void CountMultipleColumns() - { - var query = new Query("A").AsCount(new[] { "ColumnA", "ColumnB" }); - - var c = Compile(query); - - Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT 1 FROM [A] WHERE [ColumnA] IS NOT NULL AND [ColumnB] IS NOT NULL) AS [countQuery]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void DistinctCount() - { - var query = new Query("A").Distinct().AsCount(); - - var c = Compile(query); - - Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT DISTINCT * FROM [A]) AS [countQuery]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void DistinctCountMultipleColumns() - { - var query = new Query("A").Distinct().AsCount(new[] { "ColumnA", "ColumnB" }); - - var c = Compile(query); - - Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT DISTINCT [ColumnA], [ColumnB] FROM [A]) AS [countQuery]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void Average() - { - var query = new Query("A").AsAverage("TTL"); - - var c = Compile(query); - - Assert.Equal("SELECT AVG([TTL]) AS [avg] FROM [A]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void Sum() - { - var query = new Query("A").AsSum("PacketsDropped"); - - var c = Compile(query); - - Assert.Equal("SELECT SUM([PacketsDropped]) AS [sum] FROM [A]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void Max() - { - var query = new Query("A").AsMax("LatencyMs"); - - var c = Compile(query); - - Assert.Equal("SELECT MAX([LatencyMs]) AS [max] FROM [A]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void Min() - { - var query = new Query("A").AsMin("LatencyMs"); - - var c = Compile(query); - - Assert.Equal("SELECT MIN([LatencyMs]) AS [min] FROM [A]", c[EngineCodes.SqlServer]); - } - } -} +namespace SqlKata.Tests; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class AggregateTests : TestSupport { + [Fact] + public void Count() { + var query = new Query("A").AsCount(); + + var c = Compile(query); + + Assert.Equal("SELECT COUNT(*) AS [count] FROM [A]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT COUNT(*) AS `count` FROM `A`", c[EngineCodes.MySql]); + Assert.Equal("SELECT COUNT(*) AS \"count\" FROM \"A\"", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT COUNT(*) AS \"COUNT\" FROM \"A\"", c[EngineCodes.Firebird]); + } + + [Fact] + public void CountMultipleColumns() { + var query = new Query("A").AsCount(new[] { "ColumnA", "ColumnB" }); + + var c = Compile(query); + + Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT 1 FROM [A] WHERE [ColumnA] IS NOT NULL AND [ColumnB] IS NOT NULL) AS [countQuery]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void DistinctCount() { + var query = new Query("A").Distinct().AsCount(); + + var c = Compile(query); + + Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT DISTINCT * FROM [A]) AS [countQuery]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void DistinctCountMultipleColumns() { + var query = new Query("A").Distinct().AsCount(new[] { "ColumnA", "ColumnB" }); + + var c = Compile(query); + + Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT DISTINCT [ColumnA], [ColumnB] FROM [A]) AS [countQuery]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void Average() { + var query = new Query("A").AsAverage("TTL"); + + var c = Compile(query); + + Assert.Equal("SELECT AVG([TTL]) AS [avg] FROM [A]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void Sum() { + var query = new Query("A").AsSum("PacketsDropped"); + + var c = Compile(query); + + Assert.Equal("SELECT SUM([PacketsDropped]) AS [sum] FROM [A]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void Max() { + var query = new Query("A").AsMax("LatencyMs"); + + var c = Compile(query); + + Assert.Equal("SELECT MAX([LatencyMs]) AS [max] FROM [A]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void Min() { + var query = new Query("A").AsMin("LatencyMs"); + + var c = Compile(query); + + Assert.Equal("SELECT MIN([LatencyMs]) AS [min] FROM [A]", c[EngineCodes.SqlServer]); + } +} diff --git a/QueryBuilder.Tests/DefineTest.cs b/QueryBuilder.Tests/DefineTest.cs index 0b5ff292..56b22b0f 100644 --- a/QueryBuilder.Tests/DefineTest.cs +++ b/QueryBuilder.Tests/DefineTest.cs @@ -1,427 +1,407 @@ -using static SqlKata.Expressions; -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests -{ - /// - /// If you want to test this queries against a database use NorthWind database - /// - public class DefineTest : TestSupport - { - - [Fact] - public void Test_Define_Where() - { - var query = new Query("Products") - .Define("@name", "Anto") - .Where("ProductName", Variable("@name")); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Products] WHERE [ProductName] = 'Anto'", c[EngineCodes.SqlServer]); - - } - - [Fact] - public void Test_Define_SubQuery() - { - - var subquery = new Query("Products") - .AsAverage("unitprice") - .Define("@UnitsInSt", 10) - .Where("UnitsInStock", ">", Variable("@UnitsInSt")); - - var query = new Query("Products") - .Where("unitprice", ">", subquery) - .Where("UnitsOnOrder", ">", 5); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Products] WHERE [unitprice] > (SELECT AVG([unitprice]) AS [avg] FROM [Products] WHERE [UnitsInStock] > 10) AND [UnitsOnOrder] > 5", c[EngineCodes.SqlServer]); - - } - - - [Fact] - public void Test_Define_WhereEnds() - { - - var query1 = new Query("Products") - .Select("ProductId") - .Define("@product", "Coffee") - .WhereEnds("ProductName", Variable("@product")); - - - var query2 = new Query("Products") - .Select("ProductId", "ProductName") - .Define("@product", "Coffee") - .WhereEnds("ProductName", Variable("@product"), true); - - var c1 = Compile(query1); - var c2 = Compile(query2); - - Assert.Equal("SELECT [ProductId] FROM [Products] WHERE LOWER([ProductName]) like '%coffee'", c1[EngineCodes.SqlServer]); - - Assert.Equal("SELECT [ProductId], [ProductName] FROM [Products] WHERE [ProductName] like '%Coffee'", c2[EngineCodes.SqlServer]); - - } - - - - [Fact] - public void Test_Define_WhereStarts() - { - - - var query1 = new Query("Products") - .Select("ProductId", "QuantityPerUnit") - .Define("@perUnit", "12") - .WhereStarts("QuantityPerUnit", Variable("@perUnit")); - - - var query2 = new Query("Products") - .Select("ProductId", "QuantityPerUnit") - .Define("@perUnit", "12") - .WhereStarts("QuantityPerUnit", Variable("@perUnit"), true); - - var c1 = Compile(query1); - var c2 = Compile(query2); - - Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE LOWER([QuantityPerUnit]) like '12%'", c1[EngineCodes.SqlServer]); - Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE [QuantityPerUnit] like '12%'", c2[EngineCodes.SqlServer]); - } - - - [Fact] - public void Test_Define_WhereContains() - { - - var query1 = new Query("Products") - .Define("@perUnit", "500") - .Select("ProductId", "QuantityPerUnit") - .WhereContains("QuantityPerUnit", Variable("@perUnit")); - - - var query2 = new Query("Products") - .Define("@perUnit", "500") - .Select("ProductId", "QuantityPerUnit") - .WhereContains("QuantityPerUnit", Variable("@perUnit"), true); - - var c1 = Compile(query1); - var c2 = Compile(query2); - - Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE LOWER([QuantityPerUnit]) like '%500%'", c1[EngineCodes.SqlServer]); - Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE [QuantityPerUnit] like '%500%'", c2[EngineCodes.SqlServer]); - - } - - - [Fact] - public void Test_Define_WhereLike() - { - var query1 = new Query("Products") - .Select("ProductId", "ProductName", "SupplierID") - .Define("@id", "20") - .WhereLike("SupplierID", Variable("@id")); - - - var query2 = new Query("Products") - .Select("ProductId", "ProductName", "SupplierID") - .Define("@id", "20") - .WhereLike("SupplierID", Variable("@id"), true); - - var c1 = Compile(query1); - var c2 = Compile(query2); - - Assert.Equal("SELECT [ProductId], [ProductName], [SupplierID] FROM [Products] WHERE LOWER([SupplierID]) like '20'", c1[EngineCodes.SqlServer]); - - Assert.Equal("SELECT [ProductId], [ProductName], [SupplierID] FROM [Products] WHERE [SupplierID] like '20'", c2[EngineCodes.SqlServer]); - } - - - [Fact] - public void Test_Define_WhereInSubquery() - { - - var subquery = new Query("Orders") - .Define("@shipId", 3) - .Select("ShipVia").Where("ShipVia", Variable("@shipId")); - - - var query1 = new Query("Shippers") - .Select("ShipperID", "CompanyName") - .WhereIn("ShipperID", subquery); - - - var c1 = Compile(query1); - - Assert.Equal("SELECT [ShipperID], [CompanyName] FROM [Shippers] WHERE [ShipperID] IN (SELECT [ShipVia] FROM [Orders] WHERE [ShipVia] = 3)", c1[EngineCodes.SqlServer]); - } - - [Fact] - public void Test_Define_Having() - { - var c = Compile(new Query("Table") - .Define("@foo", 1) - .Having("Id", "=", Variable("@foo"))); - - Assert.Equal("SELECT * FROM [Table] HAVING [Id] = 1", c[EngineCodes.SqlServer]); - } - - /* - [Fact] - public void Test_Define_HavingRaw() - { - var query1 = new Query("Orders") - .Define("@count", 80) - .Select("Employees.LastName") - .SelectRaw("COUNT(Orders.OrderID) AS NumberOfOrders") - .Join("Employees", "Employees.EmployeeID", "Orders.EmployeeID") - .GroupBy("LastName") - .HavingRaw("COUNT(Orders.OrderID) > @count"); - - var c = Compile(query1); - - Assert.Equal("SELECT [Employees].[LastName], COUNT(Orders.OrderID) AS NumberOfOrders FROM [Orders] \nINNER JOIN [Employees] ON [Employees].[EmployeeID] = [Orders].[EmployeeID] GROUP BY [LastName] HAVING COUNT(Orders.OrderID) > 80", c[EngineCodes.SqlServer]); - - } - */ - - [Fact] - public void Test_Define_HavingStarts() - { - - var query = new Query("Customers") - .Define("@label", "U") - .SelectRaw("COUNT(CustomerID)") - .Select("Country") - .GroupBy("Country") - .HavingStarts("Country", Variable("@label")); - - var c = Compile(query); - - Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like 'u%'", c[EngineCodes.SqlServer]); - - } - - - - [Fact] - public void Test_Define_Having_Ends() - { - var query = new Query("Customers") - .Define("@label", "d") - .SelectRaw("COUNT(CustomerID)") - .Select("Country") - .GroupBy("Country") - .HavingEnds("Country", Variable("@label")); - - var c = Compile(query); - - Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like '%d'", c[EngineCodes.SqlServer]); - } - - - [Fact] - public void Test_Define_Having_Contains() - { - - - var query = new Query("Customers") - .Define("@label", "d") - .SelectRaw("COUNT(CustomerID)") - .Select("Country") - .GroupBy("Country") - .HavingContains("Country", Variable("@label")); - - var c = Compile(query); - - Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like '%d%'", c[EngineCodes.SqlServer]); - - } - - - [Fact] - public void Test_Define_NestedCondition() - { - var query = new Query("Orders") - .Define("@shipReg", null) - .Define("@one", 1) - .Where(q => - q.Where("ShipRegion", "!=", Variable("@shipReg")) - // .WhereRaw("1 = @one") - ).AsCount(); - - var c = Compile(query); - - Assert.Equal("SELECT COUNT(*) AS [count] FROM [Orders] WHERE ([ShipRegion] != NULL)", c[EngineCodes.SqlServer]); - - } - - - [Fact] - public void Test_Define_WhereDate() - { - var dateObj = new System.DateTime(year: 1996, month: 8, day: 1); - - var query = new Query("Orders") - .Define("@d", dateObj) - .WhereDate("RequiredDate", Variable("@d")); - - - var query2 = new Query("Orders") - .Define("@d", 1996) - .WhereDatePart("year", "RequiredDate", "=", Variable("@d")); - - var query3 = new Query("Orders") - .Define("@d", "00:00:00") - .WhereTime("RequiredDate", "!=", Variable("@d")); - - var c = Compile(query); - var c2 = Compile(query2); - var c3 = Compile(query3); - - Assert.Equal("SELECT * FROM [Orders] WHERE CAST([RequiredDate] AS DATE) = '1996-08-01'", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM \"Orders\" WHERE \"RequiredDate\"::date = '1996-08-01'", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT * FROM \"Orders\" WHERE strftime('%Y-%m-%d', \"RequiredDate\") = cast('1996-08-01' as text)", c[EngineCodes.Sqlite]); - Assert.Equal("SELECT * FROM \"ORDERS\" WHERE CAST(\"REQUIREDDATE\" as DATE) = '1996-08-01'", c[EngineCodes.Firebird]); - - - - Assert.Equal("SELECT * FROM [Orders] WHERE DATEPART(YEAR, [RequiredDate]) = 1996", c2[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM [Orders] WHERE CAST([RequiredDate] AS TIME) != '00:00:00'", c3[EngineCodes.SqlServer]); - - } - - - [Fact] - public void Test_Define_WhereExists() - { - var query = new Query("Customers") - .WhereExists(q => q.From("Orders") - .Define("@postal", "8200") - .Where("ShipPostalCode", Variable("@postal")) - ); - - var c = Compile(query); - Assert.Equal("SELECT * FROM [Customers] WHERE EXISTS (SELECT 1 FROM [Orders] WHERE [ShipPostalCode] = '8200')", c[EngineCodes.SqlServer]); - } - - - - [Fact] - public void Test_Define_With() - { - - var query = new Query("Products") - .Define("@unit", 10) - .Join("Categories", "Categories.CategoryID", "Products.CategoryID") - .Select("Categories.CategoryName", "Products.UnitPrice") - .Where("Products.UnitPrice", ">", Variable("@unit")); - - var queryCTe = new Query("prodCTE") - .With("prodCTE", query); - - var c = Compile(queryCTe); - - - Assert.Equal("WITH [prodCTE] AS (SELECT [Categories].[CategoryName], [Products].[UnitPrice] FROM [Products] \nINNER JOIN [Categories] ON [Categories].[CategoryID] = [Products].[CategoryID] WHERE [Products].[UnitPrice] > 10)\nSELECT * FROM [prodCTE]", c[EngineCodes.SqlServer]); - } - - - - /* - [Fact] - public void Test_Define_WithRaw() - { - - //WithRaw - var query = new Query("prodCTE") - .Define("@unit", 10) - .Define("@foo", 2) - .Select("CategoryName", "UnitPrice") - .WithRaw("prodCTE", "SELECT c.CategoryName, p.UnitPrice FROM Products p INNER JOIN Categories c ON c.CategoryID = p.CategoryID WHERE p.UnitPrice > @unit AND 2 = @foo"); - - var c = Compile(query); - - Assert.Equal("WITH [prodCTE] AS (SELECT c.CategoryName, p.UnitPrice FROM Products p INNER JOIN Categories c ON c.CategoryID = p.CategoryID WHERE p.UnitPrice > 10 AND 2 = 2)\nSELECT [CategoryName], [UnitPrice] FROM [prodCTE]", c[EngineCodes.SqlServer]); - - } - */ - - // - [Fact] - public void Test_Define_Union() - { - var q1 = new Query("Suppliers") - .Define("@foo", "Beirut") - .Select("City") - .Where("City", Variable("@foo")); - - var q2 = new Query("Customers") - .Define("@city", "Z") - .Select("City") - .Union(q1) - .WhereNotLike("City", Variable("@city")); - - var c = Compile(q2); - Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') UNION SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]); - } - - - [Fact] - public void Test_Define_Except() - { - var q1 = new Query("Suppliers") - .Define("@foo", "Beirut") - .Select("City") - .Where("City", Variable("@foo")); - - var q2 = new Query("Customers") - .Define("@city", "Z") - .Select("City") - .Except(q1) - .WhereNotLike("City", Variable("@city")); - - var c = Compile(q2); - Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') EXCEPT SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void Test_Define_Intersect() - { - var q1 = new Query("Suppliers") - .Define("@foo", "Beirut") - .Select("City") - .Where("City", Variable("@foo")); - - var q2 = new Query("Customers") - .Define("@city", "Z") - .Select("City") - .Intersect(q1) - .WhereNotLike("City", Variable("@city")); - - var c = Compile(q2); - Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') INTERSECT SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]); - } - - /* - [Fact] - public void Test_Define_CombineRaw() - { - - var query = new Query("Customers") - .Define("@foo", 1) - .Define("@faa", 2) - .Select("City") - .CombineRaw("UNION ALL SELECT City FROM Suppliers WHERE 1 = @foo AND 2 = @faa"); - - var c = Compile(query); - Assert.Equal("SELECT [City] FROM [Customers] UNION ALL SELECT City FROM Suppliers WHERE 1 = 1 AND 2 = 2", c[EngineCodes.SqlServer]); - } - */ - - } -} +namespace SqlKata.Tests; + +using static SqlKata.Expressions; +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +/// +/// If you want to test this queries against a database use NorthWind database +/// +public class DefineTest : TestSupport { + + [Fact] + public void Test_Define_Where() { + var query = new Query("Products") + .Define("@name", "Anto") + .Where("ProductName", Variable("@name")); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Products] WHERE [ProductName] = 'Anto'", c[EngineCodes.SqlServer]); + + } + + [Fact] + public void Test_Define_SubQuery() { + + var subquery = new Query("Products") + .AsAverage("unitprice") + .Define("@UnitsInSt", 10) + .Where("UnitsInStock", ">", Variable("@UnitsInSt")); + + var query = new Query("Products") + .Where("unitprice", ">", subquery) + .Where("UnitsOnOrder", ">", 5); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Products] WHERE [unitprice] > (SELECT AVG([unitprice]) AS [avg] FROM [Products] WHERE [UnitsInStock] > 10) AND [UnitsOnOrder] > 5", c[EngineCodes.SqlServer]); + + } + + + [Fact] + public void Test_Define_WhereEnds() { + + var query1 = new Query("Products") + .Select("ProductId") + .Define("@product", "Coffee") + .WhereEnds("ProductName", Variable("@product")); + + + var query2 = new Query("Products") + .Select("ProductId", "ProductName") + .Define("@product", "Coffee") + .WhereEnds("ProductName", Variable("@product"), true); + + var c1 = Compile(query1); + var c2 = Compile(query2); + + Assert.Equal("SELECT [ProductId] FROM [Products] WHERE LOWER([ProductName]) like '%coffee'", c1[EngineCodes.SqlServer]); + + Assert.Equal("SELECT [ProductId], [ProductName] FROM [Products] WHERE [ProductName] like '%Coffee'", c2[EngineCodes.SqlServer]); + + } + + + + [Fact] + public void Test_Define_WhereStarts() { + + + var query1 = new Query("Products") + .Select("ProductId", "QuantityPerUnit") + .Define("@perUnit", "12") + .WhereStarts("QuantityPerUnit", Variable("@perUnit")); + + + var query2 = new Query("Products") + .Select("ProductId", "QuantityPerUnit") + .Define("@perUnit", "12") + .WhereStarts("QuantityPerUnit", Variable("@perUnit"), true); + + var c1 = Compile(query1); + var c2 = Compile(query2); + + Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE LOWER([QuantityPerUnit]) like '12%'", c1[EngineCodes.SqlServer]); + Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE [QuantityPerUnit] like '12%'", c2[EngineCodes.SqlServer]); + } + + + [Fact] + public void Test_Define_WhereContains() { + + var query1 = new Query("Products") + .Define("@perUnit", "500") + .Select("ProductId", "QuantityPerUnit") + .WhereContains("QuantityPerUnit", Variable("@perUnit")); + + + var query2 = new Query("Products") + .Define("@perUnit", "500") + .Select("ProductId", "QuantityPerUnit") + .WhereContains("QuantityPerUnit", Variable("@perUnit"), true); + + var c1 = Compile(query1); + var c2 = Compile(query2); + + Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE LOWER([QuantityPerUnit]) like '%500%'", c1[EngineCodes.SqlServer]); + Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE [QuantityPerUnit] like '%500%'", c2[EngineCodes.SqlServer]); + + } + + + [Fact] + public void Test_Define_WhereLike() { + var query1 = new Query("Products") + .Select("ProductId", "ProductName", "SupplierID") + .Define("@id", "20") + .WhereLike("SupplierID", Variable("@id")); + + + var query2 = new Query("Products") + .Select("ProductId", "ProductName", "SupplierID") + .Define("@id", "20") + .WhereLike("SupplierID", Variable("@id"), true); + + var c1 = Compile(query1); + var c2 = Compile(query2); + + Assert.Equal("SELECT [ProductId], [ProductName], [SupplierID] FROM [Products] WHERE LOWER([SupplierID]) like '20'", c1[EngineCodes.SqlServer]); + + Assert.Equal("SELECT [ProductId], [ProductName], [SupplierID] FROM [Products] WHERE [SupplierID] like '20'", c2[EngineCodes.SqlServer]); + } + + + [Fact] + public void Test_Define_WhereInSubquery() { + + var subquery = new Query("Orders") + .Define("@shipId", 3) + .Select("ShipVia").Where("ShipVia", Variable("@shipId")); + + + var query1 = new Query("Shippers") + .Select("ShipperID", "CompanyName") + .WhereIn("ShipperID", subquery); + + + var c1 = Compile(query1); + + Assert.Equal("SELECT [ShipperID], [CompanyName] FROM [Shippers] WHERE [ShipperID] IN (SELECT [ShipVia] FROM [Orders] WHERE [ShipVia] = 3)", c1[EngineCodes.SqlServer]); + } + + [Fact] + public void Test_Define_Having() { + var c = Compile(new Query("Table") + .Define("@foo", 1) + .Having("Id", "=", Variable("@foo"))); + + Assert.Equal("SELECT * FROM [Table] HAVING [Id] = 1", c[EngineCodes.SqlServer]); + } + + /* + [Fact] + public void Test_Define_HavingRaw() + { + var query1 = new Query("Orders") + .Define("@count", 80) + .Select("Employees.LastName") + .SelectRaw("COUNT(Orders.OrderID) AS NumberOfOrders") + .Join("Employees", "Employees.EmployeeID", "Orders.EmployeeID") + .GroupBy("LastName") + .HavingRaw("COUNT(Orders.OrderID) > @count"); + + var c = Compile(query1); + + Assert.Equal("SELECT [Employees].[LastName], COUNT(Orders.OrderID) AS NumberOfOrders FROM [Orders] \nINNER JOIN [Employees] ON [Employees].[EmployeeID] = [Orders].[EmployeeID] GROUP BY [LastName] HAVING COUNT(Orders.OrderID) > 80", c[EngineCodes.SqlServer]); + + } + */ + + [Fact] + public void Test_Define_HavingStarts() { + + var query = new Query("Customers") + .Define("@label", "U") + .SelectRaw("COUNT(CustomerID)") + .Select("Country") + .GroupBy("Country") + .HavingStarts("Country", Variable("@label")); + + var c = Compile(query); + + Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like 'u%'", c[EngineCodes.SqlServer]); + + } + + + + [Fact] + public void Test_Define_Having_Ends() { + var query = new Query("Customers") + .Define("@label", "d") + .SelectRaw("COUNT(CustomerID)") + .Select("Country") + .GroupBy("Country") + .HavingEnds("Country", Variable("@label")); + + var c = Compile(query); + + Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like '%d'", c[EngineCodes.SqlServer]); + } + + + [Fact] + public void Test_Define_Having_Contains() { + + + var query = new Query("Customers") + .Define("@label", "d") + .SelectRaw("COUNT(CustomerID)") + .Select("Country") + .GroupBy("Country") + .HavingContains("Country", Variable("@label")); + + var c = Compile(query); + + Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like '%d%'", c[EngineCodes.SqlServer]); + + } + + + [Fact] + public void Test_Define_NestedCondition() { + var query = new Query("Orders") + .Define("@shipReg", null) + .Define("@one", 1) + .Where(q => + q.Where("ShipRegion", "!=", Variable("@shipReg")) + // .WhereRaw("1 = @one") + ).AsCount(); + + var c = Compile(query); + + Assert.Equal("SELECT COUNT(*) AS [count] FROM [Orders] WHERE ([ShipRegion] != NULL)", c[EngineCodes.SqlServer]); + + } + + + [Fact] + public void Test_Define_WhereDate() { + var dateObj = new System.DateTime(year: 1996, month: 8, day: 1); + + var query = new Query("Orders") + .Define("@d", dateObj) + .WhereDate("RequiredDate", Variable("@d")); + + + var query2 = new Query("Orders") + .Define("@d", 1996) + .WhereDatePart("year", "RequiredDate", "=", Variable("@d")); + + var query3 = new Query("Orders") + .Define("@d", "00:00:00") + .WhereTime("RequiredDate", "!=", Variable("@d")); + + var c = Compile(query); + var c2 = Compile(query2); + var c3 = Compile(query3); + + Assert.Equal("SELECT * FROM [Orders] WHERE CAST([RequiredDate] AS DATE) = '1996-08-01'", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM \"Orders\" WHERE \"RequiredDate\"::date = '1996-08-01'", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT * FROM \"Orders\" WHERE strftime('%Y-%m-%d', \"RequiredDate\") = cast('1996-08-01' as text)", c[EngineCodes.Sqlite]); + Assert.Equal("SELECT * FROM \"ORDERS\" WHERE CAST(\"REQUIREDDATE\" as DATE) = '1996-08-01'", c[EngineCodes.Firebird]); + + + + Assert.Equal("SELECT * FROM [Orders] WHERE DATEPART(YEAR, [RequiredDate]) = 1996", c2[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM [Orders] WHERE CAST([RequiredDate] AS TIME) != '00:00:00'", c3[EngineCodes.SqlServer]); + + } + + + [Fact] + public void Test_Define_WhereExists() { + var query = new Query("Customers") + .WhereExists(q => q.From("Orders") + .Define("@postal", "8200") + .Where("ShipPostalCode", Variable("@postal")) + ); + + var c = Compile(query); + Assert.Equal("SELECT * FROM [Customers] WHERE EXISTS (SELECT 1 FROM [Orders] WHERE [ShipPostalCode] = '8200')", c[EngineCodes.SqlServer]); + } + + + + [Fact] + public void Test_Define_With() { + + var query = new Query("Products") + .Define("@unit", 10) + .Join("Categories", "Categories.CategoryID", "Products.CategoryID") + .Select("Categories.CategoryName", "Products.UnitPrice") + .Where("Products.UnitPrice", ">", Variable("@unit")); + + var queryCTe = new Query("prodCTE") + .With("prodCTE", query); + + var c = Compile(queryCTe); + + + Assert.Equal("WITH [prodCTE] AS (SELECT [Categories].[CategoryName], [Products].[UnitPrice] FROM [Products] \nINNER JOIN [Categories] ON [Categories].[CategoryID] = [Products].[CategoryID] WHERE [Products].[UnitPrice] > 10)\nSELECT * FROM [prodCTE]", c[EngineCodes.SqlServer]); + } + + + + /* + [Fact] + public void Test_Define_WithRaw() + { + + //WithRaw + var query = new Query("prodCTE") + .Define("@unit", 10) + .Define("@foo", 2) + .Select("CategoryName", "UnitPrice") + .WithRaw("prodCTE", "SELECT c.CategoryName, p.UnitPrice FROM Products p INNER JOIN Categories c ON c.CategoryID = p.CategoryID WHERE p.UnitPrice > @unit AND 2 = @foo"); + + var c = Compile(query); + + Assert.Equal("WITH [prodCTE] AS (SELECT c.CategoryName, p.UnitPrice FROM Products p INNER JOIN Categories c ON c.CategoryID = p.CategoryID WHERE p.UnitPrice > 10 AND 2 = 2)\nSELECT [CategoryName], [UnitPrice] FROM [prodCTE]", c[EngineCodes.SqlServer]); + + } + */ + + // + [Fact] + public void Test_Define_Union() { + var q1 = new Query("Suppliers") + .Define("@foo", "Beirut") + .Select("City") + .Where("City", Variable("@foo")); + + var q2 = new Query("Customers") + .Define("@city", "Z") + .Select("City") + .Union(q1) + .WhereNotLike("City", Variable("@city")); + + var c = Compile(q2); + Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') UNION SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]); + } + + + [Fact] + public void Test_Define_Except() { + var q1 = new Query("Suppliers") + .Define("@foo", "Beirut") + .Select("City") + .Where("City", Variable("@foo")); + + var q2 = new Query("Customers") + .Define("@city", "Z") + .Select("City") + .Except(q1) + .WhereNotLike("City", Variable("@city")); + + var c = Compile(q2); + Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') EXCEPT SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void Test_Define_Intersect() { + var q1 = new Query("Suppliers") + .Define("@foo", "Beirut") + .Select("City") + .Where("City", Variable("@foo")); + + var q2 = new Query("Customers") + .Define("@city", "Z") + .Select("City") + .Intersect(q1) + .WhereNotLike("City", Variable("@city")); + + var c = Compile(q2); + Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') INTERSECT SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]); + } + + /* + [Fact] + public void Test_Define_CombineRaw() + { + + var query = new Query("Customers") + .Define("@foo", 1) + .Define("@faa", 2) + .Select("City") + .CombineRaw("UNION ALL SELECT City FROM Suppliers WHERE 1 = @foo AND 2 = @faa"); + + var c = Compile(query); + Assert.Equal("SELECT [City] FROM [Customers] UNION ALL SELECT City FROM Suppliers WHERE 1 = 1 AND 2 = 2", c[EngineCodes.SqlServer]); + } + */ + +} diff --git a/QueryBuilder.Tests/DeleteTests.cs b/QueryBuilder.Tests/DeleteTests.cs index 14fd4043..faa35a10 100644 --- a/QueryBuilder.Tests/DeleteTests.cs +++ b/QueryBuilder.Tests/DeleteTests.cs @@ -1,49 +1,44 @@ -using SqlKata.Compilers; -using SqlKata.Extensions; -using SqlKata.Tests.Infrastructure; -using System; -using System.Linq; -using Xunit; - -namespace SqlKata.Tests -{ - public class DeleteTests : TestSupport - { - [Fact] - public void BasicDelete() - { - var q = new Query("Posts").AsDelete(); - - var c = Compile(q); - - Assert.Equal("DELETE FROM [Posts]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void DeleteWithJoin() - { - var q = new Query("Posts") - .Join("Authors", "Authors.Id", "Posts.AuthorId") - .Where("Authors.Id", 5) - .AsDelete(); - - var c = Compile(q); - - Assert.Equal("DELETE [Posts] FROM [Posts] \nINNER JOIN [Authors] ON [Authors].[Id] = [Posts].[AuthorId] WHERE [Authors].[Id] = 5", c[EngineCodes.SqlServer]); - Assert.Equal("DELETE `Posts` FROM `Posts` \nINNER JOIN `Authors` ON `Authors`.`Id` = `Posts`.`AuthorId` WHERE `Authors`.`Id` = 5", c[EngineCodes.MySql]); - } - - [Fact] - public void DeleteWithJoinAndAlias() - { - var q = new Query("Posts as P") - .Join("Authors", "Authors.Id", "P.AuthorId") - .Where("Authors.Id", 5) - .AsDelete(); - - var c = Compile(q); - - Assert.Equal("DELETE [P] FROM [Posts] AS [P] \nINNER JOIN [Authors] ON [Authors].[Id] = [P].[AuthorId] WHERE [Authors].[Id] = 5", c[EngineCodes.SqlServer]); - } - } -} +namespace SqlKata.Tests; + +using SqlKata.Compilers; +using SqlKata.Extensions; +using SqlKata.Tests.Infrastructure; +using System; +using System.Linq; +using Xunit; + +public class DeleteTests : TestSupport { + [Fact] + public void BasicDelete() { + var q = new Query("Posts").AsDelete(); + + var c = Compile(q); + + Assert.Equal("DELETE FROM [Posts]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void DeleteWithJoin() { + var q = new Query("Posts") + .Join("Authors", "Authors.Id", "Posts.AuthorId") + .Where("Authors.Id", 5) + .AsDelete(); + + var c = Compile(q); + + Assert.Equal("DELETE [Posts] FROM [Posts] \nINNER JOIN [Authors] ON [Authors].[Id] = [Posts].[AuthorId] WHERE [Authors].[Id] = 5", c[EngineCodes.SqlServer]); + Assert.Equal("DELETE `Posts` FROM `Posts` \nINNER JOIN `Authors` ON `Authors`.`Id` = `Posts`.`AuthorId` WHERE `Authors`.`Id` = 5", c[EngineCodes.MySql]); + } + + [Fact] + public void DeleteWithJoinAndAlias() { + var q = new Query("Posts as P") + .Join("Authors", "Authors.Id", "P.AuthorId") + .Where("Authors.Id", 5) + .AsDelete(); + + var c = Compile(q); + + Assert.Equal("DELETE [P] FROM [Posts] AS [P] \nINNER JOIN [Authors] ON [Authors].[Id] = [P].[AuthorId] WHERE [Authors].[Id] = 5", c[EngineCodes.SqlServer]); + } +} diff --git a/QueryBuilder.Tests/ExecutionTests.cs b/QueryBuilder.Tests/ExecutionTests.cs index 5d41e7fd..f02555f5 100644 --- a/QueryBuilder.Tests/ExecutionTests.cs +++ b/QueryBuilder.Tests/ExecutionTests.cs @@ -1,36 +1,30 @@ -using System; -using SqlKata.Execution; -using Xunit; - -namespace SqlKata.Tests -{ - public class ExecutionTests - { - [Fact] - public void ShouldThrowException() - { - Assert.Throws(() => - { - new Query("Books").Get(); - }); - } - - [Fact] - public void TimeoutShouldBeCarriedToNewCreatedFactory() - { - var db = new QueryFactory(); - db.QueryTimeout = 4000; - var newFactory = QueryExtensions.CreateQueryFactory(db.Query()); - Assert.Equal(db.QueryTimeout, newFactory.QueryTimeout); - } - - [Fact(Skip = "timeout over cloned xQuery is not supported yet")] - public void TimeoutShouldBeCarriedToNewCreatedFactoryAfterClone() - { - var db = new QueryFactory(); - db.QueryTimeout = 4000; - var newFactory = QueryExtensions.CreateQueryFactory(db.Query().Clone()); - Assert.Equal(db.QueryTimeout, newFactory.QueryTimeout); - } - } -} +namespace SqlKata.Tests; + +using System; +using SqlKata.Execution; +using Xunit; + +public class ExecutionTests { + [Fact] + public void ShouldThrowException() { + Assert.Throws(() => { + new Query("Books").Get(); + }); + } + + [Fact] + public void TimeoutShouldBeCarriedToNewCreatedFactory() { + var db = new QueryFactory(); + db.QueryTimeout = 4000; + var newFactory = QueryExtensions.CreateQueryFactory(db.Query()); + Assert.Equal(db.QueryTimeout, newFactory.QueryTimeout); + } + + [Fact(Skip = "timeout over cloned xQuery is not supported yet")] + public void TimeoutShouldBeCarriedToNewCreatedFactoryAfterClone() { + var db = new QueryFactory(); + db.QueryTimeout = 4000; + var newFactory = QueryExtensions.CreateQueryFactory(db.Query().Clone()); + Assert.Equal(db.QueryTimeout, newFactory.QueryTimeout); + } +} diff --git a/QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs b/QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs index b449bae8..677919d4 100644 --- a/QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs +++ b/QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs @@ -1,55 +1,48 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.Firebird -{ - public class FirebirdLimitTests : TestSupport - { - private readonly FirebirdCompiler compiler; - - public FirebirdLimitTests() - { - compiler = Compilers.Get(EngineCodes.Firebird); - } - - [Fact] - public void NoLimitNorOffset() - { - var query = new Query("Table"); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void LimitOnly() - { - var query = new Query("Table").Limit(10); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void OffsetOnly() - { - var query = new Query("Table").Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void LimitAndOffset() - { - var query = new Query("Table").Limit(5).Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Equal("ROWS ? TO ?", compiler.CompileLimit(ctx)); - Assert.Equal(21L, ctx.Bindings[0]); - Assert.Equal(25L, ctx.Bindings[1]); - Assert.Equal(2, ctx.Bindings.Count); - } - } -} +namespace SqlKata.Tests.Firebird; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class FirebirdLimitTests : TestSupport { + private readonly FirebirdCompiler compiler; + + public FirebirdLimitTests() { + compiler = Compilers.Get(EngineCodes.Firebird); + } + + [Fact] + public void NoLimitNorOffset() { + var query = new Query("Table"); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void LimitOnly() { + var query = new Query("Table").Limit(10); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void OffsetOnly() { + var query = new Query("Table").Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void LimitAndOffset() { + var query = new Query("Table").Limit(5).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("ROWS ? TO ?", compiler.CompileLimit(ctx)); + Assert.Equal(21L, ctx.Bindings[0]); + Assert.Equal(25L, ctx.Bindings[1]); + Assert.Equal(2, ctx.Bindings.Count); + } +} diff --git a/QueryBuilder.Tests/GeneralTests.cs b/QueryBuilder.Tests/GeneralTests.cs index 63fb3d3f..4e0dd202 100644 --- a/QueryBuilder.Tests/GeneralTests.cs +++ b/QueryBuilder.Tests/GeneralTests.cs @@ -1,605 +1,564 @@ -using SqlKata.Compilers; -using SqlKata.Extensions; -using SqlKata.Tests.Infrastructure; -using System; -using System.Linq; -using Xunit; - -namespace SqlKata.Tests -{ - public class GeneralTests : TestSupport - { - [Fact] - public void ColumnsEscaping() - { - var q = new Query().From("users") - .Select("mycol[isthis]"); - - var c = Compile(q); - - Assert.Equal("SELECT [mycol[isthis]]] FROM [users]", c[EngineCodes.SqlServer]); - } - - - [Fact] - public void InnerScopeEngineWithinCTE() - { - var series = new Query("table") - .ForPostgreSql(q => q.WhereRaw("postgres = true")) - .ForSqlServer(q => q.WhereRaw("sqlsrv = 1")) - .ForFirebird(q => q.WhereRaw("firebird = 1")); - var query = new Query("series").With("series", series); - - var c = Compile(query); - - Assert.Equal("WITH [series] AS (SELECT * FROM [table] WHERE sqlsrv = 1)\nSELECT * FROM [series]", c[EngineCodes.SqlServer]); - - Assert.Equal("WITH \"series\" AS (SELECT * FROM \"table\" WHERE postgres = true)\nSELECT * FROM \"series\"", - c[EngineCodes.PostgreSql]); - Assert.Equal("WITH \"SERIES\" AS (SELECT * FROM \"TABLE\" WHERE firebird = 1)\nSELECT * FROM \"SERIES\"", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InnerScopeEngineWithinSubQuery() - { - var series = new Query("table") - .ForPostgreSql(q => q.WhereRaw("postgres = true")) - .ForSqlServer(q => q.WhereRaw("sqlsrv = 1")) - .ForFirebird(q => q.WhereRaw("firebird = 1")); - var query = new Query("series").From(series.As("series")); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM (SELECT * FROM [table] WHERE sqlsrv = 1) AS [series]", c[EngineCodes.SqlServer]); - - Assert.Equal("SELECT * FROM (SELECT * FROM \"table\" WHERE postgres = true) AS \"series\"", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT * FROM (SELECT * FROM \"TABLE\" WHERE firebird = 1) AS \"SERIES\"", c[EngineCodes.Firebird]); - } - - [Fact] - public void ItShouldCacheMethodInfoByType() - { - var compiler = new TestSqlServerCompiler(); - - var call1 = compiler.Call_FindCompilerMethodInfo( - typeof(BasicCondition), "CompileBasicCondition" - ); - - var call2 = compiler.Call_FindCompilerMethodInfo( - typeof(BasicCondition), "CompileBasicCondition" - ); - - Assert.Same(call1, call2); - } - - [Fact] - public void Return_Different_MethodInfo_WhenSame_Method_With_Different_GenericTypes() - { - var compiler = new TestSqlServerCompiler(); - - var call1 = compiler.Call_FindCompilerMethodInfo( - typeof(NestedCondition), "CompileNestedCondition" - ); - - var call2 = compiler.Call_FindCompilerMethodInfo( - typeof(NestedCondition), "CompileNestedCondition" - ); - - Assert.NotSame(call1, call2); - } - - [Fact] - public void Custom_compiler_with_empty_identifier_overrides_should_remove_identifiers() - { - var compiler = new TestEmptyIdentifiersCompiler(); - - var wrappedValue = compiler.WrapValue("Table"); - - Assert.Equal("Table", wrappedValue); - } - - [Fact] - public void Should_Equal_AfterMultipleCompile() - { - var query = new Query() - .Select("Id", "Name") - .From("Table") - .OrderBy("Name") - .Limit(20) - .Offset(1); - - var first = Compile(query); - Assert.Equal( - "SELECT * FROM (SELECT [Id], [Name], ROW_NUMBER() OVER (ORDER BY [Name]) AS [row_num] FROM [Table]) AS [results_wrapper] WHERE [row_num] BETWEEN 2 AND 21", - first[EngineCodes.SqlServer]); - Assert.Equal("SELECT `Id`, `Name` FROM `Table` ORDER BY `Name` LIMIT 20 OFFSET 1", first[EngineCodes.MySql]); - Assert.Equal("SELECT \"Id\", \"Name\" FROM \"Table\" ORDER BY \"Name\" LIMIT 20 OFFSET 1", first[EngineCodes.PostgreSql]); - Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"TABLE\" ORDER BY \"NAME\" ROWS 2 TO 21", first[EngineCodes.Firebird]); - - var second = Compile(query); - - Assert.Equal(first[EngineCodes.SqlServer], second[EngineCodes.SqlServer]); - Assert.Equal(first[EngineCodes.MySql], second[EngineCodes.MySql]); - Assert.Equal(first[EngineCodes.PostgreSql], second[EngineCodes.PostgreSql]); - Assert.Equal(first[EngineCodes.Firebird], second[EngineCodes.Firebird]); - } - - [Fact] - public void Raw_WrapIdentifiers() - { - var query = new Query("Users").SelectRaw("[Id], [Name], {Age}"); - - var c = Compile(query); - - Assert.Equal("SELECT [Id], [Name], [Age] FROM [Users]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT `Id`, `Name`, `Age` FROM `Users`", c[EngineCodes.MySql]); - Assert.Equal("SELECT \"Id\", \"Name\", \"Age\" FROM \"Users\"", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT \"Id\", \"Name\", \"Age\" FROM \"USERS\"", c[EngineCodes.Firebird]); - } - - [Fact] - public void Raw_WrapIdentifiers_Escaped() - { - var query = new Query("Users").SelectRaw("'\\{1,2,3\\}'::int\\[\\]"); - - var c = Compile(query); - - Assert.Equal("SELECT '{1,2,3}'::int[] FROM \"Users\"", c[EngineCodes.PostgreSql]); - } - - [Fact] - public void WrapWithSpace() - { - var compiler = new SqlServerCompiler(); - - - Assert.Equal("[My Table] AS [Table]", compiler.Wrap("My Table as Table")); - } - - [Fact] - public void WrapWithDotes() - { - var compiler = new SqlServerCompiler(); - - - Assert.Equal("[My Schema].[My Table] AS [Table]", compiler.Wrap("My Schema.My Table as Table")); - } - - [Fact] - public void WrapWithMultipleSpaces() - { - var compiler = new SqlServerCompiler(); - - - Assert.Equal("[My Table One] AS [Table One]", compiler.Wrap("My Table One as Table One")); - } - - [Fact] - public void CompilerSpecificFrom() - { - var query = new Query() - .ForSqlServer(q => q.From("mssql")) - .ForPostgreSql(q => q.From("pgsql")) - .ForMySql(q => q.From("mysql")); - var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); - Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); - Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); - } - - [Fact] - public void CompilerSpecificFromRaw() - { - var query = new Query() - .ForSqlServer(q => q.FromRaw("[mssql]")) - .ForPostgreSql(q => q.FromRaw("[pgsql]")) - .ForMySql(q => q.FromRaw("[mysql]")); - var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); - Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); - Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); - } - - [Fact] - public void CompilerSpecificFromMixed() - { - var query = new Query() - .ForSqlServer(q => q.From("mssql")) - .ForPostgreSql(q => q.FromRaw("[pgsql]")) - .ForMySql(q => q.From("mysql")); - var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); - Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); - Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); - } - - [Fact] - public void OneFromPerEngine() - { - var query = new Query("generic") - .ForSqlServer(q => q.From("dnu")) - .ForSqlServer(q => q.From("mssql")); - var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal(2, query.Clauses.OfType().Count()); - Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); - Assert.Equal("SELECT * FROM \"generic\"", c[EngineCodes.PostgreSql].RawSql); - Assert.Equal("SELECT * FROM `generic`", c[EngineCodes.MySql].RawSql); - } - - [Theory] - [InlineData(null, null)] - [InlineData(null, "mssql")] - [InlineData("original", null)] - [InlineData("original", "mssql")] - public void AddOrReplace_Works(string table, string engine) - { - var query = new Query(); - if (table != null) - query.From(table); - query.AddOrReplaceComponent("from", new FromClause() { Table = "updated", Engine = engine }); - var froms = query.Clauses.OfType(); - - Assert.Single(froms); - Assert.Equal("updated", froms.Single().Table); - } - - [Theory] - [InlineData(null, "generic")] - [InlineData(EngineCodes.SqlServer, "mssql")] - [InlineData(EngineCodes.MySql, "generic")] - public void GetOneComponent_Prefers_Engine(string engine, string column) - { - var query = new Query() - .Where("generic", "foo") - .ForSqlServer(q => q.Where("mssql", "foo")); - - var where = query.GetOneComponent("where", engine) as BasicCondition; - - Assert.NotNull(where); - Assert.Equal(column, where.Column); - } - - [Fact] - public void AddOrReplace_Throws_MoreThanOne() - { - var query = new Query() - .Where("a", "b") - .Where("c", "d"); - - Action act = () => query.AddOrReplaceComponent("where", new BasicCondition()); - Assert.Throws(act); - } - - [Fact] - public void OneLimitPerEngine() - { - var query = new Query("mytable") - .ForSqlServer(q => q.Limit(5)) - .ForSqlServer(q => q.Limit(10)); - - var limits = query.GetComponents("limit", EngineCodes.SqlServer); - Assert.Single(limits); - Assert.Equal(10, limits.Single().Limit); - } - - [Fact] - public void CompilerSpecificLimit() - { - var query = new Query("mytable") - .ForSqlServer(q => q.Limit(5)) - .ForPostgreSql(q => q.Limit(10)); - - var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal(2, query.GetComponents("limit").Count); - Assert.Equal("SELECT TOP (5) * FROM [mytable]", c[EngineCodes.SqlServer].ToString()); - Assert.Equal("SELECT * FROM \"mytable\" LIMIT 10", c[EngineCodes.PostgreSql].ToString()); - Assert.Equal("SELECT * FROM `mytable`", c[EngineCodes.MySql].ToString()); - } - - [Fact] - public void OneOffsetPerEngine() - { - var query = new Query("mytable") - .ForSqlServer(q => q.Offset(5)) - .ForSqlServer(q => q.Offset(10)); - - var limits = query.GetComponents("offset", EngineCodes.SqlServer); - Assert.Single(limits); - Assert.Equal(10, limits.Single().Offset); - } - - [Fact] - public void CompilerSpecificOffset() - { - var query = new Query("mytable") - .ForMySql(q => q.Offset(5)) - .ForPostgreSql(q => q.Offset(10)); - - var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal(2, query.GetComponents("offset").Count); - Assert.Equal("SELECT * FROM `mytable` LIMIT 18446744073709551615 OFFSET 5", c[EngineCodes.MySql].ToString()); - Assert.Equal("SELECT * FROM \"mytable\" OFFSET 10", c[EngineCodes.PostgreSql].ToString()); - Assert.Equal("SELECT * FROM [mytable]", c[EngineCodes.SqlServer].ToString()); - } - - [Fact] - public void Limit_Takes_Generic_If_Needed() - { - var query = new Query("mytable") - .Limit(5) - .Offset(10) - .ForPostgreSql(q => q.Offset(20)); - - var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 10", c[EngineCodes.MySql].ToString()); - Assert.Equal("SELECT * FROM \"mytable\" LIMIT 5 OFFSET 20", c[EngineCodes.PostgreSql].ToString()); - } - - [Fact] - public void Offset_Takes_Generic_If_Needed() - { - var query = new Query("mytable") - .Limit(5) - .Offset(10) - .ForPostgreSql(q => q.Limit(20)); - - var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 10", c[EngineCodes.MySql].ToString()); - Assert.Equal("SELECT * FROM \"mytable\" LIMIT 20 OFFSET 10", c[EngineCodes.PostgreSql].ToString()); - } - - [Fact] - public void Can_Change_Generic_Limit_After_SpecificOffset() - { - var query = new Query("mytable") - .Limit(5) - .Offset(10) - .ForPostgreSql(q => q.Offset(20)) - .Limit(7); - - var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM `mytable` LIMIT 7 OFFSET 10", c[EngineCodes.MySql].ToString()); - Assert.Equal("SELECT * FROM \"mytable\" LIMIT 7 OFFSET 20", c[EngineCodes.PostgreSql].ToString()); - } - - [Fact] - public void Can_Change_Generic_Offset_After_SpecificLimit() - { - var query = new Query("mytable") - .Limit(5) - .Offset(10) - .ForPostgreSql(q => q.Limit(20)) - .Offset(7); - - var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 7", c[EngineCodes.MySql].ToString()); - Assert.Equal("SELECT * FROM \"mytable\" LIMIT 20 OFFSET 7", c[EngineCodes.PostgreSql].ToString()); - } - - [Fact] - public void Where_Nested() - { - var query = new Query("table") - .Where(q => q.Where("a", 1).OrWhere("a", 2)); - - var engines = new[] { - EngineCodes.SqlServer, - }; - - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM [table] WHERE ([a] = 1 OR [a] = 2)", c[EngineCodes.SqlServer].ToString()); - } - - [Fact] - public void AdHoc_Throws_WhenNoColumnsProvided() => - Assert.Throws(() => - new Query("rows").With("rows", - new string[0], - new object[][] { - new object[] {}, - new object[] {}, - })); - - [Fact] - public void AdHoc_Throws_WhenNoValueRowsProvided() => - Assert.Throws(() => - new Query("rows").With("rows", - new[] { "a", "b", "c" }, - new object[][] { - })); - - [Fact] - public void AdHoc_Throws_WhenColumnsOutnumberFieldValues() => - Assert.Throws(() => - new Query("rows").With("rows", - new[] { "a", "b", "c", "d" }, - new object[][] { - new object[] { 1, 2, 3 }, - new object[] { 4, 5, 6 }, - })); - - [Fact] - public void AdHoc_Throws_WhenFieldValuesOutNumberColumns() => - Assert.Throws(() => - new Query("rows").With("rows", - new[] { "a", "b" }, - new object[][] { - new object[] { 1, 2, 3 }, - new object[] { 4, 5, 6 }, - })); - - [Fact] - public void AdHoc_SingletonRow() - { - var query = new Query("rows").With("rows", - new[] { "a" }, - new object[][] { - new object[] { 1 }, - }); - - var c = Compilers.Compile(query); - - Assert.Equal("WITH [rows] AS (SELECT [a] FROM (VALUES (1)) AS tbl ([a]))\nSELECT * FROM [rows]", c[EngineCodes.SqlServer].ToString()); - Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\")\nSELECT * FROM \"rows\"", c[EngineCodes.PostgreSql].ToString()); - Assert.Equal("WITH `rows` AS (SELECT 1 AS `a`)\nSELECT * FROM `rows`", c[EngineCodes.MySql].ToString()); - Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\")\nSELECT * FROM \"rows\"", c[EngineCodes.Sqlite].ToString()); - Assert.Equal("WITH \"ROWS\" AS (SELECT 1 AS \"A\" FROM RDB$DATABASE)\nSELECT * FROM \"ROWS\"", c[EngineCodes.Firebird].ToString()); - Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\" FROM DUAL)\nSELECT * FROM \"rows\"", c[EngineCodes.Oracle].ToString()); - } - - [Fact] - public void AdHoc_TwoRows() - { - var query = new Query("rows").With("rows", - new[] { "a", "b", "c" }, - new object[][] { - new object[] { 1, 2, 3 }, - new object[] { 4, 5, 6 }, - }); - - var c = Compilers.Compile(query); - - Assert.Equal("WITH [rows] AS (SELECT [a], [b], [c] FROM (VALUES (1, 2, 3), (4, 5, 6)) AS tbl ([a], [b], [c]))\nSELECT * FROM [rows]", c[EngineCodes.SqlServer].ToString()); - Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\", 2 AS \"b\", 3 AS \"c\" UNION ALL SELECT 4 AS \"a\", 5 AS \"b\", 6 AS \"c\")\nSELECT * FROM \"rows\"", c[EngineCodes.PostgreSql].ToString()); - Assert.Equal("WITH `rows` AS (SELECT 1 AS `a`, 2 AS `b`, 3 AS `c` UNION ALL SELECT 4 AS `a`, 5 AS `b`, 6 AS `c`)\nSELECT * FROM `rows`", c[EngineCodes.MySql].ToString()); - Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\", 2 AS \"b\", 3 AS \"c\" UNION ALL SELECT 4 AS \"a\", 5 AS \"b\", 6 AS \"c\")\nSELECT * FROM \"rows\"", c[EngineCodes.Sqlite].ToString()); - Assert.Equal("WITH \"ROWS\" AS (SELECT 1 AS \"A\", 2 AS \"B\", 3 AS \"C\" FROM RDB$DATABASE UNION ALL SELECT 4 AS \"A\", 5 AS \"B\", 6 AS \"C\" FROM RDB$DATABASE)\nSELECT * FROM \"ROWS\"", c[EngineCodes.Firebird].ToString()); - Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\", 2 AS \"b\", 3 AS \"c\" FROM DUAL UNION ALL SELECT 4 AS \"a\", 5 AS \"b\", 6 AS \"c\" FROM DUAL)\nSELECT * FROM \"rows\"", c[EngineCodes.Oracle].ToString()); - } - - [Fact] - public void AdHoc_ProperBindingsPlacement() - { - var query = new Query("rows") - .With("othercte", q => q.From("othertable").Where("othertable.status", "A")) - .Where("rows.foo", "bar") - .With("rows", - new[] { "a", "b", "c" }, - new object[][] { - new object[] { 1, 2, 3 }, - new object[] { 4, 5, 6 }, - }) - .Where("rows.baz", "buzz"); - - var c = Compilers.Compile(query); - - Assert.Equal(string.Join("\n", new[] { - "WITH [othercte] AS (SELECT * FROM [othertable] WHERE [othertable].[status] = 'A'),", - "[rows] AS (SELECT [a], [b], [c] FROM (VALUES (1, 2, 3), (4, 5, 6)) AS tbl ([a], [b], [c]))", - "SELECT * FROM [rows] WHERE [rows].[foo] = 'bar' AND [rows].[baz] = 'buzz'", - }), c[EngineCodes.SqlServer].ToString()); - } - - [Fact] - public void UnsafeLiteral_Insert() - { - var query = new Query("Table").AsInsert(new - { - Count = new UnsafeLiteral("Count + 1") - }); - - var engines = new[] { - EngineCodes.SqlServer, - }; - - var c = Compilers.Compile(engines, query); - - Assert.Equal("INSERT INTO [Table] ([Count]) VALUES (Count + 1)", c[EngineCodes.SqlServer].ToString()); - } - - [Fact] - public void UnsafeLiteral_Update() - { - var query = new Query("Table").AsUpdate(new - { - Count = new UnsafeLiteral("Count + 1") - }); - - var engines = new[] { - EngineCodes.SqlServer, - }; - - var c = Compilers.Compile(engines, query); - - Assert.Equal("UPDATE [Table] SET [Count] = Count + 1", c[EngineCodes.SqlServer].ToString()); - } - - [Fact] - public void Passing_Boolean_To_Where_Should_Call_WhereTrue_Or_WhereFalse() - { - var query = new Query("Table").Where("Col", true); - - var engines = new[] { - EngineCodes.SqlServer, - }; - - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM [Table] WHERE [Col] = cast(1 as bit)", c[EngineCodes.SqlServer].ToString()); - } - - [Fact] - public void Passing_Boolean_False_To_Where_Should_Call_WhereTrue_Or_WhereFalse() - { - var query = new Query("Table").Where("Col", false); - - var engines = new[] { - EngineCodes.SqlServer, - }; - - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM [Table] WHERE [Col] = cast(0 as bit)", c[EngineCodes.SqlServer].ToString()); - } - - [Fact] - public void Passing_Negative_Boolean_To_Where_Should_Call_WhereTrue_Or_WhereFalse() - { - var query = new Query("Table").Where("Col", "!=", true); - - var engines = new[] { - EngineCodes.SqlServer, - }; - - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM [Table] WHERE [Col] != cast(1 as bit)", c[EngineCodes.SqlServer].ToString()); - } - - [Fact] - public void Passing_Negative_Boolean_False_To_Where_Should_Call_WhereTrue_Or_WhereFalse() - { - var query = new Query("Table").Where("Col", "!=", false); - - var engines = new[] { - EngineCodes.SqlServer, - }; - - var c = Compilers.Compile(engines, query); - - Assert.Equal("SELECT * FROM [Table] WHERE [Col] != cast(0 as bit)", c[EngineCodes.SqlServer].ToString()); - } - } -} +namespace SqlKata.Tests; + +using SqlKata.Compilers; +using SqlKata.Extensions; +using SqlKata.Tests.Infrastructure; +using System; +using System.Linq; +using Xunit; + +public class GeneralTests : TestSupport { + [Fact] + public void ColumnsEscaping() { + var q = new Query().From("users") + .Select("mycol[isthis]"); + + var c = Compile(q); + + Assert.Equal("SELECT [mycol[isthis]]] FROM [users]", c[EngineCodes.SqlServer]); + } + + + [Fact] + public void InnerScopeEngineWithinCTE() { + var series = new Query("table") + .ForPostgreSql(q => q.WhereRaw("postgres = true")) + .ForSqlServer(q => q.WhereRaw("sqlsrv = 1")) + .ForFirebird(q => q.WhereRaw("firebird = 1")); + var query = new Query("series").With("series", series); + + var c = Compile(query); + + Assert.Equal("WITH [series] AS (SELECT * FROM [table] WHERE sqlsrv = 1)\nSELECT * FROM [series]", c[EngineCodes.SqlServer]); + + Assert.Equal("WITH \"series\" AS (SELECT * FROM \"table\" WHERE postgres = true)\nSELECT * FROM \"series\"", + c[EngineCodes.PostgreSql]); + Assert.Equal("WITH \"SERIES\" AS (SELECT * FROM \"TABLE\" WHERE firebird = 1)\nSELECT * FROM \"SERIES\"", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InnerScopeEngineWithinSubQuery() { + var series = new Query("table") + .ForPostgreSql(q => q.WhereRaw("postgres = true")) + .ForSqlServer(q => q.WhereRaw("sqlsrv = 1")) + .ForFirebird(q => q.WhereRaw("firebird = 1")); + var query = new Query("series").From(series.As("series")); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM (SELECT * FROM [table] WHERE sqlsrv = 1) AS [series]", c[EngineCodes.SqlServer]); + + Assert.Equal("SELECT * FROM (SELECT * FROM \"table\" WHERE postgres = true) AS \"series\"", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT * FROM (SELECT * FROM \"TABLE\" WHERE firebird = 1) AS \"SERIES\"", c[EngineCodes.Firebird]); + } + + [Fact] + public void ItShouldCacheMethodInfoByType() { + var compiler = new TestSqlServerCompiler(); + + var call1 = compiler.Call_FindCompilerMethodInfo( + typeof(BasicCondition), "CompileBasicCondition" + ); + + var call2 = compiler.Call_FindCompilerMethodInfo( + typeof(BasicCondition), "CompileBasicCondition" + ); + + Assert.Same(call1, call2); + } + + [Fact] + public void Return_Different_MethodInfo_WhenSame_Method_With_Different_GenericTypes() { + var compiler = new TestSqlServerCompiler(); + + var call1 = compiler.Call_FindCompilerMethodInfo( + typeof(NestedCondition), "CompileNestedCondition" + ); + + var call2 = compiler.Call_FindCompilerMethodInfo( + typeof(NestedCondition), "CompileNestedCondition" + ); + + Assert.NotSame(call1, call2); + } + + [Fact] + public void Custom_compiler_with_empty_identifier_overrides_should_remove_identifiers() { + var compiler = new TestEmptyIdentifiersCompiler(); + + var wrappedValue = compiler.WrapValue("Table"); + + Assert.Equal("Table", wrappedValue); + } + + [Fact] + public void Should_Equal_AfterMultipleCompile() { + var query = new Query() + .Select("Id", "Name") + .From("Table") + .OrderBy("Name") + .Limit(20) + .Offset(1); + + var first = Compile(query); + Assert.Equal( + "SELECT * FROM (SELECT [Id], [Name], ROW_NUMBER() OVER (ORDER BY [Name]) AS [row_num] FROM [Table]) AS [results_wrapper] WHERE [row_num] BETWEEN 2 AND 21", + first[EngineCodes.SqlServer]); + Assert.Equal("SELECT `Id`, `Name` FROM `Table` ORDER BY `Name` LIMIT 20 OFFSET 1", first[EngineCodes.MySql]); + Assert.Equal("SELECT \"Id\", \"Name\" FROM \"Table\" ORDER BY \"Name\" LIMIT 20 OFFSET 1", first[EngineCodes.PostgreSql]); + Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"TABLE\" ORDER BY \"NAME\" ROWS 2 TO 21", first[EngineCodes.Firebird]); + + var second = Compile(query); + + Assert.Equal(first[EngineCodes.SqlServer], second[EngineCodes.SqlServer]); + Assert.Equal(first[EngineCodes.MySql], second[EngineCodes.MySql]); + Assert.Equal(first[EngineCodes.PostgreSql], second[EngineCodes.PostgreSql]); + Assert.Equal(first[EngineCodes.Firebird], second[EngineCodes.Firebird]); + } + + [Fact] + public void Raw_WrapIdentifiers() { + var query = new Query("Users").SelectRaw("[Id], [Name], {Age}"); + + var c = Compile(query); + + Assert.Equal("SELECT [Id], [Name], [Age] FROM [Users]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT `Id`, `Name`, `Age` FROM `Users`", c[EngineCodes.MySql]); + Assert.Equal("SELECT \"Id\", \"Name\", \"Age\" FROM \"Users\"", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT \"Id\", \"Name\", \"Age\" FROM \"USERS\"", c[EngineCodes.Firebird]); + } + + [Fact] + public void Raw_WrapIdentifiers_Escaped() { + var query = new Query("Users").SelectRaw("'\\{1,2,3\\}'::int\\[\\]"); + + var c = Compile(query); + + Assert.Equal("SELECT '{1,2,3}'::int[] FROM \"Users\"", c[EngineCodes.PostgreSql]); + } + + [Fact] + public void WrapWithSpace() { + var compiler = new SqlServerCompiler(); + + + Assert.Equal("[My Table] AS [Table]", compiler.Wrap("My Table as Table")); + } + + [Fact] + public void WrapWithDotes() { + var compiler = new SqlServerCompiler(); + + + Assert.Equal("[My Schema].[My Table] AS [Table]", compiler.Wrap("My Schema.My Table as Table")); + } + + [Fact] + public void WrapWithMultipleSpaces() { + var compiler = new SqlServerCompiler(); + + + Assert.Equal("[My Table One] AS [Table One]", compiler.Wrap("My Table One as Table One")); + } + + [Fact] + public void CompilerSpecificFrom() { + var query = new Query() + .ForSqlServer(q => q.From("mssql")) + .ForPostgreSql(q => q.From("pgsql")) + .ForMySql(q => q.From("mysql")); + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); + Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); + Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); + } + + [Fact] + public void CompilerSpecificFromRaw() { + var query = new Query() + .ForSqlServer(q => q.FromRaw("[mssql]")) + .ForPostgreSql(q => q.FromRaw("[pgsql]")) + .ForMySql(q => q.FromRaw("[mysql]")); + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); + Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); + Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); + } + + [Fact] + public void CompilerSpecificFromMixed() { + var query = new Query() + .ForSqlServer(q => q.From("mssql")) + .ForPostgreSql(q => q.FromRaw("[pgsql]")) + .ForMySql(q => q.From("mysql")); + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); + Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); + Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); + } + + [Fact] + public void OneFromPerEngine() { + var query = new Query("generic") + .ForSqlServer(q => q.From("dnu")) + .ForSqlServer(q => q.From("mssql")); + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal(2, query.Clauses.OfType().Count()); + Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); + Assert.Equal("SELECT * FROM \"generic\"", c[EngineCodes.PostgreSql].RawSql); + Assert.Equal("SELECT * FROM `generic`", c[EngineCodes.MySql].RawSql); + } + + [Theory] + [InlineData(null, null)] + [InlineData(null, "mssql")] + [InlineData("original", null)] + [InlineData("original", "mssql")] + public void AddOrReplace_Works(string table, string engine) { + var query = new Query(); + if (table != null) + query.From(table); + query.AddOrReplaceComponent("from", new FromClause() { Table = "updated", Engine = engine }); + var froms = query.Clauses.OfType(); + + Assert.Single(froms); + Assert.Equal("updated", froms.Single().Table); + } + + [Theory] + [InlineData(null, "generic")] + [InlineData(EngineCodes.SqlServer, "mssql")] + [InlineData(EngineCodes.MySql, "generic")] + public void GetOneComponent_Prefers_Engine(string engine, string column) { + var query = new Query() + .Where("generic", "foo") + .ForSqlServer(q => q.Where("mssql", "foo")); + + var where = query.GetOneComponent("where", engine) as BasicCondition; + + Assert.NotNull(where); + Assert.Equal(column, where.Column); + } + + [Fact] + public void AddOrReplace_Throws_MoreThanOne() { + var query = new Query() + .Where("a", "b") + .Where("c", "d"); + + Action act = () => query.AddOrReplaceComponent("where", new BasicCondition()); + Assert.Throws(act); + } + + [Fact] + public void OneLimitPerEngine() { + var query = new Query("mytable") + .ForSqlServer(q => q.Limit(5)) + .ForSqlServer(q => q.Limit(10)); + + var limits = query.GetComponents("limit", EngineCodes.SqlServer); + Assert.Single(limits); + Assert.Equal(10, limits.Single().Limit); + } + + [Fact] + public void CompilerSpecificLimit() { + var query = new Query("mytable") + .ForSqlServer(q => q.Limit(5)) + .ForPostgreSql(q => q.Limit(10)); + + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal(2, query.GetComponents("limit").Count); + Assert.Equal("SELECT TOP (5) * FROM [mytable]", c[EngineCodes.SqlServer].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 10", c[EngineCodes.PostgreSql].ToString()); + Assert.Equal("SELECT * FROM `mytable`", c[EngineCodes.MySql].ToString()); + } + + [Fact] + public void OneOffsetPerEngine() { + var query = new Query("mytable") + .ForSqlServer(q => q.Offset(5)) + .ForSqlServer(q => q.Offset(10)); + + var limits = query.GetComponents("offset", EngineCodes.SqlServer); + Assert.Single(limits); + Assert.Equal(10, limits.Single().Offset); + } + + [Fact] + public void CompilerSpecificOffset() { + var query = new Query("mytable") + .ForMySql(q => q.Offset(5)) + .ForPostgreSql(q => q.Offset(10)); + + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal(2, query.GetComponents("offset").Count); + Assert.Equal("SELECT * FROM `mytable` LIMIT 18446744073709551615 OFFSET 5", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" OFFSET 10", c[EngineCodes.PostgreSql].ToString()); + Assert.Equal("SELECT * FROM [mytable]", c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void Limit_Takes_Generic_If_Needed() { + var query = new Query("mytable") + .Limit(5) + .Offset(10) + .ForPostgreSql(q => q.Offset(20)); + + var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 10", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 5 OFFSET 20", c[EngineCodes.PostgreSql].ToString()); + } + + [Fact] + public void Offset_Takes_Generic_If_Needed() { + var query = new Query("mytable") + .Limit(5) + .Offset(10) + .ForPostgreSql(q => q.Limit(20)); + + var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 10", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 20 OFFSET 10", c[EngineCodes.PostgreSql].ToString()); + } + + [Fact] + public void Can_Change_Generic_Limit_After_SpecificOffset() { + var query = new Query("mytable") + .Limit(5) + .Offset(10) + .ForPostgreSql(q => q.Offset(20)) + .Limit(7); + + var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM `mytable` LIMIT 7 OFFSET 10", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 7 OFFSET 20", c[EngineCodes.PostgreSql].ToString()); + } + + [Fact] + public void Can_Change_Generic_Offset_After_SpecificLimit() { + var query = new Query("mytable") + .Limit(5) + .Offset(10) + .ForPostgreSql(q => q.Limit(20)) + .Offset(7); + + var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 7", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 20 OFFSET 7", c[EngineCodes.PostgreSql].ToString()); + } + + [Fact] + public void Where_Nested() { + var query = new Query("table") + .Where(q => q.Where("a", 1).OrWhere("a", 2)); + + var engines = new[] { + EngineCodes.SqlServer, + }; + + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [table] WHERE ([a] = 1 OR [a] = 2)", c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void AdHoc_Throws_WhenNoColumnsProvided() => + Assert.Throws(() => + new Query("rows").With("rows", + new string[0], + new object[][] { + new object[] {}, + new object[] {}, + })); + + [Fact] + public void AdHoc_Throws_WhenNoValueRowsProvided() => + Assert.Throws(() => + new Query("rows").With("rows", + new[] { "a", "b", "c" }, + new object[][] { + })); + + [Fact] + public void AdHoc_Throws_WhenColumnsOutnumberFieldValues() => + Assert.Throws(() => + new Query("rows").With("rows", + new[] { "a", "b", "c", "d" }, + new object[][] { + new object[] { 1, 2, 3 }, + new object[] { 4, 5, 6 }, + })); + + [Fact] + public void AdHoc_Throws_WhenFieldValuesOutNumberColumns() => + Assert.Throws(() => + new Query("rows").With("rows", + new[] { "a", "b" }, + new object[][] { + new object[] { 1, 2, 3 }, + new object[] { 4, 5, 6 }, + })); + + [Fact] + public void AdHoc_SingletonRow() { + var query = new Query("rows").With("rows", + new[] { "a" }, + new object[][] { + new object[] { 1 }, + }); + + var c = Compilers.Compile(query); + + Assert.Equal("WITH [rows] AS (SELECT [a] FROM (VALUES (1)) AS tbl ([a]))\nSELECT * FROM [rows]", c[EngineCodes.SqlServer].ToString()); + Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\")\nSELECT * FROM \"rows\"", c[EngineCodes.PostgreSql].ToString()); + Assert.Equal("WITH `rows` AS (SELECT 1 AS `a`)\nSELECT * FROM `rows`", c[EngineCodes.MySql].ToString()); + Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\")\nSELECT * FROM \"rows\"", c[EngineCodes.Sqlite].ToString()); + Assert.Equal("WITH \"ROWS\" AS (SELECT 1 AS \"A\" FROM RDB$DATABASE)\nSELECT * FROM \"ROWS\"", c[EngineCodes.Firebird].ToString()); + Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\" FROM DUAL)\nSELECT * FROM \"rows\"", c[EngineCodes.Oracle].ToString()); + } + + [Fact] + public void AdHoc_TwoRows() { + var query = new Query("rows").With("rows", + new[] { "a", "b", "c" }, + new object[][] { + new object[] { 1, 2, 3 }, + new object[] { 4, 5, 6 }, + }); + + var c = Compilers.Compile(query); + + Assert.Equal("WITH [rows] AS (SELECT [a], [b], [c] FROM (VALUES (1, 2, 3), (4, 5, 6)) AS tbl ([a], [b], [c]))\nSELECT * FROM [rows]", c[EngineCodes.SqlServer].ToString()); + Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\", 2 AS \"b\", 3 AS \"c\" UNION ALL SELECT 4 AS \"a\", 5 AS \"b\", 6 AS \"c\")\nSELECT * FROM \"rows\"", c[EngineCodes.PostgreSql].ToString()); + Assert.Equal("WITH `rows` AS (SELECT 1 AS `a`, 2 AS `b`, 3 AS `c` UNION ALL SELECT 4 AS `a`, 5 AS `b`, 6 AS `c`)\nSELECT * FROM `rows`", c[EngineCodes.MySql].ToString()); + Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\", 2 AS \"b\", 3 AS \"c\" UNION ALL SELECT 4 AS \"a\", 5 AS \"b\", 6 AS \"c\")\nSELECT * FROM \"rows\"", c[EngineCodes.Sqlite].ToString()); + Assert.Equal("WITH \"ROWS\" AS (SELECT 1 AS \"A\", 2 AS \"B\", 3 AS \"C\" FROM RDB$DATABASE UNION ALL SELECT 4 AS \"A\", 5 AS \"B\", 6 AS \"C\" FROM RDB$DATABASE)\nSELECT * FROM \"ROWS\"", c[EngineCodes.Firebird].ToString()); + Assert.Equal("WITH \"rows\" AS (SELECT 1 AS \"a\", 2 AS \"b\", 3 AS \"c\" FROM DUAL UNION ALL SELECT 4 AS \"a\", 5 AS \"b\", 6 AS \"c\" FROM DUAL)\nSELECT * FROM \"rows\"", c[EngineCodes.Oracle].ToString()); + } + + [Fact] + public void AdHoc_ProperBindingsPlacement() { + var query = new Query("rows") + .With("othercte", q => q.From("othertable").Where("othertable.status", "A")) + .Where("rows.foo", "bar") + .With("rows", + new[] { "a", "b", "c" }, + new object[][] { + new object[] { 1, 2, 3 }, + new object[] { 4, 5, 6 }, + }) + .Where("rows.baz", "buzz"); + + var c = Compilers.Compile(query); + + Assert.Equal(string.Join("\n", new[] { + "WITH [othercte] AS (SELECT * FROM [othertable] WHERE [othertable].[status] = 'A'),", + "[rows] AS (SELECT [a], [b], [c] FROM (VALUES (1, 2, 3), (4, 5, 6)) AS tbl ([a], [b], [c]))", + "SELECT * FROM [rows] WHERE [rows].[foo] = 'bar' AND [rows].[baz] = 'buzz'", + }), c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void UnsafeLiteral_Insert() { + var query = new Query("Table").AsInsert(new { + Count = new UnsafeLiteral("Count + 1") + }); + + var engines = new[] { + EngineCodes.SqlServer, + }; + + var c = Compilers.Compile(engines, query); + + Assert.Equal("INSERT INTO [Table] ([Count]) VALUES (Count + 1)", c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void UnsafeLiteral_Update() { + var query = new Query("Table").AsUpdate(new { + Count = new UnsafeLiteral("Count + 1") + }); + + var engines = new[] { + EngineCodes.SqlServer, + }; + + var c = Compilers.Compile(engines, query); + + Assert.Equal("UPDATE [Table] SET [Count] = Count + 1", c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void Passing_Boolean_To_Where_Should_Call_WhereTrue_Or_WhereFalse() { + var query = new Query("Table").Where("Col", true); + + var engines = new[] { + EngineCodes.SqlServer, + }; + + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [Table] WHERE [Col] = cast(1 as bit)", c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void Passing_Boolean_False_To_Where_Should_Call_WhereTrue_Or_WhereFalse() { + var query = new Query("Table").Where("Col", false); + + var engines = new[] { + EngineCodes.SqlServer, + }; + + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [Table] WHERE [Col] = cast(0 as bit)", c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void Passing_Negative_Boolean_To_Where_Should_Call_WhereTrue_Or_WhereFalse() { + var query = new Query("Table").Where("Col", "!=", true); + + var engines = new[] { + EngineCodes.SqlServer, + }; + + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [Table] WHERE [Col] != cast(1 as bit)", c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void Passing_Negative_Boolean_False_To_Where_Should_Call_WhereTrue_Or_WhereFalse() { + var query = new Query("Table").Where("Col", "!=", false); + + var engines = new[] { + EngineCodes.SqlServer, + }; + + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [Table] WHERE [Col] != cast(0 as bit)", c[EngineCodes.SqlServer].ToString()); + } +} diff --git a/QueryBuilder.Tests/HelperTests.cs b/QueryBuilder.Tests/HelperTests.cs index ab80e2c5..d2d7acde 100644 --- a/QueryBuilder.Tests/HelperTests.cs +++ b/QueryBuilder.Tests/HelperTests.cs @@ -1,239 +1,221 @@ -using System.Collections; -using System.Linq; -using Xunit; - -namespace SqlKata.Tests -{ - public class HelperTests - { - [Theory] - [InlineData("")] - [InlineData(null)] - [InlineData(" ")] - [InlineData(" ")] - [InlineData(" ")] - public void ItShouldKeepItAsIs(string input) - { - var output = Helper.ReplaceAll(input, "any", "\\", x => x + ""); - - Assert.Equal(input, output); - } - - [Theory] - [InlineData("hello", "hello")] - [InlineData("?hello", "@hello")] - [InlineData("??hello", "@@hello")] - [InlineData("?? hello", "@@ hello")] - [InlineData("? ? hello", "@ @ hello")] - [InlineData(" ? ? hello", " @ @ hello")] - public void ReplaceOnTheBegining(string input, string expected) - { - var output = Helper.ReplaceAll(input, "?", "\\", x => "@"); - Assert.Equal(expected, output); - } - - [Theory] - [InlineData("hello?", "hello@")] - [InlineData("hello? ", "hello@ ")] - [InlineData("hello??? ", "hello@@@ ")] - [InlineData("hello ? ?? ? ", "hello @ @@ @ ")] - public void ReplaceOnTheEnd(string input, string expected) - { - var output = Helper.ReplaceAll(input, "?", "\\", x => "@"); - Assert.Equal(expected, output); - } - - [Theory] - [InlineData("hel\\?o ??? ", "hel\\?o 012 ")] - [InlineData("hel\\?o ?? \\?", "hel\\?o 01 \\?")] - [InlineData("hello?", "hello0")] - [InlineData("hello? ", "hello0 ")] - [InlineData("hello??? ", "hello012 ")] - [InlineData("hel?lo ? ?? ? ", "hel0lo 1 23 4 ")] - [InlineData("????", "0123")] - public void ReplaceWithPositions(string input, string expected) - { - var output = Helper.ReplaceAll(input, "?", "\\", x => x + ""); - Assert.Equal(expected, output); - } - - [Fact] - public void AllIndexesOf_ReturnIndexes_IfValueIsContainedInAString() - { - // Given - var input = "hello"; - - // When - var result = Helper.AllIndexesOf(input, "l"); - - // Then - Assert.Equal(new[] { 2, 3 }, result); - } - - [Theory] - [InlineData("")] - [InlineData(null)] - public void AllIndexesOf_ReturnEmptyCollection_IfValueIsEmptyOrNull(string value) - { - // Given - var input = "hello"; - - // When - var result = Helper.AllIndexesOf(input, value); - - // Then - Assert.Empty(result); - } - - [Fact] - public void AllIndexesOf_ReturnEmptyCollection_IfValueIsNotContainedInAString() - { - // Given - var input = "hello"; - - // When - var result = Helper.AllIndexesOf(input, "F"); - - // Then - Assert.Empty(result); - } - - [Fact] - public void Flatten_ReturnFlatttenDeepCollectionRecursively_IfArrayIsNested() - { - // Given - var objects = new object[] - { - 1, - 0.1, - 'A', - new object[] - { - 'A', - "B", - new object[] - { - "C", - 'D' - } - } - }; - - // When - var flatten = Helper.FlattenDeep(objects); - - // Then - Assert.Equal(new object[] { 1, 0.1, 'A', 'A', "B", "C", 'D' }, flatten); - } - - [Fact] - public void Flatten_FlatOneLevel() - { - // Given - var objects = new object[] - { - 1, - new object[] - { - 2, - 3, - new [] {4,5,6} - } - }; - - // When - var flatten = Helper.Flatten(objects); - - // Then - Assert.Equal(new[] { 4, 5, 6 }, flatten.ElementAt(3)); - } - [Fact] - public void Flatten_ShouldRemoveEmptyCollections() - { - // Given - var objects = new object[] - { - 1, - new object[] {}, - new object[] - { - 2, - 3, - } - }; - - // When - var flatten = Helper.Flatten(objects); - - // Then - Assert.Equal(new object[] { 1, 2, 3 }, flatten); - } - - [Fact] - public void IsArray_ReturnFalse_IfValueIsNull() - { - // Given - IEnumerable test = null; - - // When - var isArray = Helper.IsArray(test); - - // Then - Assert.False(isArray); - } - - [Fact] - public void IsArray_ReturnFalse_IfTypeOfValueIsString() - { - // Given - var value = "string"; - - // When - var isArray = Helper.IsArray(value); - - // Then - Assert.False(isArray); - } - - [Fact] - public void IsArray_ReturnTrue_IfValueIsExactlyIEnumerable() - { - // Given - var value = new object[] { 1, 'B', "C" }; - - // When - var isArray = Helper.IsArray(value); - - // Then - Assert.True(isArray); - } - - [Theory] - [InlineData("Users.Id", "Users.Id")] - [InlineData("Users.{Id", "Users.{Id")] - [InlineData("Users.{Id}", "Users.Id")] - [InlineData("Users.{Id,Name}", "Users.Id, Users.Name")] - [InlineData("Users.{Id,Name, Last_Name }", "Users.Id, Users.Name, Users.Last_Name")] - public void ExpandExpression(string input, string expected) - { - Assert.Equal(expected, string.Join(", ", Helper.ExpandExpression(input))); - } - - [Fact] - public void ExpandParameters() - { - var expanded = Helper.ExpandParameters("where id = ? or id in (?) or id in (?)", "?", "\\", new object[] { 1, new[] { 1, 2 }, new object[] { } }); - - Assert.Equal("where id = ? or id in (?,?) or id in ()", expanded); - } - - [Theory] - [InlineData(@"\{ text {", @"\", "{", "[", "{ text [")] - [InlineData(@"{ text {", @"\", "{", "[", "[ text [")] - public void WrapIdentifiers(string input, string escapeCharacter, string identifier, string newIdentifier, string expected) - { - var result = input.ReplaceIdentifierUnlessEscaped(escapeCharacter, identifier, newIdentifier); - Assert.Equal(expected, result); - } - } -} +namespace SqlKata.Tests; + +using System.Collections; +using System.Linq; +using Xunit; + +public class HelperTests { + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(" ")] + public void ItShouldKeepItAsIs(string input) { + var output = Helper.ReplaceAll(input, "any", "\\", x => x + ""); + + Assert.Equal(input, output); + } + + [Theory] + [InlineData("hello", "hello")] + [InlineData("?hello", "@hello")] + [InlineData("??hello", "@@hello")] + [InlineData("?? hello", "@@ hello")] + [InlineData("? ? hello", "@ @ hello")] + [InlineData(" ? ? hello", " @ @ hello")] + public void ReplaceOnTheBegining(string input, string expected) { + var output = Helper.ReplaceAll(input, "?", "\\", x => "@"); + Assert.Equal(expected, output); + } + + [Theory] + [InlineData("hello?", "hello@")] + [InlineData("hello? ", "hello@ ")] + [InlineData("hello??? ", "hello@@@ ")] + [InlineData("hello ? ?? ? ", "hello @ @@ @ ")] + public void ReplaceOnTheEnd(string input, string expected) { + var output = Helper.ReplaceAll(input, "?", "\\", x => "@"); + Assert.Equal(expected, output); + } + + [Theory] + [InlineData("hel\\?o ??? ", "hel\\?o 012 ")] + [InlineData("hel\\?o ?? \\?", "hel\\?o 01 \\?")] + [InlineData("hello?", "hello0")] + [InlineData("hello? ", "hello0 ")] + [InlineData("hello??? ", "hello012 ")] + [InlineData("hel?lo ? ?? ? ", "hel0lo 1 23 4 ")] + [InlineData("????", "0123")] + public void ReplaceWithPositions(string input, string expected) { + var output = Helper.ReplaceAll(input, "?", "\\", x => x + ""); + Assert.Equal(expected, output); + } + + [Fact] + public void AllIndexesOf_ReturnIndexes_IfValueIsContainedInAString() { + // Given + var input = "hello"; + + // When + var result = Helper.AllIndexesOf(input, "l"); + + // Then + Assert.Equal(new[] { 2, 3 }, result); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void AllIndexesOf_ReturnEmptyCollection_IfValueIsEmptyOrNull(string value) { + // Given + var input = "hello"; + + // When + var result = Helper.AllIndexesOf(input, value); + + // Then + Assert.Empty(result); + } + + [Fact] + public void AllIndexesOf_ReturnEmptyCollection_IfValueIsNotContainedInAString() { + // Given + var input = "hello"; + + // When + var result = Helper.AllIndexesOf(input, "F"); + + // Then + Assert.Empty(result); + } + + [Fact] + public void Flatten_ReturnFlatttenDeepCollectionRecursively_IfArrayIsNested() { + // Given + var objects = new object[] + { + 1, + 0.1, + 'A', + new object[] + { + 'A', + "B", + new object[] + { + "C", + 'D' + } + } + }; + + // When + var flatten = Helper.FlattenDeep(objects); + + // Then + Assert.Equal(new object[] { 1, 0.1, 'A', 'A', "B", "C", 'D' }, flatten); + } + + [Fact] + public void Flatten_FlatOneLevel() { + // Given + var objects = new object[] + { + 1, + new object[] + { + 2, + 3, + new [] {4,5,6} + } + }; + + // When + var flatten = Helper.Flatten(objects); + + // Then + Assert.Equal(new[] { 4, 5, 6 }, flatten.ElementAt(3)); + } + [Fact] + public void Flatten_ShouldRemoveEmptyCollections() { + // Given + var objects = new object[] + { + 1, + new object[] {}, + new object[] + { + 2, + 3, + } + }; + + // When + var flatten = Helper.Flatten(objects); + + // Then + Assert.Equal(new object[] { 1, 2, 3 }, flatten); + } + + [Fact] + public void IsArray_ReturnFalse_IfValueIsNull() { + // Given + IEnumerable test = null; + + // When + var isArray = Helper.IsArray(test); + + // Then + Assert.False(isArray); + } + + [Fact] + public void IsArray_ReturnFalse_IfTypeOfValueIsString() { + // Given + var value = "string"; + + // When + var isArray = Helper.IsArray(value); + + // Then + Assert.False(isArray); + } + + [Fact] + public void IsArray_ReturnTrue_IfValueIsExactlyIEnumerable() { + // Given + var value = new object[] { 1, 'B', "C" }; + + // When + var isArray = Helper.IsArray(value); + + // Then + Assert.True(isArray); + } + + [Theory] + [InlineData("Users.Id", "Users.Id")] + [InlineData("Users.{Id", "Users.{Id")] + [InlineData("Users.{Id}", "Users.Id")] + [InlineData("Users.{Id,Name}", "Users.Id, Users.Name")] + [InlineData("Users.{Id,Name, Last_Name }", "Users.Id, Users.Name, Users.Last_Name")] + public void ExpandExpression(string input, string expected) { + Assert.Equal(expected, string.Join(", ", Helper.ExpandExpression(input))); + } + + [Fact] + public void ExpandParameters() { + var expanded = Helper.ExpandParameters("where id = ? or id in (?) or id in (?)", "?", "\\", new object[] { 1, new[] { 1, 2 }, new object[] { } }); + + Assert.Equal("where id = ? or id in (?,?) or id in ()", expanded); + } + + [Theory] + [InlineData(@"\{ text {", @"\", "{", "[", "{ text [")] + [InlineData(@"{ text {", @"\", "{", "[", "[ text [")] + public void WrapIdentifiers(string input, string escapeCharacter, string identifier, string newIdentifier, string expected) { + var result = input.ReplaceIdentifierUnlessEscaped(escapeCharacter, identifier, newIdentifier); + Assert.Equal(expected, result); + } +} diff --git a/QueryBuilder.Tests/Infrastructure/TestCompiler.cs b/QueryBuilder.Tests/Infrastructure/TestCompiler.cs index aded9fd1..8a3f1509 100644 --- a/QueryBuilder.Tests/Infrastructure/TestCompiler.cs +++ b/QueryBuilder.Tests/Infrastructure/TestCompiler.cs @@ -1,58 +1,46 @@ -using System; -using System.Reflection; -using SqlKata.Compilers; - -namespace SqlKata.Tests.Infrastructure -{ - /// - /// A test class to expose private methods - /// - class TestCompiler : Compiler - { - public override string EngineCode { get; } = "test"; - - public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) - { - return FindCompilerMethodInfo(clauseType, methodName); - } - } - - class TestSqlServerCompiler : SqlServerCompiler - { - public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) - { - return FindCompilerMethodInfo(clauseType, methodName); - } - } - - class TestMySqlCompiler : MySqlCompiler - { - public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) - { - return FindCompilerMethodInfo(clauseType, methodName); - } - } - - class TestPostgresCompiler : PostgresCompiler - { - public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) - { - return FindCompilerMethodInfo(clauseType, methodName); - } - } - - class TestFirebirdCompiler : FirebirdCompiler - { - public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) - { - return FindCompilerMethodInfo(clauseType, methodName); - } - } - - class TestEmptyIdentifiersCompiler : TestCompiler - { - protected override string OpeningIdentifier { get; set; } = ""; - protected override string ClosingIdentifier { get; set; } = ""; - } -} - +namespace SqlKata.Tests.Infrastructure; + +using System; +using System.Reflection; +using SqlKata.Compilers; + +/// +/// A test class to expose private methods +/// +class TestCompiler : Compiler { + public override string EngineCode { get; } = "test"; + + public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) { + return FindCompilerMethodInfo(clauseType, methodName); + } +} + +class TestSqlServerCompiler : SqlServerCompiler { + public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) { + return FindCompilerMethodInfo(clauseType, methodName); + } +} + +class TestMySqlCompiler : MySqlCompiler { + public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) { + return FindCompilerMethodInfo(clauseType, methodName); + } +} + +class TestPostgresCompiler : PostgresCompiler { + public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) { + return FindCompilerMethodInfo(clauseType, methodName); + } +} + +class TestFirebirdCompiler : FirebirdCompiler { + public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) { + return FindCompilerMethodInfo(clauseType, methodName); + } +} + +class TestEmptyIdentifiersCompiler : TestCompiler { + protected override string OpeningIdentifier { get; set; } = ""; + protected override string ClosingIdentifier { get; set; } = ""; +} + diff --git a/QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs b/QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs index 2312a192..0750f5c4 100644 --- a/QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs +++ b/QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs @@ -1,109 +1,96 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using SqlKata.Compilers; - -namespace SqlKata.Tests.Infrastructure -{ - public class TestCompilersContainer - { - private static class Messages - { - public const string ERR_INVALID_ENGINECODE = "Engine code '{0}' is not valid"; - public const string ERR_INVALID_ENGINECODES = "Invalid engine codes supplied '{0}'"; - } - - protected readonly IDictionary Compilers = new Dictionary - { - [EngineCodes.Firebird] = new FirebirdCompiler(), - [EngineCodes.MySql] = new MySqlCompiler(), - [EngineCodes.Oracle] = new OracleCompiler(), - [EngineCodes.PostgreSql] = new PostgresCompiler(), - [EngineCodes.Sqlite] = new SqliteCompiler(), - [EngineCodes.SqlServer] = new SqlServerCompiler() - { - UseLegacyPagination = true - } - }; - - public IEnumerable KnownEngineCodes - { - get { return Compilers.Select(s => s.Key); } - } - - /// - /// Returns a instance for the given engine code - /// - /// - /// - public Compiler Get(string engineCode) - { - if (!Compilers.ContainsKey(engineCode)) - { - throw new InvalidOperationException(string.Format(Messages.ERR_INVALID_ENGINECODE, engineCode)); - } - - return Compilers[engineCode]; - } - - /// - /// Convenience method - /// - /// Does not validate generic type against engine code before cast - /// - /// - /// - public TCompiler Get(string engineCode) where TCompiler : Compiler - { - return (TCompiler)Get(engineCode); - } - - /// - /// Compiles the against the given engine code - /// - /// - /// - /// - public SqlResult CompileFor(string engineCode, Query query) - { - var compiler = Get(engineCode); - return compiler.Compile(query); - } - - /// - /// Compiles the against the given engine codes - /// - /// - /// - /// - public TestSqlResultContainer Compile(IEnumerable engineCodes, Query query) - { - var codes = engineCodes.ToList(); - - var results = Compilers - .Where(w => codes.Contains(w.Key)) - .ToDictionary(k => k.Key, v => v.Value.Compile(query.Clone())); - - if (results.Count != codes.Count) - { - var missingCodes = codes.Where(w => Compilers.All(a => a.Key != w)); - var templateArg = string.Join(", ", missingCodes); - throw new InvalidOperationException(string.Format(Messages.ERR_INVALID_ENGINECODES, templateArg)); - } - - return new TestSqlResultContainer(results); - } - - /// - /// Compiles the against all s - /// - /// - /// - public TestSqlResultContainer Compile(Query query) - { - var resultKeyValues = Compilers - .ToDictionary(k => k.Key, v => v.Value.Compile(query.Clone())); - return new TestSqlResultContainer(resultKeyValues); - } - } -} +namespace SqlKata.Tests.Infrastructure; + +using System; +using System.Collections.Generic; +using System.Linq; +using SqlKata.Compilers; + +public class TestCompilersContainer { + private static class Messages { + public const string ERR_INVALID_ENGINECODE = "Engine code '{0}' is not valid"; + public const string ERR_INVALID_ENGINECODES = "Invalid engine codes supplied '{0}'"; + } + + protected readonly IDictionary Compilers = new Dictionary { + [EngineCodes.Firebird] = new FirebirdCompiler(), + [EngineCodes.MySql] = new MySqlCompiler(), + [EngineCodes.Oracle] = new OracleCompiler(), + [EngineCodes.PostgreSql] = new PostgresCompiler(), + [EngineCodes.Sqlite] = new SqliteCompiler(), + [EngineCodes.SqlServer] = new SqlServerCompiler() { + UseLegacyPagination = true + } + }; + + public IEnumerable KnownEngineCodes { + get { return Compilers.Select(s => s.Key); } + } + + /// + /// Returns a instance for the given engine code + /// + /// + /// + public Compiler Get(string engineCode) { + if (!Compilers.ContainsKey(engineCode)) { + throw new InvalidOperationException(string.Format(Messages.ERR_INVALID_ENGINECODE, engineCode)); + } + + return Compilers[engineCode]; + } + + /// + /// Convenience method + /// + /// Does not validate generic type against engine code before cast + /// + /// + /// + public TCompiler Get(string engineCode) where TCompiler : Compiler { + return (TCompiler)Get(engineCode); + } + + /// + /// Compiles the against the given engine code + /// + /// + /// + /// + public SqlResult CompileFor(string engineCode, Query query) { + var compiler = Get(engineCode); + return compiler.Compile(query); + } + + /// + /// Compiles the against the given engine codes + /// + /// + /// + /// + public TestSqlResultContainer Compile(IEnumerable engineCodes, Query query) { + var codes = engineCodes.ToList(); + + var results = Compilers + .Where(w => codes.Contains(w.Key)) + .ToDictionary(k => k.Key, v => v.Value.Compile(query.Clone())); + + if (results.Count != codes.Count) { + var missingCodes = codes.Where(w => Compilers.All(a => a.Key != w)); + var templateArg = string.Join(", ", missingCodes); + throw new InvalidOperationException(string.Format(Messages.ERR_INVALID_ENGINECODES, templateArg)); + } + + return new TestSqlResultContainer(results); + } + + /// + /// Compiles the against all s + /// + /// + /// + public TestSqlResultContainer Compile(Query query) { + var resultKeyValues = Compilers + .ToDictionary(k => k.Key, v => v.Value.Compile(query.Clone())); + return new TestSqlResultContainer(resultKeyValues); + } +} diff --git a/QueryBuilder.Tests/Infrastructure/TestSqlResultContainer.cs b/QueryBuilder.Tests/Infrastructure/TestSqlResultContainer.cs index 16a5eeff..989a3f7e 100644 --- a/QueryBuilder.Tests/Infrastructure/TestSqlResultContainer.cs +++ b/QueryBuilder.Tests/Infrastructure/TestSqlResultContainer.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; - -namespace SqlKata.Tests.Infrastructure -{ - public class TestSqlResultContainer : ReadOnlyDictionary - { - public TestSqlResultContainer(IDictionary dictionary) : base(dictionary) - { - - } - } -} +namespace SqlKata.Tests.Infrastructure; + +using System.Collections.Generic; +using System.Collections.ObjectModel; + +public class TestSqlResultContainer : ReadOnlyDictionary { + public TestSqlResultContainer(IDictionary dictionary) : base(dictionary) { + + } +} diff --git a/QueryBuilder.Tests/Infrastructure/TestSupport.cs b/QueryBuilder.Tests/Infrastructure/TestSupport.cs index 78d912cc..51820911 100644 --- a/QueryBuilder.Tests/Infrastructure/TestSupport.cs +++ b/QueryBuilder.Tests/Infrastructure/TestSupport.cs @@ -1,20 +1,17 @@ -using System.Collections.Generic; -using System.Linq; - -namespace SqlKata.Tests.Infrastructure -{ - public abstract class TestSupport - { - protected readonly TestCompilersContainer Compilers = new TestCompilersContainer(); - - /// - /// For legacy test support - /// - /// - /// - protected IReadOnlyDictionary Compile(Query query) - { - return Compilers.Compile(query).ToDictionary(s => s.Key, v => v.Value.ToString()); - } - } -} +namespace SqlKata.Tests.Infrastructure; + +using System.Collections.Generic; +using System.Linq; + +public abstract class TestSupport { + protected readonly TestCompilersContainer Compilers = new TestCompilersContainer(); + + /// + /// For legacy test support + /// + /// + /// + protected IReadOnlyDictionary Compile(Query query) { + return Compilers.Compile(query).ToDictionary(s => s.Key, v => v.Value.ToString()); + } +} diff --git a/QueryBuilder.Tests/InfrastructureTests.cs b/QueryBuilder.Tests/InfrastructureTests.cs index 5bb7f2df..9223f51a 100644 --- a/QueryBuilder.Tests/InfrastructureTests.cs +++ b/QueryBuilder.Tests/InfrastructureTests.cs @@ -1,54 +1,47 @@ -using System; -using System.Linq; -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests -{ - public class InfrastructureTests : TestSupport - { - [Fact] - public void CanGetCompiler() - { - var compiler = Compilers.Get(EngineCodes.SqlServer); - - Assert.NotNull(compiler); - Assert.IsType(compiler); - } - - [Fact] - public void CanCompile() - { - var results = Compilers.Compile(new Query("Table")); - - Assert.NotNull(results); - Assert.Equal(Compilers.KnownEngineCodes.Count(), results.Count); - } - - [Fact] - public void CanCompileSelectively() - { - var desiredEngines = new[] { EngineCodes.SqlServer, EngineCodes.MySql }; - var results = Compilers.Compile(desiredEngines, new Query("Table")); - - Assert.Equal(desiredEngines.Length, results.Count); - Assert.Contains(results, a => a.Key == EngineCodes.SqlServer); - Assert.Contains(results, a => a.Key == EngineCodes.MySql); - } - - - [Fact] - public void ShouldThrowIfInvalidEngineCode() - { - Assert.Throws(() => Compilers.CompileFor("XYZ", new Query())); - } - - [Fact] - public void ShouldThrowIfAnyEngineCodesAreInvalid() - { - var codes = new[] { EngineCodes.SqlServer, "123", EngineCodes.MySql, "abc" }; - Assert.Throws(() => Compilers.Compile(codes, new Query())); - } - } -} +namespace SqlKata.Tests; + +using System; +using System.Linq; +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class InfrastructureTests : TestSupport { + [Fact] + public void CanGetCompiler() { + var compiler = Compilers.Get(EngineCodes.SqlServer); + + Assert.NotNull(compiler); + Assert.IsType(compiler); + } + + [Fact] + public void CanCompile() { + var results = Compilers.Compile(new Query("Table")); + + Assert.NotNull(results); + Assert.Equal(Compilers.KnownEngineCodes.Count(), results.Count); + } + + [Fact] + public void CanCompileSelectively() { + var desiredEngines = new[] { EngineCodes.SqlServer, EngineCodes.MySql }; + var results = Compilers.Compile(desiredEngines, new Query("Table")); + + Assert.Equal(desiredEngines.Length, results.Count); + Assert.Contains(results, a => a.Key == EngineCodes.SqlServer); + Assert.Contains(results, a => a.Key == EngineCodes.MySql); + } + + + [Fact] + public void ShouldThrowIfInvalidEngineCode() { + Assert.Throws(() => Compilers.CompileFor("XYZ", new Query())); + } + + [Fact] + public void ShouldThrowIfAnyEngineCodesAreInvalid() { + var codes = new[] { EngineCodes.SqlServer, "123", EngineCodes.MySql, "abc" }; + Assert.Throws(() => Compilers.Compile(codes, new Query())); + } +} diff --git a/QueryBuilder.Tests/InsertTests.cs b/QueryBuilder.Tests/InsertTests.cs index 926e18b2..0156a423 100644 --- a/QueryBuilder.Tests/InsertTests.cs +++ b/QueryBuilder.Tests/InsertTests.cs @@ -1,308 +1,287 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Dynamic; -using System.Linq; -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests -{ - public class InsertTests : TestSupport - { - private class Account - { - public Account(string name, string currency = null, string created_at = null, string color = null) - { - this.name = name ?? throw new ArgumentNullException(nameof(name)); - this.Currency = currency; - this.color = color; - } - - public string name { get; set; } - - [Column("currency_id")] - public string Currency { get; set; } - - [Ignore] - public string color { get; set; } - } - - [Fact] - public void InsertObject() - { - var query = new Query("Table") - .AsInsert( - new - { - Name = "The User", - Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc), - }); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InsertFromSubQueryWithCte() - { - var query = new Query("expensive_cars") - .With("old_cards", new Query("all_cars").Where("year", "<", 2000)) - .AsInsert( - new[] { "name", "model", "year" }, - new Query("old_cars").Where("price", ">", 100).ForPage(2, 10)); - - var c = Compile(query); - - Assert.Equal( - "WITH [old_cards] AS (SELECT * FROM [all_cars] WHERE [year] < 2000)\nINSERT INTO [expensive_cars] ([name], [model], [year]) SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [old_cars] WHERE [price] > 100) AS [results_wrapper] WHERE [row_num] BETWEEN 11 AND 20", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "WITH `old_cards` AS (SELECT * FROM `all_cars` WHERE `year` < 2000)\nINSERT INTO `expensive_cars` (`name`, `model`, `year`) SELECT * FROM `old_cars` WHERE `price` > 100 LIMIT 10 OFFSET 10", - c[EngineCodes.MySql]); - - Assert.Equal( - "WITH \"old_cards\" AS (SELECT * FROM \"all_cars\" WHERE \"year\" < 2000)\nINSERT INTO \"expensive_cars\" (\"name\", \"model\", \"year\") SELECT * FROM \"old_cars\" WHERE \"price\" > 100 LIMIT 10 OFFSET 10", - c[EngineCodes.PostgreSql]); - } - - [Fact] - public void InsertMultiRecords() - { - var query = new Query("expensive_cars") - .AsInsert( - new[] { "name", "brand", "year" }, - new[] - { - new object[] { "Chiron", "Bugatti", null }, - new object[] { "Huayra", "Pagani", 2012 }, - new object[] { "Reventon roadster", "Lamborghini", 2009 } - }); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO [expensive_cars] ([name], [brand], [year]) VALUES ('Chiron', 'Bugatti', NULL), ('Huayra', 'Pagani', 2012), ('Reventon roadster', 'Lamborghini', 2009)", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "INSERT INTO \"EXPENSIVE_CARS\" (\"NAME\", \"BRAND\", \"YEAR\") SELECT 'Chiron', 'Bugatti', NULL FROM RDB$DATABASE UNION ALL SELECT 'Huayra', 'Pagani', 2012 FROM RDB$DATABASE UNION ALL SELECT 'Reventon roadster', 'Lamborghini', 2009 FROM RDB$DATABASE", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InsertWithNullValues() - { - var query = new Query("Books") - .AsInsert( - new[] { "Id", "Author", "ISBN", "Date" }, - new object[] { 1, "Author 1", "123456", null }); - - var c = Compile(query); - - Assert.Equal("INSERT INTO [Books] ([Id], [Author], [ISBN], [Date]) VALUES (1, 'Author 1', '123456', NULL)", - c[EngineCodes.SqlServer]); - - - Assert.Equal( - "INSERT INTO \"BOOKS\" (\"ID\", \"AUTHOR\", \"ISBN\", \"DATE\") VALUES (1, 'Author 1', '123456', NULL)", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InsertWithEmptyString() - { - var query = new Query("Books") - .AsInsert( - new[] { "Id", "Author", "ISBN", "Description" }, - new object[] { 1, "Author 1", "123456", "" }); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO [Books] ([Id], [Author], [ISBN], [Description]) VALUES (1, 'Author 1', '123456', '')", - c[EngineCodes.SqlServer]); - - - Assert.Equal( - "INSERT INTO \"BOOKS\" (\"ID\", \"AUTHOR\", \"ISBN\", \"DESCRIPTION\") VALUES (1, 'Author 1', '123456', '')", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InsertWithByteArray() - { - var fauxImagebytes = new byte[] { 0x1, 0x3, 0x3, 0x7 }; - var query = new Query("Books") - .AsInsert( - new[] { "Id", "CoverImageBytes" }, - new object[] - { - 1, - fauxImagebytes - }); - - var c = Compilers.Compile(query); - Assert.All(c.Values, a => Assert.Equal(2, a.NamedBindings.Count)); - - var exemplar = c[EngineCodes.SqlServer]; - - Assert.Equal("INSERT INTO [Books] ([Id], [CoverImageBytes]) VALUES (?, ?)", exemplar.RawSql); - Assert.Equal("INSERT INTO [Books] ([Id], [CoverImageBytes]) VALUES (@p0, @p1)", exemplar.Sql); - } - - [Fact] - public void InsertWithIgnoreAndColumnProperties() - { - var account = new Account(name: $"popular", color: $"blue", currency: "US"); - var query = new Query("Account").AsInsert(account); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO [Account] ([name], [currency_id]) VALUES ('popular', 'US')", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "INSERT INTO \"ACCOUNT\" (\"NAME\", \"CURRENCY_ID\") VALUES ('popular', 'US')", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InsertFromRaw() - { - var query = new Query() - .FromRaw("Table.With.Dots") - .AsInsert( - new - { - Name = "The User", - Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc), - }); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO Table.With.Dots ([Name], [Age]) VALUES ('The User', '2018-01-01')", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void InsertFromQueryShouldFail() - { - var query = new Query() - .From(new Query("InnerTable")) - .AsInsert( - new - { - Name = "The User", - Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc), - }); - - Assert.Throws(() => - { - Compile(query); - }); - } - - [Fact] - public void InsertKeyValuePairs() - { - var dictionaryUser = new Dictionary - { - { "Name", "The User" }, - { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, - } - .ToArray(); - - var query = new Query("Table") - .AsInsert(dictionaryUser); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InsertDictionary() - { - var dictionaryUser = new Dictionary { - { "Name", "The User" }, - { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, - }; - - var query = new Query("Table") - .AsInsert(dictionaryUser); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InsertReadOnlyDictionary() - { - var dictionaryUser = new ReadOnlyDictionary( - new Dictionary - { - { "Name", "The User" }, - { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, - }); - - var query = new Query("Table") - .AsInsert(dictionaryUser); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", - c[EngineCodes.Firebird]); - } - - [Fact] - public void InsertExpandoObject() - { - dynamic expandoUser = new ExpandoObject(); - expandoUser.Name = "The User"; - expandoUser.Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - var query = new Query("Table") - .AsInsert(expandoUser); - - var c = Compile(query); - - Assert.Equal( - "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", - c[EngineCodes.Firebird]); - } - } -} +namespace SqlKata.Tests; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Dynamic; +using System.Linq; +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class InsertTests : TestSupport { + private class Account { + public Account(string name, string currency = null, string created_at = null, string color = null) { + this.name = name ?? throw new ArgumentNullException(nameof(name)); + this.Currency = currency; + this.color = color; + } + + public string name { get; set; } + + [Column("currency_id")] + public string Currency { get; set; } + + [Ignore] + public string color { get; set; } + } + + [Fact] + public void InsertObject() { + var query = new Query("Table") + .AsInsert( + new { + Name = "The User", + Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc), + }); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InsertFromSubQueryWithCte() { + var query = new Query("expensive_cars") + .With("old_cards", new Query("all_cars").Where("year", "<", 2000)) + .AsInsert( + new[] { "name", "model", "year" }, + new Query("old_cars").Where("price", ">", 100).ForPage(2, 10)); + + var c = Compile(query); + + Assert.Equal( + "WITH [old_cards] AS (SELECT * FROM [all_cars] WHERE [year] < 2000)\nINSERT INTO [expensive_cars] ([name], [model], [year]) SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [old_cars] WHERE [price] > 100) AS [results_wrapper] WHERE [row_num] BETWEEN 11 AND 20", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "WITH `old_cards` AS (SELECT * FROM `all_cars` WHERE `year` < 2000)\nINSERT INTO `expensive_cars` (`name`, `model`, `year`) SELECT * FROM `old_cars` WHERE `price` > 100 LIMIT 10 OFFSET 10", + c[EngineCodes.MySql]); + + Assert.Equal( + "WITH \"old_cards\" AS (SELECT * FROM \"all_cars\" WHERE \"year\" < 2000)\nINSERT INTO \"expensive_cars\" (\"name\", \"model\", \"year\") SELECT * FROM \"old_cars\" WHERE \"price\" > 100 LIMIT 10 OFFSET 10", + c[EngineCodes.PostgreSql]); + } + + [Fact] + public void InsertMultiRecords() { + var query = new Query("expensive_cars") + .AsInsert( + new[] { "name", "brand", "year" }, + new[] + { + new object[] { "Chiron", "Bugatti", null }, + new object[] { "Huayra", "Pagani", 2012 }, + new object[] { "Reventon roadster", "Lamborghini", 2009 } + }); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO [expensive_cars] ([name], [brand], [year]) VALUES ('Chiron', 'Bugatti', NULL), ('Huayra', 'Pagani', 2012), ('Reventon roadster', 'Lamborghini', 2009)", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "INSERT INTO \"EXPENSIVE_CARS\" (\"NAME\", \"BRAND\", \"YEAR\") SELECT 'Chiron', 'Bugatti', NULL FROM RDB$DATABASE UNION ALL SELECT 'Huayra', 'Pagani', 2012 FROM RDB$DATABASE UNION ALL SELECT 'Reventon roadster', 'Lamborghini', 2009 FROM RDB$DATABASE", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InsertWithNullValues() { + var query = new Query("Books") + .AsInsert( + new[] { "Id", "Author", "ISBN", "Date" }, + new object[] { 1, "Author 1", "123456", null }); + + var c = Compile(query); + + Assert.Equal("INSERT INTO [Books] ([Id], [Author], [ISBN], [Date]) VALUES (1, 'Author 1', '123456', NULL)", + c[EngineCodes.SqlServer]); + + + Assert.Equal( + "INSERT INTO \"BOOKS\" (\"ID\", \"AUTHOR\", \"ISBN\", \"DATE\") VALUES (1, 'Author 1', '123456', NULL)", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InsertWithEmptyString() { + var query = new Query("Books") + .AsInsert( + new[] { "Id", "Author", "ISBN", "Description" }, + new object[] { 1, "Author 1", "123456", "" }); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO [Books] ([Id], [Author], [ISBN], [Description]) VALUES (1, 'Author 1', '123456', '')", + c[EngineCodes.SqlServer]); + + + Assert.Equal( + "INSERT INTO \"BOOKS\" (\"ID\", \"AUTHOR\", \"ISBN\", \"DESCRIPTION\") VALUES (1, 'Author 1', '123456', '')", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InsertWithByteArray() { + var fauxImagebytes = new byte[] { 0x1, 0x3, 0x3, 0x7 }; + var query = new Query("Books") + .AsInsert( + new[] { "Id", "CoverImageBytes" }, + new object[] + { + 1, + fauxImagebytes + }); + + var c = Compilers.Compile(query); + Assert.All(c.Values, a => Assert.Equal(2, a.NamedBindings.Count)); + + var exemplar = c[EngineCodes.SqlServer]; + + Assert.Equal("INSERT INTO [Books] ([Id], [CoverImageBytes]) VALUES (?, ?)", exemplar.RawSql); + Assert.Equal("INSERT INTO [Books] ([Id], [CoverImageBytes]) VALUES (@p0, @p1)", exemplar.Sql); + } + + [Fact] + public void InsertWithIgnoreAndColumnProperties() { + var account = new Account(name: $"popular", color: $"blue", currency: "US"); + var query = new Query("Account").AsInsert(account); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO [Account] ([name], [currency_id]) VALUES ('popular', 'US')", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "INSERT INTO \"ACCOUNT\" (\"NAME\", \"CURRENCY_ID\") VALUES ('popular', 'US')", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InsertFromRaw() { + var query = new Query() + .FromRaw("Table.With.Dots") + .AsInsert( + new { + Name = "The User", + Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc), + }); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO Table.With.Dots ([Name], [Age]) VALUES ('The User', '2018-01-01')", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void InsertFromQueryShouldFail() { + var query = new Query() + .From(new Query("InnerTable")) + .AsInsert( + new { + Name = "The User", + Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc), + }); + + Assert.Throws(() => { + Compile(query); + }); + } + + [Fact] + public void InsertKeyValuePairs() { + var dictionaryUser = new Dictionary + { + { "Name", "The User" }, + { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, + } + .ToArray(); + + var query = new Query("Table") + .AsInsert(dictionaryUser); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InsertDictionary() { + var dictionaryUser = new Dictionary { + { "Name", "The User" }, + { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, + }; + + var query = new Query("Table") + .AsInsert(dictionaryUser); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InsertReadOnlyDictionary() { + var dictionaryUser = new ReadOnlyDictionary( + new Dictionary + { + { "Name", "The User" }, + { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, + }); + + var query = new Query("Table") + .AsInsert(dictionaryUser); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", + c[EngineCodes.Firebird]); + } + + [Fact] + public void InsertExpandoObject() { + dynamic expandoUser = new ExpandoObject(); + expandoUser.Name = "The User"; + expandoUser.Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + var query = new Query("Table") + .AsInsert(expandoUser); + + var c = Compile(query); + + Assert.Equal( + "INSERT INTO [Table] ([Name], [Age]) VALUES ('The User', '2018-01-01')", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "INSERT INTO \"TABLE\" (\"NAME\", \"AGE\") VALUES ('The User', '2018-01-01')", + c[EngineCodes.Firebird]); + } +} diff --git a/QueryBuilder.Tests/MySql/MySqlLimitTests.cs b/QueryBuilder.Tests/MySql/MySqlLimitTests.cs index 3e22c19a..3abfadc2 100644 --- a/QueryBuilder.Tests/MySql/MySqlLimitTests.cs +++ b/QueryBuilder.Tests/MySql/MySqlLimitTests.cs @@ -1,58 +1,51 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.MySql -{ - public class MySqlLimitTests : TestSupport - { - private readonly MySqlCompiler compiler; - - public MySqlLimitTests() - { - compiler = Compilers.Get(EngineCodes.MySql); - } - - [Fact] - public void WithNoLimitNorOffset() - { - var query = new Query("Table"); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void WithNoOffset() - { - var query = new Query("Table").Limit(10); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); - Assert.Equal(10, ctx.Bindings[0]); - } - - [Fact] - public void WithNoLimit() - { - var query = new Query("Table").Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Equal("LIMIT 18446744073709551615 OFFSET ?", compiler.CompileLimit(ctx)); - Assert.Equal(20L, ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void WithLimitAndOffset() - { - var query = new Query("Table").Limit(5).Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); - Assert.Equal(5, ctx.Bindings[0]); - Assert.Equal(20L, ctx.Bindings[1]); - Assert.Equal(2, ctx.Bindings.Count); - } - } -} +namespace SqlKata.Tests.MySql; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class MySqlLimitTests : TestSupport { + private readonly MySqlCompiler compiler; + + public MySqlLimitTests() { + compiler = Compilers.Get(EngineCodes.MySql); + } + + [Fact] + public void WithNoLimitNorOffset() { + var query = new Query("Table"); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void WithNoOffset() { + var query = new Query("Table").Limit(10); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); + Assert.Equal(10, ctx.Bindings[0]); + } + + [Fact] + public void WithNoLimit() { + var query = new Query("Table").Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("LIMIT 18446744073709551615 OFFSET ?", compiler.CompileLimit(ctx)); + Assert.Equal(20L, ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void WithLimitAndOffset() { + var query = new Query("Table").Limit(5).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); + Assert.Equal(5, ctx.Bindings[0]); + Assert.Equal(20L, ctx.Bindings[1]); + Assert.Equal(2, ctx.Bindings.Count); + } +} diff --git a/QueryBuilder.Tests/MySqlExecutionTest.cs b/QueryBuilder.Tests/MySqlExecutionTest.cs index 259dcccf..8aecf8a7 100644 --- a/QueryBuilder.Tests/MySqlExecutionTest.cs +++ b/QueryBuilder.Tests/MySqlExecutionTest.cs @@ -1,270 +1,250 @@ -using SqlKata.Compilers; -using Xunit; -using SqlKata.Execution; -using MySql.Data.MySqlClient; -using System; -using System.Linq; -using static SqlKata.Expressions; -using System.Collections.Generic; - -namespace SqlKata.Tests -{ - public class MySqlExecutionTest - { - [Fact] - public void EmptySelect() - { - - var db = DB().Create("Cars", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - var rows = db.Query("Cars").Get(); - - Assert.Empty(rows); - - db.Drop("Cars"); - } - - [Fact] - public void SelectWithLimit() - { - var db = DB().Create("Cars", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); - - var rows = db.Query("Cars").Get().ToList(); - - Assert.Single(rows); - - db.Drop("Cars"); - } - - [Fact] - public void Count() - { - var db = DB().Create("Cars", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); - var count = db.Query("Cars").Count(); - Assert.Equal(1, count); - - db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Toyota', 2021)"); - count = db.Query("Cars").Count(); - Assert.Equal(2, count); - - int affected = db.Query("Cars").Delete(); - Assert.Equal(2, affected); - - count = db.Query("Cars").Count(); - Assert.Equal(0, count); - - db.Drop("Cars"); - } - - [Fact] - public void CloneThenCount() - { - var db = DB().Create("Cars", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - for (int i = 0; i < 10; i++) - { - db.Query("Cars").Insert(new - { - Brand = "Brand " + i, - Year = "2020", - }); - } - - var query = db.Query("Cars").Where("Id", "<", 5); - var count = query.Count(); - var cloneCount = query.Clone().Count(); - - Assert.Equal(4, count); - Assert.Equal(4, cloneCount); - - db.Drop("Cars"); - } - - [Fact] - public void QueryWithVariable() - { - var db = DB().Create("Cars", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - for (int i = 0; i < 10; i++) - { - db.Query("Cars").Insert(new - { - Brand = "Brand " + i, - Year = "2020", - }); - } - - - var count = db.Query("Cars") - .Define("Threshold", 5) - .Where("Id", "<", SqlKata.Expressions.Variable("Threshold")) - .Count(); - - Assert.Equal(4, count); - - db.Drop("Cars"); - } - - [Fact] - public void InlineTable() - { - var db = DB().Create("Transaction", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Amount int NOT NULL", - "Date DATE NOT NULL", - }); - - db.Query("Transaction").Insert(new - { - Date = "2022-01-01", - Amount = 10 - }); - - - var rows = db.Query("Transaction") - .With("Rates", new[] { "Date", "Rate" }, new object[][] { - new object[] {"2022-01-01", 0.5}, - }) - .Join("Rates", "Rates.Date", "Transaction.Date") - .SelectRaw("Transaction.Amount * Rates.Rate as AmountConverted") - .Get(); - - Assert.Single(rows); - Assert.Equal(5, rows.First().AmountConverted); - - db.Drop("Transaction"); - } - - [Fact] - public void ExistsShouldReturnFalseForEmptyTable() - { - var db = DB().Create("Transaction", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Amount int NOT NULL", - "Date DATE NOT NULL", - }); - - var exists = db.Query("Transaction").Exists(); - Assert.False(exists); - - db.Drop("Transaction"); - } - - [Fact] - public void ExistsShouldReturnTrueForNonEmptyTable() - { - var db = DB().Create("Transaction", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Amount int NOT NULL", - "Date DATE NOT NULL", - }); - - db.Query("Transaction").Insert(new - { - Date = "2022-01-01", - Amount = 10 - }); - - var exists = db.Query("Transaction").Exists(); - Assert.True(exists); - - db.Drop("Transaction"); - } - - [Fact] - public void BasicSelectFilter() - { - var db = DB().Create("Transaction", new[] { - "Id INT PRIMARY KEY AUTO_INCREMENT", - "Date DATE NOT NULL", - "Amount int NOT NULL", - }); - - var data = new Dictionary { - // 2020 - {"2020-01-01", 10}, - {"2020-05-01", 20}, - - // 2021 - {"2021-01-01", 40}, - {"2021-02-01", 10}, - {"2021-04-01", -10}, - - // 2022 - {"2022-01-01", 80}, - {"2022-02-01", -30}, - {"2022-05-01", 50}, - }; - - foreach (var row in data) - { - db.Query("Transaction").Insert(new - { - Date = row.Key, - Amount = row.Value - }); - } - - var query = db.Query("Transaction") - .SelectSum("Amount as Total_2020", q => q.WhereDatePart("year", "date", 2020)) - .SelectSum("Amount as Total_2021", q => q.WhereDatePart("year", "date", 2021)) - .SelectSum("Amount as Total_2022", q => q.WhereDatePart("year", "date", 2022)) - ; - - var results = query.Get().ToList(); - Assert.Single(results); - Assert.Equal(30, results[0].Total_2020); - Assert.Equal(40, results[0].Total_2021); - Assert.Equal(100, results[0].Total_2022); - - db.Drop("Transaction"); - } - - QueryFactory DB() - { - var host = Environment.GetEnvironmentVariable("SQLKATA_MYSQL_HOST"); - var user = Environment.GetEnvironmentVariable("SQLKATA_MYSQL_USER"); - var dbName = Environment.GetEnvironmentVariable("SQLKATA_MYSQL_DB"); - var dbPass = Environment.GetEnvironmentVariable("SQLKATA_MYSQL_PASSWORD"); - var cs = $"Server={host};User={user};Database={dbName};Password={dbPass}"; - - var connection = new MySqlConnection(cs); - - var db = new QueryFactory(connection, new MySqlCompiler()); - - return db; - } - - - - } -} +namespace SqlKata.Tests; + +using SqlKata.Compilers; +using Xunit; +using SqlKata.Execution; +using global::MySql.Data.MySqlClient; +using System; +using System.Linq; +using static SqlKata.Expressions; +using System.Collections.Generic; + +public class MySqlExecutionTest { + [Fact] + public void EmptySelect() { + + var db = DB().Create("Cars", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + var rows = db.Query("Cars").Get(); + + Assert.Empty(rows); + + db.Drop("Cars"); + } + + [Fact] + public void SelectWithLimit() { + var db = DB().Create("Cars", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); + + var rows = db.Query("Cars").Get().ToList(); + + Assert.Single(rows); + + db.Drop("Cars"); + } + + [Fact] + public void Count() { + var db = DB().Create("Cars", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); + var count = db.Query("Cars").Count(); + Assert.Equal(1, count); + + db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Toyota', 2021)"); + count = db.Query("Cars").Count(); + Assert.Equal(2, count); + + int affected = db.Query("Cars").Delete(); + Assert.Equal(2, affected); + + count = db.Query("Cars").Count(); + Assert.Equal(0, count); + + db.Drop("Cars"); + } + + [Fact] + public void CloneThenCount() { + var db = DB().Create("Cars", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + for (int i = 0; i < 10; i++) { + db.Query("Cars").Insert(new { + Brand = "Brand " + i, + Year = "2020", + }); + } + + var query = db.Query("Cars").Where("Id", "<", 5); + var count = query.Count(); + var cloneCount = query.Clone().Count(); + + Assert.Equal(4, count); + Assert.Equal(4, cloneCount); + + db.Drop("Cars"); + } + + [Fact] + public void QueryWithVariable() { + var db = DB().Create("Cars", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + for (int i = 0; i < 10; i++) { + db.Query("Cars").Insert(new { + Brand = "Brand " + i, + Year = "2020", + }); + } + + + var count = db.Query("Cars") + .Define("Threshold", 5) + .Where("Id", "<", SqlKata.Expressions.Variable("Threshold")) + .Count(); + + Assert.Equal(4, count); + + db.Drop("Cars"); + } + + [Fact] + public void InlineTable() { + var db = DB().Create("Transaction", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Amount int NOT NULL", + "Date DATE NOT NULL", + }); + + db.Query("Transaction").Insert(new { + Date = "2022-01-01", + Amount = 10 + }); + + + var rows = db.Query("Transaction") + .With("Rates", new[] { "Date", "Rate" }, new object[][] { + new object[] {"2022-01-01", 0.5}, + }) + .Join("Rates", "Rates.Date", "Transaction.Date") + .SelectRaw("Transaction.Amount * Rates.Rate as AmountConverted") + .Get(); + + Assert.Single(rows); + Assert.Equal(5, rows.First().AmountConverted); + + db.Drop("Transaction"); + } + + [Fact] + public void ExistsShouldReturnFalseForEmptyTable() { + var db = DB().Create("Transaction", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Amount int NOT NULL", + "Date DATE NOT NULL", + }); + + var exists = db.Query("Transaction").Exists(); + Assert.False(exists); + + db.Drop("Transaction"); + } + + [Fact] + public void ExistsShouldReturnTrueForNonEmptyTable() { + var db = DB().Create("Transaction", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Amount int NOT NULL", + "Date DATE NOT NULL", + }); + + db.Query("Transaction").Insert(new { + Date = "2022-01-01", + Amount = 10 + }); + + var exists = db.Query("Transaction").Exists(); + Assert.True(exists); + + db.Drop("Transaction"); + } + + [Fact] + public void BasicSelectFilter() { + var db = DB().Create("Transaction", new[] { + "Id INT PRIMARY KEY AUTO_INCREMENT", + "Date DATE NOT NULL", + "Amount int NOT NULL", + }); + + var data = new Dictionary { + // 2020 + {"2020-01-01", 10}, + {"2020-05-01", 20}, + + // 2021 + {"2021-01-01", 40}, + {"2021-02-01", 10}, + {"2021-04-01", -10}, + + // 2022 + {"2022-01-01", 80}, + {"2022-02-01", -30}, + {"2022-05-01", 50}, + }; + + foreach (var row in data) { + db.Query("Transaction").Insert(new { + Date = row.Key, + Amount = row.Value + }); + } + + var query = db.Query("Transaction") + .SelectSum("Amount as Total_2020", q => q.WhereDatePart("year", "date", 2020)) + .SelectSum("Amount as Total_2021", q => q.WhereDatePart("year", "date", 2021)) + .SelectSum("Amount as Total_2022", q => q.WhereDatePart("year", "date", 2022)) + ; + + var results = query.Get().ToList(); + Assert.Single(results); + Assert.Equal(30, results[0].Total_2020); + Assert.Equal(40, results[0].Total_2021); + Assert.Equal(100, results[0].Total_2022); + + db.Drop("Transaction"); + } + + QueryFactory DB() { + var host = Environment.GetEnvironmentVariable("SQLKATA_MYSQL_HOST"); + var user = Environment.GetEnvironmentVariable("SQLKATA_MYSQL_USER"); + var dbName = Environment.GetEnvironmentVariable("SQLKATA_MYSQL_DB"); + var dbPass = Environment.GetEnvironmentVariable("SQLKATA_MYSQL_PASSWORD"); + var cs = $"Server={host};User={user};Database={dbName};Password={dbPass}"; + + var connection = new MySqlConnection(cs); + + var db = new QueryFactory(connection, new MySqlCompiler()); + + return db; + } + + + +} diff --git a/QueryBuilder.Tests/OperatorWhitelistTests.cs b/QueryBuilder.Tests/OperatorWhitelistTests.cs index 55c3aafc..6dc653dc 100644 --- a/QueryBuilder.Tests/OperatorWhitelistTests.cs +++ b/QueryBuilder.Tests/OperatorWhitelistTests.cs @@ -1,139 +1,127 @@ -using System; -using SqlKata.Compilers; -using Xunit; - -namespace SqlKata.Tests -{ - public class OperatorWhitelistTests - { - - public OperatorWhitelistTests() - { - - } - - [Theory] - [InlineData("!!")] - [InlineData("~!")] - [InlineData("*=")] - public void DenyInvalidOperatorsInWhere(string op) - { - var compiler = new SqlServerCompiler(); - - Assert.Throws(() => - { - compiler.Compile(new Query("Table").Where("Id", op, 1)); - compiler.Compile(new Query("Table").OrWhere("Id", op, 1)); - compiler.Compile(new Query("Table").WhereNot("Id", op, 1)); - compiler.Compile(new Query("Table").OrWhereNot("Id", op, 1)); - - compiler.Compile(new Query("Table").WhereColumns("Col1", op, "Col2")); - compiler.Compile(new Query("Table").OrWhereColumns("Col1", op, "Col2")); - }); - } - - [Theory] - [InlineData("!!")] - [InlineData("~!")] - [InlineData("*=")] - public void DenyInvalidOperatorsInHaving(string op) - { - var compiler = new SqlServerCompiler(); - - Assert.Throws(() => - { - compiler.Compile(new Query("Table").Having("Id", op, 1)); - compiler.Compile(new Query("Table").OrHaving("Id", op, 1)); - compiler.Compile(new Query("Table").HavingNot("Id", op, 1)); - compiler.Compile(new Query("Table").OrHavingNot("Id", op, 1)); - - compiler.Compile(new Query("Table").HavingColumns("Col1", op, "Col2")); - compiler.Compile(new Query("Table").OrHavingColumns("Col1", op, "Col2")); - }); - } - - - [Theory] - [InlineData("=")] - [InlineData("!=")] - [InlineData("ilike")] - public void AllowValidOperatorsInWhere(string op) - { - new Query("Table").Where("Id", op, 1); - new Query("Table").OrWhere("Id", op, 1); - new Query("Table").WhereNot("Id", op, 1); - new Query("Table").OrWhereNot("Id", op, 1); - - new Query("Table").WhereColumns("Col1", op, "Col2"); - new Query("Table").OrWhereColumns("Col1", op, "Col2"); - } - - [Theory] - [InlineData("=")] - [InlineData("!=")] - [InlineData("ilike")] - public void AllowValidOperatorsInHaving(string op) - { - new Query("Table").Having("Id", op, 1); - new Query("Table").OrHaving("Id", op, 1); - new Query("Table").HavingNot("Id", op, 1); - new Query("Table").OrHavingNot("Id", op, 1); - - new Query("Table").HavingColumns("Col1", op, "Col2"); - new Query("Table").OrHavingColumns("Col1", op, "Col2"); - } - - [Theory] - [InlineData("^")] - [InlineData("<<")] - [InlineData(">>")] - [InlineData("~")] - [InlineData("~*")] - [InlineData("!~")] - [InlineData("!~*")] - public void ShouldNotThrowAfterWhiteListing(string op) - { - var compiler = new SqlServerCompiler().Whitelist(op); - - var query = new Query("Table"); - - compiler.Compile(query.Clone().Where("Id", op, 1)); - compiler.Compile(query.Clone().OrWhere("Id", op, 1)); - compiler.Compile(query.Clone().WhereNot("Id", op, 1)); - compiler.Compile(query.Clone().OrWhereNot("Id", op, 1)); - - compiler.Compile(query.Clone().WhereColumns("Col1", op, "Col2")); - compiler.Compile(query.Clone().OrWhereColumns("Col1", op, "Col2")); - - compiler.Compile(query.Clone().Having("Id", op, 1)); - compiler.Compile(query.Clone().OrHaving("Id", op, 1)); - compiler.Compile(query.Clone().HavingNot("Id", op, 1)); - compiler.Compile(query.Clone().OrHavingNot("Id", op, 1)); - - compiler.Compile(query.Clone().HavingColumns("Col1", op, "Col2")); - compiler.Compile(query.Clone().OrHavingColumns("Col1", op, "Col2")); - } - - [Fact] - public void ShouldAllowWhiteListedOperatorsInNestedWhere() - { - var compiler = new SqlServerCompiler().Whitelist("!!"); - - var query = new Query("Table") - .Where(q => q.Where("A", "!!", "value")); - - compiler.Compile(query); - } - - [Fact] - public void ShouldNotConsiderWhereRawCondition() - { - var compiler = new SqlServerCompiler(); - - var query = new Query("Table") - .WhereRaw("Col !! value"); - - } - - } -} +namespace SqlKata.Tests; + +using System; +using SqlKata.Compilers; +using Xunit; + +public class OperatorWhitelistTests { + + public OperatorWhitelistTests() { + + } + + [Theory] + [InlineData("!!")] + [InlineData("~!")] + [InlineData("*=")] + public void DenyInvalidOperatorsInWhere(string op) { + var compiler = new SqlServerCompiler(); + + Assert.Throws(() => { + compiler.Compile(new Query("Table").Where("Id", op, 1)); + compiler.Compile(new Query("Table").OrWhere("Id", op, 1)); + compiler.Compile(new Query("Table").WhereNot("Id", op, 1)); + compiler.Compile(new Query("Table").OrWhereNot("Id", op, 1)); + + compiler.Compile(new Query("Table").WhereColumns("Col1", op, "Col2")); + compiler.Compile(new Query("Table").OrWhereColumns("Col1", op, "Col2")); + }); + } + + [Theory] + [InlineData("!!")] + [InlineData("~!")] + [InlineData("*=")] + public void DenyInvalidOperatorsInHaving(string op) { + var compiler = new SqlServerCompiler(); + + Assert.Throws(() => { + compiler.Compile(new Query("Table").Having("Id", op, 1)); + compiler.Compile(new Query("Table").OrHaving("Id", op, 1)); + compiler.Compile(new Query("Table").HavingNot("Id", op, 1)); + compiler.Compile(new Query("Table").OrHavingNot("Id", op, 1)); + + compiler.Compile(new Query("Table").HavingColumns("Col1", op, "Col2")); + compiler.Compile(new Query("Table").OrHavingColumns("Col1", op, "Col2")); + }); + } + + + [Theory] + [InlineData("=")] + [InlineData("!=")] + [InlineData("ilike")] + public void AllowValidOperatorsInWhere(string op) { + new Query("Table").Where("Id", op, 1); + new Query("Table").OrWhere("Id", op, 1); + new Query("Table").WhereNot("Id", op, 1); + new Query("Table").OrWhereNot("Id", op, 1); + + new Query("Table").WhereColumns("Col1", op, "Col2"); + new Query("Table").OrWhereColumns("Col1", op, "Col2"); + } + + [Theory] + [InlineData("=")] + [InlineData("!=")] + [InlineData("ilike")] + public void AllowValidOperatorsInHaving(string op) { + new Query("Table").Having("Id", op, 1); + new Query("Table").OrHaving("Id", op, 1); + new Query("Table").HavingNot("Id", op, 1); + new Query("Table").OrHavingNot("Id", op, 1); + + new Query("Table").HavingColumns("Col1", op, "Col2"); + new Query("Table").OrHavingColumns("Col1", op, "Col2"); + } + + [Theory] + [InlineData("^")] + [InlineData("<<")] + [InlineData(">>")] + [InlineData("~")] + [InlineData("~*")] + [InlineData("!~")] + [InlineData("!~*")] + public void ShouldNotThrowAfterWhiteListing(string op) { + var compiler = new SqlServerCompiler().Whitelist(op); + + var query = new Query("Table"); + + compiler.Compile(query.Clone().Where("Id", op, 1)); + compiler.Compile(query.Clone().OrWhere("Id", op, 1)); + compiler.Compile(query.Clone().WhereNot("Id", op, 1)); + compiler.Compile(query.Clone().OrWhereNot("Id", op, 1)); + + compiler.Compile(query.Clone().WhereColumns("Col1", op, "Col2")); + compiler.Compile(query.Clone().OrWhereColumns("Col1", op, "Col2")); + + compiler.Compile(query.Clone().Having("Id", op, 1)); + compiler.Compile(query.Clone().OrHaving("Id", op, 1)); + compiler.Compile(query.Clone().HavingNot("Id", op, 1)); + compiler.Compile(query.Clone().OrHavingNot("Id", op, 1)); + + compiler.Compile(query.Clone().HavingColumns("Col1", op, "Col2")); + compiler.Compile(query.Clone().OrHavingColumns("Col1", op, "Col2")); + } + + [Fact] + public void ShouldAllowWhiteListedOperatorsInNestedWhere() { + var compiler = new SqlServerCompiler().Whitelist("!!"); + + var query = new Query("Table") + .Where(q => q.Where("A", "!!", "value")); + + compiler.Compile(query); + } + + [Fact] + public void ShouldNotConsiderWhereRawCondition() { + var compiler = new SqlServerCompiler(); + + var query = new Query("Table") + .WhereRaw("Col !! value"); + + } + +} diff --git a/QueryBuilder.Tests/Oracle/OracleDateConditionTests.cs b/QueryBuilder.Tests/Oracle/OracleDateConditionTests.cs index 57a5605e..b667ad19 100644 --- a/QueryBuilder.Tests/Oracle/OracleDateConditionTests.cs +++ b/QueryBuilder.Tests/Oracle/OracleDateConditionTests.cs @@ -1,223 +1,208 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.Oracle -{ - public class OracleDateConditionTests : TestSupport - { - private const string TableName = "Table"; - private const string SqlPlaceholder = "GENERATED_SQL"; - - private readonly OracleCompiler compiler; - - public OracleDateConditionTests() - { - compiler = Compilers.Get(EngineCodes.Oracle); - } - - [Fact] - public void SimpleWhereDateTest() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDate("STAMP", "=", "2018-04-01"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'YY-MM-DD') = TO_CHAR(TO_DATE(?, 'YY-MM-DD'), 'YY-MM-DD')", ctx.RawSql); - Assert.Equal("2018-04-01", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartDateTest() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("date", "STAMP", "=", "2018-04-01"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'YY-MM-DD') = TO_CHAR(TO_DATE(?, 'YY-MM-DD'), 'YY-MM-DD')", ctx.RawSql); - Assert.Equal("2018-04-01", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereTimeWithSecondsTest() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereTime("STAMP", "=", "19:01:10"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI:SS'), 'HH24:MI:SS')", ctx.RawSql); - Assert.Equal("19:01:10", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartTimeWithSecondsTest() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("time", "STAMP", "=", "19:01:10"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI:SS'), 'HH24:MI:SS')", ctx.RawSql); - Assert.Equal("19:01:10", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereTimeWithoutSecondsTest() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereTime("STAMP", "=", "19:01"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI'), 'HH24:MI:SS')", ctx.RawSql); - Assert.Equal("19:01", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartTimeWithoutSecondsTest() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("time", "STAMP", "=", "19:01"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI'), 'HH24:MI:SS')", ctx.RawSql); - Assert.Equal("19:01", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartYear() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("year", "STAMP", "=", "2018"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(YEAR FROM \"STAMP\") = ?", ctx.RawSql); - Assert.Equal("2018", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartMonth() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("month", "STAMP", "=", "9"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(MONTH FROM \"STAMP\") = ?", ctx.RawSql); - Assert.Equal("9", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartDay() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("day", "STAMP", "=", "15"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(DAY FROM \"STAMP\") = ?", ctx.RawSql); - Assert.Equal("15", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartHour() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("hour", "STAMP", "=", "15"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(HOUR FROM \"STAMP\") = ?", ctx.RawSql); - Assert.Equal("15", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartMinute() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("minute", "STAMP", "=", "25"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(MINUTE FROM \"STAMP\") = ?", ctx.RawSql); - Assert.Equal("25", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void SimpleWhereDatePartSecond() - { - // Arrange: - var query = new Query(TableName) - .Select() - .WhereDatePart("second", "STAMP", "=", "59"); - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(SECOND FROM \"STAMP\") = ?", ctx.RawSql); - Assert.Equal("59", ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - } -} +namespace SqlKata.Tests.Oracle; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class OracleDateConditionTests : TestSupport { + private const string TableName = "Table"; + private const string SqlPlaceholder = "GENERATED_SQL"; + + private readonly OracleCompiler compiler; + + public OracleDateConditionTests() { + compiler = Compilers.Get(EngineCodes.Oracle); + } + + [Fact] + public void SimpleWhereDateTest() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDate("STAMP", "=", "2018-04-01"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'YY-MM-DD') = TO_CHAR(TO_DATE(?, 'YY-MM-DD'), 'YY-MM-DD')", ctx.RawSql); + Assert.Equal("2018-04-01", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartDateTest() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("date", "STAMP", "=", "2018-04-01"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'YY-MM-DD') = TO_CHAR(TO_DATE(?, 'YY-MM-DD'), 'YY-MM-DD')", ctx.RawSql); + Assert.Equal("2018-04-01", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereTimeWithSecondsTest() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereTime("STAMP", "=", "19:01:10"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI:SS'), 'HH24:MI:SS')", ctx.RawSql); + Assert.Equal("19:01:10", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartTimeWithSecondsTest() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("time", "STAMP", "=", "19:01:10"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI:SS'), 'HH24:MI:SS')", ctx.RawSql); + Assert.Equal("19:01:10", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereTimeWithoutSecondsTest() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereTime("STAMP", "=", "19:01"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI'), 'HH24:MI:SS')", ctx.RawSql); + Assert.Equal("19:01", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartTimeWithoutSecondsTest() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("time", "STAMP", "=", "19:01"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI'), 'HH24:MI:SS')", ctx.RawSql); + Assert.Equal("19:01", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartYear() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("year", "STAMP", "=", "2018"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(YEAR FROM \"STAMP\") = ?", ctx.RawSql); + Assert.Equal("2018", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartMonth() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("month", "STAMP", "=", "9"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(MONTH FROM \"STAMP\") = ?", ctx.RawSql); + Assert.Equal("9", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartDay() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("day", "STAMP", "=", "15"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(DAY FROM \"STAMP\") = ?", ctx.RawSql); + Assert.Equal("15", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartHour() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("hour", "STAMP", "=", "15"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(HOUR FROM \"STAMP\") = ?", ctx.RawSql); + Assert.Equal("15", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartMinute() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("minute", "STAMP", "=", "25"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(MINUTE FROM \"STAMP\") = ?", ctx.RawSql); + Assert.Equal("25", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void SimpleWhereDatePartSecond() { + // Arrange: + var query = new Query(TableName) + .Select() + .WhereDatePart("second", "STAMP", "=", "59"); + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(SECOND FROM \"STAMP\") = ?", ctx.RawSql); + Assert.Equal("59", ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } +} diff --git a/QueryBuilder.Tests/Oracle/OracleInsertManyTests.cs b/QueryBuilder.Tests/Oracle/OracleInsertManyTests.cs index f25cf2ba..b284994d 100644 --- a/QueryBuilder.Tests/Oracle/OracleInsertManyTests.cs +++ b/QueryBuilder.Tests/Oracle/OracleInsertManyTests.cs @@ -1,61 +1,56 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.Oracle -{ - public class OracleInsertManyTests : TestSupport - { - private const string TableName = "Table"; - private readonly OracleCompiler compiler; - - public OracleInsertManyTests() - { - compiler = Compilers.Get(EngineCodes.Oracle); - } - - [Fact] - public void InsertManyForOracle_ShouldRepeatColumnsAndAddSelectFromDual() - { - // Arrange: - var cols = new[] { "Name", "Price" }; - - var data = new[] { - new object[] { "A", 1000 }, - new object[] { "B", 2000 }, - new object[] { "C", 3000 }, - }; - - var query = new Query(TableName) - .AsInsert(cols, data); - - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($@"INSERT ALL INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) SELECT 1 FROM DUAL", ctx.RawSql); - } - - [Fact] - public void InsertForOracle_SingleInsertShouldNotAddALLKeywordAndNotHaveSelectFromDual() - { - // Arrange: - var cols = new[] { "Name", "Price" }; - - var data = new[] { - new object[] { "A", 1000 } - }; - - var query = new Query(TableName) - .AsInsert(cols, data); - - - // Act: - var ctx = compiler.Compile(query); - - // Assert: - Assert.Equal($@"INSERT INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?)", ctx.RawSql); - } - } -} +namespace SqlKata.Tests.Oracle; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class OracleInsertManyTests : TestSupport { + private const string TableName = "Table"; + private readonly OracleCompiler compiler; + + public OracleInsertManyTests() { + compiler = Compilers.Get(EngineCodes.Oracle); + } + + [Fact] + public void InsertManyForOracle_ShouldRepeatColumnsAndAddSelectFromDual() { + // Arrange: + var cols = new[] { "Name", "Price" }; + + var data = new[] { + new object[] { "A", 1000 }, + new object[] { "B", 2000 }, + new object[] { "C", 3000 }, + }; + + var query = new Query(TableName) + .AsInsert(cols, data); + + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($@"INSERT ALL INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) SELECT 1 FROM DUAL", ctx.RawSql); + } + + [Fact] + public void InsertForOracle_SingleInsertShouldNotAddALLKeywordAndNotHaveSelectFromDual() { + // Arrange: + var cols = new[] { "Name", "Price" }; + + var data = new[] { + new object[] { "A", 1000 } + }; + + var query = new Query(TableName) + .AsInsert(cols, data); + + + // Act: + var ctx = compiler.Compile(query); + + // Assert: + Assert.Equal($@"INSERT INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?)", ctx.RawSql); + } +} diff --git a/QueryBuilder.Tests/Oracle/OracleLegacyLimitTests.cs b/QueryBuilder.Tests/Oracle/OracleLegacyLimitTests.cs index 47a233dd..027ce174 100644 --- a/QueryBuilder.Tests/Oracle/OracleLegacyLimitTests.cs +++ b/QueryBuilder.Tests/Oracle/OracleLegacyLimitTests.cs @@ -1,82 +1,75 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.Oracle -{ - public class OracleLegacyLimitTests : TestSupport - { - private const string TableName = "Table"; - private const string SqlPlaceholder = "GENERATED_SQL"; - private readonly OracleCompiler compiler; - - public OracleLegacyLimitTests() - { - compiler = Compilers.Get(EngineCodes.Oracle); - compiler.UseLegacyPagination = true; - } - - [Fact] - public void WithNoLimitNorOffset() - { - // Arrange: - var query = new Query(TableName); - var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; - - // Act: - compiler.ApplyLegacyLimit(ctx); - - // Assert: - Assert.Equal(SqlPlaceholder, ctx.RawSql); - } - - [Fact] - public void WithNoOffset() - { - // Arrange: - var query = new Query(TableName).Limit(10); - var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; - - // Act: - compiler.ApplyLegacyLimit(ctx); - - // Assert: - Assert.Matches($"SELECT \\* FROM \\({SqlPlaceholder}\\) WHERE ROWNUM <= ?", ctx.RawSql); - Assert.Equal(10, ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void WithNoLimit() - { - // Arrange: - var query = new Query(TableName).Offset(20); - var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; - - // Act: - compiler.ApplyLegacyLimit(ctx); - - // Assert: - Assert.Equal("SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM (GENERATED_SQL) \"results_wrapper\") WHERE \"row_num\" > ?", ctx.RawSql); - Assert.Equal(20L, ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void WithLimitAndOffset() - { - // Arrange: - var query = new Query(TableName).Limit(5).Offset(20); - var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; - - // Act: - compiler.ApplyLegacyLimit(ctx); - - // Assert: - Assert.Equal("SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM (GENERATED_SQL) \"results_wrapper\" WHERE ROWNUM <= ?) WHERE \"row_num\" > ?", ctx.RawSql); - Assert.Equal(25L, ctx.Bindings[0]); - Assert.Equal(20L, ctx.Bindings[1]); - Assert.Equal(2, ctx.Bindings.Count); - } - } -} +namespace SqlKata.Tests.Oracle; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class OracleLegacyLimitTests : TestSupport { + private const string TableName = "Table"; + private const string SqlPlaceholder = "GENERATED_SQL"; + private readonly OracleCompiler compiler; + + public OracleLegacyLimitTests() { + compiler = Compilers.Get(EngineCodes.Oracle); + compiler.UseLegacyPagination = true; + } + + [Fact] + public void WithNoLimitNorOffset() { + // Arrange: + var query = new Query(TableName); + var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; + + // Act: + compiler.ApplyLegacyLimit(ctx); + + // Assert: + Assert.Equal(SqlPlaceholder, ctx.RawSql); + } + + [Fact] + public void WithNoOffset() { + // Arrange: + var query = new Query(TableName).Limit(10); + var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; + + // Act: + compiler.ApplyLegacyLimit(ctx); + + // Assert: + Assert.Matches($"SELECT \\* FROM \\({SqlPlaceholder}\\) WHERE ROWNUM <= ?", ctx.RawSql); + Assert.Equal(10, ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void WithNoLimit() { + // Arrange: + var query = new Query(TableName).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; + + // Act: + compiler.ApplyLegacyLimit(ctx); + + // Assert: + Assert.Equal("SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM (GENERATED_SQL) \"results_wrapper\") WHERE \"row_num\" > ?", ctx.RawSql); + Assert.Equal(20L, ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void WithLimitAndOffset() { + // Arrange: + var query = new Query(TableName).Limit(5).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; + + // Act: + compiler.ApplyLegacyLimit(ctx); + + // Assert: + Assert.Equal("SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM (GENERATED_SQL) \"results_wrapper\" WHERE ROWNUM <= ?) WHERE \"row_num\" > ?", ctx.RawSql); + Assert.Equal(25L, ctx.Bindings[0]); + Assert.Equal(20L, ctx.Bindings[1]); + Assert.Equal(2, ctx.Bindings.Count); + } +} diff --git a/QueryBuilder.Tests/Oracle/OracleLimitTests.cs b/QueryBuilder.Tests/Oracle/OracleLimitTests.cs index 9701705c..c98b90cc 100644 --- a/QueryBuilder.Tests/Oracle/OracleLimitTests.cs +++ b/QueryBuilder.Tests/Oracle/OracleLimitTests.cs @@ -1,75 +1,68 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.Oracle -{ - public class OracleLimitTests : TestSupport - { - private const string TableName = "Table"; - private const string SqlPlaceholder = "GENERATED_SQL"; - - private readonly OracleCompiler compiler; - - public OracleLimitTests() - { - compiler = Compilers.Get(EngineCodes.Oracle); - } - - [Fact] - public void NoLimitNorOffset() - { - // Arrange: - var query = new Query(TableName); - var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; - - // Act & Assert: - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void LimitOnly() - { - // Arrange: - var query = new Query(TableName).Limit(10); - var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; - - // Act & Assert: - Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); - Assert.Equal(2, ctx.Bindings.Count); - Assert.Equal(0L, ctx.Bindings[0]); - Assert.Equal(10, ctx.Bindings[1]); - } - - [Fact] - public void OffsetOnly() - { - // Arrange: - var query = new Query(TableName).Offset(20); - var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; - - // Act & Assert: - Assert.EndsWith("OFFSET ? ROWS", compiler.CompileLimit(ctx)); - - Assert.Single(ctx.Bindings); - Assert.Equal(20L, ctx.Bindings[0]); - } - - [Fact] - public void LimitAndOffset() - { - // Arrange: - var query = new Query(TableName).Limit(5).Offset(20); - var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; - - // Act & Assert: - Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); - - Assert.Equal(2, ctx.Bindings.Count); - Assert.Equal(20L, ctx.Bindings[0]); - Assert.Equal(5, ctx.Bindings[1]); - - compiler.CompileLimit(ctx); - } - } -} +namespace SqlKata.Tests.Oracle; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class OracleLimitTests : TestSupport { + private const string TableName = "Table"; + private const string SqlPlaceholder = "GENERATED_SQL"; + + private readonly OracleCompiler compiler; + + public OracleLimitTests() { + compiler = Compilers.Get(EngineCodes.Oracle); + } + + [Fact] + public void NoLimitNorOffset() { + // Arrange: + var query = new Query(TableName); + var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; + + // Act & Assert: + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void LimitOnly() { + // Arrange: + var query = new Query(TableName).Limit(10); + var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; + + // Act & Assert: + Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); + Assert.Equal(2, ctx.Bindings.Count); + Assert.Equal(0L, ctx.Bindings[0]); + Assert.Equal(10, ctx.Bindings[1]); + } + + [Fact] + public void OffsetOnly() { + // Arrange: + var query = new Query(TableName).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; + + // Act & Assert: + Assert.EndsWith("OFFSET ? ROWS", compiler.CompileLimit(ctx)); + + Assert.Single(ctx.Bindings); + Assert.Equal(20L, ctx.Bindings[0]); + } + + [Fact] + public void LimitAndOffset() { + // Arrange: + var query = new Query(TableName).Limit(5).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; + + // Act & Assert: + Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); + + Assert.Equal(2, ctx.Bindings.Count); + Assert.Equal(20L, ctx.Bindings[0]); + Assert.Equal(5, ctx.Bindings[1]); + + compiler.CompileLimit(ctx); + } +} diff --git a/QueryBuilder.Tests/ParameterTypeTests.cs b/QueryBuilder.Tests/ParameterTypeTests.cs index 095a6e53..7088febe 100644 --- a/QueryBuilder.Tests/ParameterTypeTests.cs +++ b/QueryBuilder.Tests/ParameterTypeTests.cs @@ -1,52 +1,47 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using SqlKata.Compilers; -using Xunit; -using System.Collections; -using SqlKata.Tests.Infrastructure; - -namespace SqlKata.Tests -{ - public class ParameterTypeTests : TestSupport - { - public enum EnumExample - { - First, - Second, - Third, - } - - public class ParameterTypeGenerator : IEnumerable - { - private readonly List _data = new List - { - new object[] {"1", 1}, - new object[] {Convert.ToSingle("10.5", CultureInfo.InvariantCulture).ToString(), 10.5}, - new object[] {"-2", -2}, - new object[] {Convert.ToSingle("-2.8", CultureInfo.InvariantCulture).ToString(), -2.8}, - new object[] {"cast(1 as bit)", true}, - new object[] {"cast(0 as bit)", false}, - new object[] {"'2018-10-28 19:22:00'", new DateTime(2018, 10, 28, 19, 22, 0)}, - new object[] {"0 /* First */", EnumExample.First}, - new object[] {"1 /* Second */", EnumExample.Second}, - new object[] {"'a string'", "a string"}, - }; - - public IEnumerator GetEnumerator() => _data.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - [Theory] - [ClassData(typeof(ParameterTypeGenerator))] - public void CorrectParameterTypeOutput(string rendered, object input) - { - var query = new Query("Table").Where("Col", input); - - var c = Compile(query); - - Assert.Equal($"SELECT * FROM [Table] WHERE [Col] = {rendered}", c[EngineCodes.SqlServer]); - } - } -} +namespace SqlKata.Tests; + +using System; +using System.Collections.Generic; +using System.Globalization; +using SqlKata.Compilers; +using Xunit; +using System.Collections; +using SqlKata.Tests.Infrastructure; + +public class ParameterTypeTests : TestSupport { + public enum EnumExample { + First, + Second, + Third, + } + + public class ParameterTypeGenerator : IEnumerable { + private readonly List _data = new List + { + new object[] {"1", 1}, + new object[] {Convert.ToSingle("10.5", CultureInfo.InvariantCulture).ToString(), 10.5}, + new object[] {"-2", -2}, + new object[] {Convert.ToSingle("-2.8", CultureInfo.InvariantCulture).ToString(), -2.8}, + new object[] {"cast(1 as bit)", true}, + new object[] {"cast(0 as bit)", false}, + new object[] {"'2018-10-28 19:22:00'", new DateTime(2018, 10, 28, 19, 22, 0)}, + new object[] {"0 /* First */", EnumExample.First}, + new object[] {"1 /* Second */", EnumExample.Second}, + new object[] {"'a string'", "a string"}, + }; + + public IEnumerator GetEnumerator() => _data.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + [Theory] + [ClassData(typeof(ParameterTypeGenerator))] + public void CorrectParameterTypeOutput(string rendered, object input) { + var query = new Query("Table").Where("Col", input); + + var c = Compile(query); + + Assert.Equal($"SELECT * FROM [Table] WHERE [Col] = {rendered}", c[EngineCodes.SqlServer]); + } +} diff --git a/QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs b/QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs index 169fb99d..61f84add 100644 --- a/QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs +++ b/QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs @@ -1,58 +1,51 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.PostgreSql -{ - public class PostgreSqlLimitTests : TestSupport - { - private readonly PostgresCompiler compiler; - - public PostgreSqlLimitTests() - { - compiler = Compilers.Get(EngineCodes.PostgreSql); - } - - [Fact] - public void WithNoLimitNorOffset() - { - var query = new Query("Table"); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void WithNoOffset() - { - var query = new Query("Table").Limit(10); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); - Assert.Equal(10, ctx.Bindings[0]); - } - - [Fact] - public void WithNoLimit() - { - var query = new Query("Table").Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Equal("OFFSET ?", compiler.CompileLimit(ctx)); - Assert.Equal(20L, ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void WithLimitAndOffset() - { - var query = new Query("Table").Limit(5).Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); - Assert.Equal(5, ctx.Bindings[0]); - Assert.Equal(20L, ctx.Bindings[1]); - Assert.Equal(2, ctx.Bindings.Count); - } - } -} +namespace SqlKata.Tests.PostgreSql; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class PostgreSqlLimitTests : TestSupport { + private readonly PostgresCompiler compiler; + + public PostgreSqlLimitTests() { + compiler = Compilers.Get(EngineCodes.PostgreSql); + } + + [Fact] + public void WithNoLimitNorOffset() { + var query = new Query("Table"); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void WithNoOffset() { + var query = new Query("Table").Limit(10); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); + Assert.Equal(10, ctx.Bindings[0]); + } + + [Fact] + public void WithNoLimit() { + var query = new Query("Table").Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("OFFSET ?", compiler.CompileLimit(ctx)); + Assert.Equal(20L, ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void WithLimitAndOffset() { + var query = new Query("Table").Limit(5).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); + Assert.Equal(5, ctx.Bindings[0]); + Assert.Equal(20L, ctx.Bindings[1]); + Assert.Equal(2, ctx.Bindings.Count); + } +} diff --git a/QueryBuilder.Tests/QueryFactoryExtension.cs b/QueryBuilder.Tests/QueryFactoryExtension.cs index 9f87bfce..eb4e4e14 100644 --- a/QueryBuilder.Tests/QueryFactoryExtension.cs +++ b/QueryBuilder.Tests/QueryFactoryExtension.cs @@ -1,19 +1,16 @@ - -using System.Collections.Generic; -using SqlKata.Execution; - -static class QueryFactoryExtensions -{ - public static QueryFactory Create(this QueryFactory db, string table, IEnumerable cols) - { - db.Drop(table); - db.Statement($"CREATE TABLE `{table}`({string.Join(", ", cols)});"); - return db; - } - - public static QueryFactory Drop(this QueryFactory db, string table) - { - db.Statement($"DROP TABLE IF EXISTS `{table}`;"); - return db; - } -} \ No newline at end of file + +using System.Collections.Generic; +using SqlKata.Execution; + +static class QueryFactoryExtensions { + public static QueryFactory Create(this QueryFactory db, string table, IEnumerable cols) { + db.Drop(table); + db.Statement($"CREATE TABLE `{table}`({string.Join(", ", cols)});"); + return db; + } + + public static QueryFactory Drop(this QueryFactory db, string table) { + db.Statement($"DROP TABLE IF EXISTS `{table}`;"); + return db; + } +} diff --git a/QueryBuilder.Tests/SQLiteExecutionTest.cs b/QueryBuilder.Tests/SQLiteExecutionTest.cs index 8c4d43f5..3d9ca81b 100644 --- a/QueryBuilder.Tests/SQLiteExecutionTest.cs +++ b/QueryBuilder.Tests/SQLiteExecutionTest.cs @@ -1,275 +1,254 @@ -using SqlKata.Compilers; -using Xunit; -using SqlKata.Execution; -using MySql.Data.MySqlClient; -using System; -using System.Linq; -using static SqlKata.Expressions; -using System.Collections.Generic; -using Microsoft.Data.Sqlite; - -namespace SqlKata.Tests -{ - public class SqliteExecutionTest - { - [Fact] - public void EmptySelect() - { - - var db = DB().Create("Cars", new[] { - "Id INTEGER PRIMARY KEY AUTOINCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - - var tables = db.Select(@"SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%'"); - - var rows = db.Query("Cars").Get(); - - Assert.Empty(rows); - - db.Drop("Cars"); - } - - [Fact] - public void SelectWithLimit() - { - var db = DB().Create("Cars", new[] { - "Id INTEGER PRIMARY KEY AUTOINCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); - - var rows = db.Query("Cars").Get().ToList(); - - Assert.Single(rows); - - db.Drop("Cars"); - } - - [Fact] - public void InsertGetId() - { - var db = DB().Create("Cars", new[] { - "Id INTEGER PRIMARY KEY AUTOINCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - }); - - var id = db.Query("Cars").InsertGetId(new - { - Brand = "Toyota", - Year = 1900 - }); - - Assert.Equal(1, id); - - id = db.Query("Cars").InsertGetId(new - { - Brand = "Toyota 2", - Year = 1901 - }); - - Assert.Equal(2, id); - - id = db.Query("Cars").InsertGetId(new - { - Brand = "Toyota 2", - Year = 1901 - }); - - Assert.Equal(3, id); - - - db.Drop("Cars"); - } - - - - [Fact] - public void Count() - { - var db = DB().Create("Cars", new[] { - "Id INTEGER PRIMARY KEY AUTOINCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); - var count = db.Query("Cars").Count(); - Assert.Equal(1, count); - - db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Toyota', 2021)"); - count = db.Query("Cars").Count(); - Assert.Equal(2, count); - - int affected = db.Query("Cars").Delete(); - Assert.Equal(2, affected); - - count = db.Query("Cars").Count(); - Assert.Equal(0, count); - - db.Drop("Cars"); - } - - [Fact] - public void CloneThenCount() - { - var db = DB().Create("Cars", new[] { - "Id INTEGER PRIMARY KEY AUTOINCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - for (int i = 0; i < 10; i++) - { - db.Query("Cars").Insert(new - { - Brand = "Brand " + i, - Year = "2020", - }); - } - - var query = db.Query("Cars").Where("Id", "<", 5); - var count = query.Count(); - var cloneCount = query.Clone().Count(); - - Assert.Equal(4, count); - Assert.Equal(4, cloneCount); - - db.Drop("Cars"); - } - - [Fact] - public void QueryWithVariable() - { - var db = DB().Create("Cars", new[] { - "Id INTEGER PRIMARY KEY AUTOINCREMENT", - "Brand TEXT NOT NULL", - "Year INT NOT NULL", - "Color TEXT NULL", - }); - - for (int i = 0; i < 10; i++) - { - db.Query("Cars").Insert(new - { - Brand = "Brand " + i, - Year = "2020", - }); - } - - - var count = db.Query("Cars") - .Define("Threshold", 5) - .Where("Id", "<", SqlKata.Expressions.Variable("Threshold")) - .Count(); - - Assert.Equal(4, count); - - db.Drop("Cars"); - } - - [Fact] - public void InlineTable() - { - var db = DB().Create("Transaction", new[] { - "Id INTEGER PRIMARY KEY AUTOINCREMENT", - "Amount int NOT NULL", - "Date DATE NOT NULL", - }); - - db.Query("Transaction").Insert(new - { - Date = "2022-01-01", - Amount = 10 - }); - - - var rows = db.Query("Transaction") - .With("Rates", new[] { "Date", "Rate" }, new object[][] { - new object[] {"2022-01-01", 0.5}, - }) - .Join("Rates", "Rates.Date", "Transaction.Date") - .SelectRaw("([Transaction].[Amount] * [Rates].[Rate]) as AmountConverted") - .Get(); - - Assert.Single(rows); - Assert.Equal(5, rows.First().AmountConverted); - - db.Drop("Transaction"); - } - - - - [Fact] - public void BasicSelectFilter() - { - var db = DB().Create("Transaction", new[] { - "Id INTEGER PRIMARY KEY AUTOINCREMENT", - "Date DATE NOT NULL", - "Amount int NOT NULL", - }); - - var data = new Dictionary { - // 2020 - {"2020-01-01", 10}, - {"2020-05-01", 20}, - - // 2021 - {"2021-01-01", 40}, - {"2021-02-01", 10}, - {"2021-04-01", -10}, - - // 2022 - {"2022-01-01", 80}, - {"2022-02-01", -30}, - {"2022-05-01", 50}, - }; - - foreach (var row in data) - { - db.Query("Transaction").Insert(new - { - Date = row.Key, - Amount = row.Value - }); - } - - var query = db.Query("Transaction") - .SelectSum("Amount as Total_2020", q => q.WhereDatePart("year", "date", 2020)) - .SelectSum("Amount as Total_2021", q => q.WhereDatePart("year", "date", 2021)) - .SelectSum("Amount as Total_2022", q => q.WhereDatePart("year", "date", 2022)) - ; - - var results = query.Get().ToList(); - Assert.Single(results); - Assert.Equal(30, results[0].Total_2020); - Assert.Equal(40, results[0].Total_2021); - Assert.Equal(100, results[0].Total_2022); - - db.Drop("Transaction"); - } - - QueryFactory DB() - { - var cs = $"Data Source=file::memory:;Cache=Shared"; - - var connection = new SqliteConnection(cs); - - var db = new QueryFactory(connection, new SqliteCompiler()); - - return db; - } - - - - } -} \ No newline at end of file +namespace SqlKata.Tests; + +using SqlKata.Compilers; +using Xunit; +using SqlKata.Execution; +using global::MySql.Data.MySqlClient; +using System; +using System.Linq; +using static SqlKata.Expressions; +using System.Collections.Generic; +using Microsoft.Data.Sqlite; + +public class SqliteExecutionTest { + [Fact] + public void EmptySelect() { + + var db = DB().Create("Cars", new[] { + "Id INTEGER PRIMARY KEY AUTOINCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + + var tables = db.Select(@"SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%'"); + + var rows = db.Query("Cars").Get(); + + Assert.Empty(rows); + + db.Drop("Cars"); + } + + [Fact] + public void SelectWithLimit() { + var db = DB().Create("Cars", new[] { + "Id INTEGER PRIMARY KEY AUTOINCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); + + var rows = db.Query("Cars").Get().ToList(); + + Assert.Single(rows); + + db.Drop("Cars"); + } + + [Fact] + public void InsertGetId() { + var db = DB().Create("Cars", new[] { + "Id INTEGER PRIMARY KEY AUTOINCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + }); + + var id = db.Query("Cars").InsertGetId(new { + Brand = "Toyota", + Year = 1900 + }); + + Assert.Equal(1, id); + + id = db.Query("Cars").InsertGetId(new { + Brand = "Toyota 2", + Year = 1901 + }); + + Assert.Equal(2, id); + + id = db.Query("Cars").InsertGetId(new { + Brand = "Toyota 2", + Year = 1901 + }); + + Assert.Equal(3, id); + + + db.Drop("Cars"); + } + + + + [Fact] + public void Count() { + var db = DB().Create("Cars", new[] { + "Id INTEGER PRIMARY KEY AUTOINCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); + var count = db.Query("Cars").Count(); + Assert.Equal(1, count); + + db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Toyota', 2021)"); + count = db.Query("Cars").Count(); + Assert.Equal(2, count); + + int affected = db.Query("Cars").Delete(); + Assert.Equal(2, affected); + + count = db.Query("Cars").Count(); + Assert.Equal(0, count); + + db.Drop("Cars"); + } + + [Fact] + public void CloneThenCount() { + var db = DB().Create("Cars", new[] { + "Id INTEGER PRIMARY KEY AUTOINCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + for (int i = 0; i < 10; i++) { + db.Query("Cars").Insert(new { + Brand = "Brand " + i, + Year = "2020", + }); + } + + var query = db.Query("Cars").Where("Id", "<", 5); + var count = query.Count(); + var cloneCount = query.Clone().Count(); + + Assert.Equal(4, count); + Assert.Equal(4, cloneCount); + + db.Drop("Cars"); + } + + [Fact] + public void QueryWithVariable() { + var db = DB().Create("Cars", new[] { + "Id INTEGER PRIMARY KEY AUTOINCREMENT", + "Brand TEXT NOT NULL", + "Year INT NOT NULL", + "Color TEXT NULL", + }); + + for (int i = 0; i < 10; i++) { + db.Query("Cars").Insert(new { + Brand = "Brand " + i, + Year = "2020", + }); + } + + + var count = db.Query("Cars") + .Define("Threshold", 5) + .Where("Id", "<", SqlKata.Expressions.Variable("Threshold")) + .Count(); + + Assert.Equal(4, count); + + db.Drop("Cars"); + } + + [Fact] + public void InlineTable() { + var db = DB().Create("Transaction", new[] { + "Id INTEGER PRIMARY KEY AUTOINCREMENT", + "Amount int NOT NULL", + "Date DATE NOT NULL", + }); + + db.Query("Transaction").Insert(new { + Date = "2022-01-01", + Amount = 10 + }); + + + var rows = db.Query("Transaction") + .With("Rates", new[] { "Date", "Rate" }, new object[][] { + new object[] {"2022-01-01", 0.5}, + }) + .Join("Rates", "Rates.Date", "Transaction.Date") + .SelectRaw("([Transaction].[Amount] * [Rates].[Rate]) as AmountConverted") + .Get(); + + Assert.Single(rows); + Assert.Equal(5, rows.First().AmountConverted); + + db.Drop("Transaction"); + } + + + + [Fact] + public void BasicSelectFilter() { + var db = DB().Create("Transaction", new[] { + "Id INTEGER PRIMARY KEY AUTOINCREMENT", + "Date DATE NOT NULL", + "Amount int NOT NULL", + }); + + var data = new Dictionary { + // 2020 + {"2020-01-01", 10}, + {"2020-05-01", 20}, + + // 2021 + {"2021-01-01", 40}, + {"2021-02-01", 10}, + {"2021-04-01", -10}, + + // 2022 + {"2022-01-01", 80}, + {"2022-02-01", -30}, + {"2022-05-01", 50}, + }; + + foreach (var row in data) { + db.Query("Transaction").Insert(new { + Date = row.Key, + Amount = row.Value + }); + } + + var query = db.Query("Transaction") + .SelectSum("Amount as Total_2020", q => q.WhereDatePart("year", "date", 2020)) + .SelectSum("Amount as Total_2021", q => q.WhereDatePart("year", "date", 2021)) + .SelectSum("Amount as Total_2022", q => q.WhereDatePart("year", "date", 2022)) + ; + + var results = query.Get().ToList(); + Assert.Single(results); + Assert.Equal(30, results[0].Total_2020); + Assert.Equal(40, results[0].Total_2021); + Assert.Equal(100, results[0].Total_2022); + + db.Drop("Transaction"); + } + + QueryFactory DB() { + var cs = $"Data Source=file::memory:;Cache=Shared"; + + var connection = new SqliteConnection(cs); + + var db = new QueryFactory(connection, new SqliteCompiler()); + + return db; + } + + + +} diff --git a/QueryBuilder.Tests/SelectTests.cs b/QueryBuilder.Tests/SelectTests.cs index 647187b9..b548b7d3 100644 --- a/QueryBuilder.Tests/SelectTests.cs +++ b/QueryBuilder.Tests/SelectTests.cs @@ -1,962 +1,892 @@ -using SqlKata.Compilers; -using SqlKata.Extensions; -using SqlKata.Tests.Infrastructure; -using System; -using System.Collections.Generic; -using Xunit; - -namespace SqlKata.Tests -{ - public class SelectTests : TestSupport - { - [Fact] - public void BasicSelect() - { - var q = new Query().From("users").Select("id", "name"); - var c = Compile(q); - - Assert.Equal("SELECT [id], [name] FROM [users]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT `id`, `name` FROM `users`", c[EngineCodes.MySql]); - Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"USERS\"", c[EngineCodes.Firebird]); - Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.Oracle]); - } - - [Fact] - public void BasicSelectEnumerable() - { - var q = new Query().From("users").Select(new List() { "id", "name" }); - var c = Compile(q); - - Assert.Equal("SELECT [id], [name] FROM [users]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT `id`, `name` FROM `users`", c[EngineCodes.MySql]); - Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"USERS\"", c[EngineCodes.Firebird]); - Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.Oracle]); - } - - [Fact] - public void BasicSelectWhereBindingIsEmptyOrNull() - { - var q = new Query() - .From("users") - .Select("id", "name") - .Where("author", "") - .OrWhere("author", null); - - var c = Compile(q); - - Assert.Equal("SELECT [id], [name] FROM [users] WHERE [author] = '' OR [author] IS NULL", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT `id`, `name` FROM `users` WHERE `author` = '' OR `author` IS NULL", c[EngineCodes.MySql]); - Assert.Equal("SELECT \"id\", \"name\" FROM \"users\" WHERE \"author\" = '' OR \"author\" IS NULL", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"USERS\" WHERE \"AUTHOR\" = '' OR \"AUTHOR\" IS NULL", c[EngineCodes.Firebird]); - } - - [Fact] - public void BasicSelectWithAlias() - { - var q = new Query().From("users as u").Select("id", "name"); - var c = Compile(q); - - Assert.Equal("SELECT [id], [name] FROM [users] AS [u]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT `id`, `name` FROM `users` AS `u`", c[EngineCodes.MySql]); - Assert.Equal("SELECT \"id\", \"name\" FROM \"users\" AS \"u\"", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"USERS\" AS \"U\"", c[EngineCodes.Firebird]); - } - - [Fact] - public void ExpandedSelect() - { - var q = new Query().From("users").Select("users.{id,name, age}"); - var c = Compile(q); - - Assert.Equal("SELECT [users].[id], [users].[name], [users].[age] FROM [users]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT `users`.`id`, `users`.`name`, `users`.`age` FROM `users`", c[EngineCodes.MySql]); - } - - [Fact] - public void ExpandedSelectMultiline() - { - var q = new Query().From("users").Select(@"users.{ - id, - name as Name, - age - }"); - var c = Compile(q); - - Assert.Equal("SELECT [users].[id], [users].[name] AS [Name], [users].[age] FROM [users]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT `users`.`id`, `users`.`name` AS `Name`, `users`.`age` FROM `users`", c[EngineCodes.MySql]); - } - - [Fact] - public void ExpandedSelectWithSchema() - { - var q = new Query().From("users").Select("dbo.users.{id,name, age}"); - var c = Compile(q); - - Assert.Equal("SELECT [dbo].[users].[id], [dbo].[users].[name], [dbo].[users].[age] FROM [users]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void ExpandedSelectMultilineWithSchema() - { - var q = new Query().From("users").Select(@"dbo.users.{ - id, - name as Name, - age - }"); - var c = Compile(q); - - Assert.Equal("SELECT [dbo].[users].[id], [dbo].[users].[name] AS [Name], [dbo].[users].[age] FROM [users]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void NestedEmptyWhereAtFirstCondition() - { - var query = new Query("table") - .Where(q => new Query()) - .Where("id", 1); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [table] WHERE [id] = 1", c[EngineCodes.SqlServer]); - - - Assert.Equal("SELECT * FROM \"TABLE\" WHERE \"ID\" = 1", c[EngineCodes.Firebird]); - } - - [Fact] - public void WhereTrue() - { - var query = new Query("Table").WhereTrue("IsActive"); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Table] WHERE [IsActive] = cast(1 as bit)", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM `Table` WHERE `IsActive` = true", c[EngineCodes.MySql]); - Assert.Equal("SELECT * FROM \"Table\" WHERE \"IsActive\" = true", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT * FROM \"TABLE\" WHERE \"ISACTIVE\" = 1", c[EngineCodes.Firebird]); - } - - [Fact] - public void WhereFalse() - { - var query = new Query("Table").WhereFalse("IsActive"); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Table] WHERE [IsActive] = cast(0 as bit)", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM `Table` WHERE `IsActive` = false", c[EngineCodes.MySql]); - Assert.Equal("SELECT * FROM \"Table\" WHERE \"IsActive\" = false", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT * FROM \"TABLE\" WHERE \"ISACTIVE\" = 0", c[EngineCodes.Firebird]); - } - - [Fact] - public void OrWhereFalse() - { - var query = new Query("Table").Where("MyCol", "abc").OrWhereFalse("IsActive"); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Table] WHERE [MyCol] = 'abc' OR [IsActive] = cast(0 as bit)", c[EngineCodes.SqlServer]); - - Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" = 'abc' OR \"IsActive\" = false", c[EngineCodes.PostgreSql]); - - } - - [Fact] - public void OrWhereTrue() - { - var query = new Query("Table").Where("MyCol", "abc").OrWhereTrue("IsActive"); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Table] WHERE [MyCol] = 'abc' OR [IsActive] = cast(1 as bit)", c[EngineCodes.SqlServer]); - - Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" = 'abc' OR \"IsActive\" = true", c[EngineCodes.PostgreSql]); - - } - - [Fact] - public void OrWhereNull() - { - var query = new Query("Table").Where("MyCol", "abc").OrWhereNull("IsActive"); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Table] WHERE [MyCol] = 'abc' OR [IsActive] IS NULL", c[EngineCodes.SqlServer]); - - Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" = 'abc' OR \"IsActive\" IS NULL", c[EngineCodes.PostgreSql]); - } - - [Fact] - public void WhereSub() - { - var subQuery = new Query("Table2").WhereColumns("Table2.Column", "=", "Table.MyCol").AsCount(); - - var query = new Query("Table").WhereSub(subQuery, 1); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Table] WHERE (SELECT COUNT(*) AS [count] FROM [Table2] WHERE [Table2].[Column] = [Table].[MyCol]) = 1", c[EngineCodes.SqlServer]); - - Assert.Equal("SELECT * FROM \"Table\" WHERE (SELECT COUNT(*) AS \"count\" FROM \"Table2\" WHERE \"Table2\".\"Column\" = \"Table\".\"MyCol\") = 1", c[EngineCodes.PostgreSql]); - } - - [Fact] - public void OrWhereSub() - { - var subQuery = new Query("Table2").WhereColumns("Table2.Column", "=", "Table.MyCol").AsCount(); - - var query = new Query("Table").WhereNull("MyCol").OrWhereSub(subQuery, "<", 1); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Table] WHERE [MyCol] IS NULL OR (SELECT COUNT(*) AS [count] FROM [Table2] WHERE [Table2].[Column] = [Table].[MyCol]) < 1", c[EngineCodes.SqlServer]); - - Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" IS NULL OR (SELECT COUNT(*) AS \"count\" FROM \"Table2\" WHERE \"Table2\".\"Column\" = \"Table\".\"MyCol\") < 1", c[EngineCodes.PostgreSql]); - } - - [Fact] - public void PassingArrayAsParameter() - { - var query = new Query("Table").WhereRaw("[Id] in (?)", new object[] { new object[] { 1, 2, 3 } }); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Table] WHERE [Id] in (1,2,3)", c[EngineCodes.SqlServer]); - } - - [Fact] - public void UsingJsonArray() - { - var query = new Query("Table").WhereRaw("[Json]->'address'->>'country' in (?)", new[] { 1, 2, 3, 4 }); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM \"Table\" WHERE \"Json\"->'address'->>'country' in (1,2,3,4)", c[EngineCodes.PostgreSql]); - } - - [Fact] - public void Union() - { - var laptops = new Query("Laptops"); - var mobiles = new Query("Phones").Union(laptops); - - var c = Compile(mobiles); - - Assert.Equal("SELECT * FROM [Phones] UNION SELECT * FROM [Laptops]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM \"Phones\" UNION SELECT * FROM \"Laptops\"", c[EngineCodes.Sqlite]); - Assert.Equal("SELECT * FROM \"PHONES\" UNION SELECT * FROM \"LAPTOPS\"", c[EngineCodes.Firebird]); - } - - - [Fact] - public void UnionWithBindings() - { - var laptops = new Query("Laptops").Where("Type", "A"); - var mobiles = new Query("Phones").Union(laptops); - - var c = Compile(mobiles); - - Assert.Equal("SELECT * FROM [Phones] UNION SELECT * FROM [Laptops] WHERE [Type] = 'A'", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM \"Phones\" UNION SELECT * FROM \"Laptops\" WHERE \"Type\" = 'A'", c[EngineCodes.Sqlite]); - Assert.Equal("SELECT * FROM `Phones` UNION SELECT * FROM `Laptops` WHERE `Type` = 'A'", c[EngineCodes.MySql]); - Assert.Equal("SELECT * FROM \"PHONES\" UNION SELECT * FROM \"LAPTOPS\" WHERE \"TYPE\" = 'A'", c[EngineCodes.Firebird]); - } - - [Fact] - public void RawUnionWithBindings() - { - var mobiles = new Query("Phones").UnionRaw("UNION SELECT * FROM [Laptops] WHERE [Type] = ?", "A"); - - var c = Compile(mobiles); - - Assert.Equal("SELECT * FROM [Phones] UNION SELECT * FROM [Laptops] WHERE [Type] = 'A'", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM `Phones` UNION SELECT * FROM `Laptops` WHERE `Type` = 'A'", c[EngineCodes.MySql]); - } - - [Fact] - public void MultipleUnion() - { - var laptops = new Query("Laptops"); - var tablets = new Query("Tablets"); - - var mobiles = new Query("Phones").Union(laptops).Union(tablets); - - var c = Compile(mobiles); - - Assert.Equal("SELECT * FROM [Phones] UNION SELECT * FROM [Laptops] UNION SELECT * FROM [Tablets]", - c[EngineCodes.SqlServer]); - - - Assert.Equal("SELECT * FROM \"PHONES\" UNION SELECT * FROM \"LAPTOPS\" UNION SELECT * FROM \"TABLETS\"", - c[EngineCodes.Firebird]); - } - - [Fact] - public void MultipleUnionWithBindings() - { - var laptops = new Query("Laptops").Where("Price", ">", 1000); - var tablets = new Query("Tablets").Where("Price", ">", 2000); - - var mobiles = new Query("Phones").Where("Price", "<", 3000).Union(laptops).Union(tablets); - - var c = Compile(mobiles); - - Assert.Equal( - "SELECT * FROM [Phones] WHERE [Price] < 3000 UNION SELECT * FROM [Laptops] WHERE [Price] > 1000 UNION SELECT * FROM [Tablets] WHERE [Price] > 2000", - c[EngineCodes.SqlServer]); - - - Assert.Equal( - "SELECT * FROM \"PHONES\" WHERE \"PRICE\" < 3000 UNION SELECT * FROM \"LAPTOPS\" WHERE \"PRICE\" > 1000 UNION SELECT * FROM \"TABLETS\" WHERE \"PRICE\" > 2000", - c[EngineCodes.Firebird]); - } - - [Fact] - public void MultipleUnionWithBindingsAndPagination() - { - var laptops = new Query("Laptops").Where("Price", ">", 1000); - var tablets = new Query("Tablets").Where("Price", ">", 2000).ForPage(2); - - var mobiles = new Query("Phones").Where("Price", "<", 3000).Union(laptops).UnionAll(tablets); - - - var c = Compile(mobiles); - - Assert.Equal( - "SELECT * FROM [Phones] WHERE [Price] < 3000 UNION SELECT * FROM [Laptops] WHERE [Price] > 1000 UNION ALL SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [Tablets] WHERE [Price] > 2000) AS [results_wrapper] WHERE [row_num] BETWEEN 16 AND 30", - c[EngineCodes.SqlServer]); - - - Assert.Equal( - "SELECT * FROM \"PHONES\" WHERE \"PRICE\" < 3000 UNION SELECT * FROM \"LAPTOPS\" WHERE \"PRICE\" > 1000 UNION ALL SELECT * FROM \"TABLETS\" WHERE \"PRICE\" > 2000 ROWS 16 TO 30", - c[EngineCodes.Firebird]); - } - - [Fact] - public void UnionWithCallbacks() - { - var mobiles = new Query("Phones") - .Where("Price", "<", 3000) - .Union(q => q.From("Laptops")) - .UnionAll(q => q.From("Tablets")); - - var c = Compile(mobiles); - - Assert.Equal( - "SELECT * FROM [Phones] WHERE [Price] < 3000 UNION SELECT * FROM [Laptops] UNION ALL SELECT * FROM [Tablets]", - c[EngineCodes.SqlServer]); - - - Assert.Equal( - "SELECT * FROM \"PHONES\" WHERE \"PRICE\" < 3000 UNION SELECT * FROM \"LAPTOPS\" UNION ALL SELECT * FROM \"TABLETS\"", - c[EngineCodes.Firebird]); - } - - [Fact] - public void UnionWithDifferentEngine() - { - var mobiles = new Query("Phones") - .Where("Price", "<", 300) - .ForSqlServer(scope => scope.Except(q => q.From("Phones").WhereNot("Os", "iOS"))) - .ForPostgreSql(scope => scope.Union(q => q.From("Laptops").Where("Price", "<", 800))) - .ForMySql(scope => scope.IntersectAll(q => q.From("Watches").Where("Os", "Android"))) - .ForFirebird(scope => scope.Union(q => q.From("Laptops").Where("Price", "<", 800))) - .UnionAll(q => q.From("Tablets").Where("Price", "<", 100)); - - var c = Compile(mobiles); - - Assert.Equal( - "SELECT * FROM [Phones] WHERE [Price] < 300 EXCEPT SELECT * FROM [Phones] WHERE NOT ([Os] = 'iOS') UNION ALL SELECT * FROM [Tablets] WHERE [Price] < 100", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "SELECT * FROM `Phones` WHERE `Price` < 300 INTERSECT ALL SELECT * FROM `Watches` WHERE `Os` = 'Android' UNION ALL SELECT * FROM `Tablets` WHERE `Price` < 100", - c[EngineCodes.MySql]); - - Assert.Equal( - "SELECT * FROM \"Phones\" WHERE \"Price\" < 300 UNION SELECT * FROM \"Laptops\" WHERE \"Price\" < 800 UNION ALL SELECT * FROM \"Tablets\" WHERE \"Price\" < 100", - c[EngineCodes.PostgreSql]); - - Assert.Equal( - "SELECT * FROM \"PHONES\" WHERE \"PRICE\" < 300 UNION SELECT * FROM \"LAPTOPS\" WHERE \"PRICE\" < 800 UNION ALL SELECT * FROM \"TABLETS\" WHERE \"PRICE\" < 100", - c[EngineCodes.Firebird]); - } - - [Fact] - public void CombineRaw() - { - var query = new Query("Mobiles").CombineRaw("UNION ALL SELECT * FROM Devices"); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Mobiles] UNION ALL SELECT * FROM Devices", c[EngineCodes.SqlServer]); - } - - [Fact] - public void CombineRawWithPlaceholders() - { - var query = new Query("Mobiles").CombineRaw("UNION ALL SELECT * FROM {Devices}"); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [Mobiles] UNION ALL SELECT * FROM [Devices]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM `Mobiles` UNION ALL SELECT * FROM `Devices`", c[EngineCodes.MySql]); - - Assert.Equal("SELECT * FROM \"MOBILES\" UNION ALL SELECT * FROM \"Devices\"", c[EngineCodes.Firebird]); - } - - [Fact] - public void NestedEmptyWhere() - { - // Empty nested where should be ignored - var query = new Query("A").Where(q => new Query().Where(q2 => new Query().Where(q3 => new Query()))); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [A]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void NestedQuery() - { - var query = new Query("A").Where(q => new Query("B")); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [A]", c[EngineCodes.SqlServer]); - } - - [Fact] - public void NestedQueryAfterNestedJoin() - { - // in this test, i am testing the compiler dynamic caching functionality - var query = new Query("users") - .Join("countries", j => j.On("countries.id", "users.country_id")) - .Where(q => new Query()); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM [users] \nINNER JOIN [countries] ON ([countries].[id] = [users].[country_id])", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void MultipleCte() - { - var q1 = new Query("A"); - var q2 = new Query("B"); - var q3 = new Query("C"); - - var query = new Query("A") - .With("A", q1) - .With("B", q2) - .With("C", q3); - - var c = Compile(query); - - Assert.Equal( - "WITH [A] AS (SELECT * FROM [A]),\n[B] AS (SELECT * FROM [B]),\n[C] AS (SELECT * FROM [C])\nSELECT * FROM [A]", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void CteAndBindings() - { - var query = new Query("Races") - .For("mysql", s => - s.With("range", q => - q.From("seqtbl") - .Select("Id").Where("Id", "<", 33)) - .WhereIn("RaceAuthor", q => q.From("Users") - .Select("Name").Where("Status", "Available") - ) - ) - .For("sqlsrv", s => - s.With("range", - q => q.From("Sequence").Select("Number").Where("Number", "<", 78) - ) - .Limit(25).Offset(20) - ) - .For("postgres", - s => s.With("range", q => q.FromRaw("generate_series(1, 33) as d").Select("d")) - .Where("Name", "3778") - ) - .For("firebird", - s => s.With("range", q => q.FromRaw("generate_series(1, 33) as d").Select("d")) - .Where("Name", "3778") - ) - .Where("Id", ">", 55) - .WhereBetween("Value", 18, 24); - - var c = Compile(query); - - Assert.Equal( - "WITH [range] AS (SELECT [Number] FROM [Sequence] WHERE [Number] < 78)\nSELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [Races] WHERE [Id] > 55 AND [Value] BETWEEN 18 AND 24) AS [results_wrapper] WHERE [row_num] BETWEEN 21 AND 45", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "WITH `range` AS (SELECT `Id` FROM `seqtbl` WHERE `Id` < 33)\nSELECT * FROM `Races` WHERE `RaceAuthor` IN (SELECT `Name` FROM `Users` WHERE `Status` = 'Available') AND `Id` > 55 AND `Value` BETWEEN 18 AND 24", - c[EngineCodes.MySql]); - - Assert.Equal( - "WITH \"range\" AS (SELECT \"d\" FROM generate_series(1, 33) as d)\nSELECT * FROM \"Races\" WHERE \"Name\" = '3778' AND \"Id\" > 55 AND \"Value\" BETWEEN 18 AND 24", - c[EngineCodes.PostgreSql]); - - Assert.Equal( - "WITH \"RANGE\" AS (SELECT \"D\" FROM generate_series(1, 33) as d)\nSELECT * FROM \"RACES\" WHERE \"NAME\" = '3778' AND \"ID\" > 55 AND \"VALUE\" BETWEEN 18 AND 24", - c[EngineCodes.Firebird]); - } - - // test for issue #50 - [Fact] - public void CascadedCteAndBindings() - { - var cte1 = new Query("Table1"); - cte1.Select("Column1", "Column2"); - cte1.Where("Column2", 1); - - var cte2 = new Query("Table2"); - cte2.With("cte1", cte1); - cte2.Select("Column3", "Column4"); - cte2.Join("cte1", join => join.On("Column1", "Column3")); - cte2.Where("Column4", 2); - - var mainQuery = new Query("Table3"); - mainQuery.With("cte2", cte2); - mainQuery.Select("*"); - mainQuery.From("cte2"); - mainQuery.Where("Column3", 5); - - var c = Compile(mainQuery); - - Assert.Equal("WITH [cte1] AS (SELECT [Column1], [Column2] FROM [Table1] WHERE [Column2] = 1),\n[cte2] AS (SELECT [Column3], [Column4] FROM [Table2] \nINNER JOIN [cte1] ON ([Column1] = [Column3]) WHERE [Column4] = 2)\nSELECT * FROM [cte2] WHERE [Column3] = 5", c[EngineCodes.SqlServer]); - - Assert.Equal("WITH `cte1` AS (SELECT `Column1`, `Column2` FROM `Table1` WHERE `Column2` = 1),\n`cte2` AS (SELECT `Column3`, `Column4` FROM `Table2` \nINNER JOIN `cte1` ON (`Column1` = `Column3`) WHERE `Column4` = 2)\nSELECT * FROM `cte2` WHERE `Column3` = 5", c[EngineCodes.MySql]); - - Assert.Equal("WITH \"cte1\" AS (SELECT \"Column1\", \"Column2\" FROM \"Table1\" WHERE \"Column2\" = 1),\n\"cte2\" AS (SELECT \"Column3\", \"Column4\" FROM \"Table2\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3\") WHERE \"Column4\" = 2)\nSELECT * FROM \"cte2\" WHERE \"Column3\" = 5", c[EngineCodes.PostgreSql]); - - Assert.Equal("WITH \"CTE1\" AS (SELECT \"COLUMN1\", \"COLUMN2\" FROM \"TABLE1\" WHERE \"COLUMN2\" = 1),\n\"CTE2\" AS (SELECT \"COLUMN3\", \"COLUMN4\" FROM \"TABLE2\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3\") WHERE \"COLUMN4\" = 2)\nSELECT * FROM \"CTE2\" WHERE \"COLUMN3\" = 5", c[EngineCodes.Firebird]); - } - - // test for issue #50 - [Fact] - public void CascadedAndMultiReferencedCteAndBindings() - { - var cte1 = new Query("Table1"); - cte1.Select("Column1", "Column2"); - cte1.Where("Column2", 1); - - var cte2 = new Query("Table2"); - cte2.With("cte1", cte1); - cte2.Select("Column3", "Column4"); - cte2.Join("cte1", join => join.On("Column1", "Column3")); - cte2.Where("Column4", 2); - - var cte3 = new Query("Table3"); - cte3.With("cte1", cte1); - cte3.Select("Column3_3", "Column3_4"); - cte3.Join("cte1", join => join.On("Column1", "Column3_3")); - cte3.Where("Column3_4", 33); - - var mainQuery = new Query("Table3"); - mainQuery.With("cte2", cte2); - mainQuery.With("cte3", cte3); - mainQuery.Select("*"); - mainQuery.From("cte2"); - mainQuery.Where("Column3", 5); - - var c = Compile(mainQuery); - - Assert.Equal("WITH [cte1] AS (SELECT [Column1], [Column2] FROM [Table1] WHERE [Column2] = 1),\n[cte2] AS (SELECT [Column3], [Column4] FROM [Table2] \nINNER JOIN [cte1] ON ([Column1] = [Column3]) WHERE [Column4] = 2),\n[cte3] AS (SELECT [Column3_3], [Column3_4] FROM [Table3] \nINNER JOIN [cte1] ON ([Column1] = [Column3_3]) WHERE [Column3_4] = 33)\nSELECT * FROM [cte2] WHERE [Column3] = 5", c[EngineCodes.SqlServer]); - - Assert.Equal("WITH `cte1` AS (SELECT `Column1`, `Column2` FROM `Table1` WHERE `Column2` = 1),\n`cte2` AS (SELECT `Column3`, `Column4` FROM `Table2` \nINNER JOIN `cte1` ON (`Column1` = `Column3`) WHERE `Column4` = 2),\n`cte3` AS (SELECT `Column3_3`, `Column3_4` FROM `Table3` \nINNER JOIN `cte1` ON (`Column1` = `Column3_3`) WHERE `Column3_4` = 33)\nSELECT * FROM `cte2` WHERE `Column3` = 5", c[EngineCodes.MySql]); - - Assert.Equal("WITH \"cte1\" AS (SELECT \"Column1\", \"Column2\" FROM \"Table1\" WHERE \"Column2\" = 1),\n\"cte2\" AS (SELECT \"Column3\", \"Column4\" FROM \"Table2\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3\") WHERE \"Column4\" = 2),\n\"cte3\" AS (SELECT \"Column3_3\", \"Column3_4\" FROM \"Table3\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3_3\") WHERE \"Column3_4\" = 33)\nSELECT * FROM \"cte2\" WHERE \"Column3\" = 5", c[EngineCodes.PostgreSql]); - - Assert.Equal("WITH \"CTE1\" AS (SELECT \"COLUMN1\", \"COLUMN2\" FROM \"TABLE1\" WHERE \"COLUMN2\" = 1),\n\"CTE2\" AS (SELECT \"COLUMN3\", \"COLUMN4\" FROM \"TABLE2\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3\") WHERE \"COLUMN4\" = 2),\n\"CTE3\" AS (SELECT \"COLUMN3_3\", \"COLUMN3_4\" FROM \"TABLE3\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3_3\") WHERE \"COLUMN3_4\" = 33)\nSELECT * FROM \"CTE2\" WHERE \"COLUMN3\" = 5", c[EngineCodes.Firebird]); - } - - // test for issue #50 - [Fact] - public void MultipleCtesAndBindings() - { - var cte1 = new Query("Table1"); - cte1.Select("Column1", "Column2"); - cte1.Where("Column2", 1); - - var cte2 = new Query("Table2"); - cte2.Select("Column3", "Column4"); - cte2.Join("cte1", join => join.On("Column1", "Column3")); - cte2.Where("Column4", 2); - - var cte3 = new Query("Table3"); - cte3.Select("Column3_3", "Column3_4"); - cte3.Join("cte1", join => join.On("Column1", "Column3_3")); - cte3.Where("Column3_4", 33); - - var mainQuery = new Query("Table3"); - mainQuery.With("cte1", cte1); - mainQuery.With("cte2", cte2); - mainQuery.With("cte3", cte3); - mainQuery.Select("*"); - mainQuery.From("cte3"); - mainQuery.Where("Column3_4", 5); - - var c = Compile(mainQuery); - - Assert.Equal("WITH [cte1] AS (SELECT [Column1], [Column2] FROM [Table1] WHERE [Column2] = 1),\n[cte2] AS (SELECT [Column3], [Column4] FROM [Table2] \nINNER JOIN [cte1] ON ([Column1] = [Column3]) WHERE [Column4] = 2),\n[cte3] AS (SELECT [Column3_3], [Column3_4] FROM [Table3] \nINNER JOIN [cte1] ON ([Column1] = [Column3_3]) WHERE [Column3_4] = 33)\nSELECT * FROM [cte3] WHERE [Column3_4] = 5", c[EngineCodes.SqlServer]); - - Assert.Equal("WITH `cte1` AS (SELECT `Column1`, `Column2` FROM `Table1` WHERE `Column2` = 1),\n`cte2` AS (SELECT `Column3`, `Column4` FROM `Table2` \nINNER JOIN `cte1` ON (`Column1` = `Column3`) WHERE `Column4` = 2),\n`cte3` AS (SELECT `Column3_3`, `Column3_4` FROM `Table3` \nINNER JOIN `cte1` ON (`Column1` = `Column3_3`) WHERE `Column3_4` = 33)\nSELECT * FROM `cte3` WHERE `Column3_4` = 5", c[EngineCodes.MySql]); - - Assert.Equal("WITH \"cte1\" AS (SELECT \"Column1\", \"Column2\" FROM \"Table1\" WHERE \"Column2\" = 1),\n\"cte2\" AS (SELECT \"Column3\", \"Column4\" FROM \"Table2\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3\") WHERE \"Column4\" = 2),\n\"cte3\" AS (SELECT \"Column3_3\", \"Column3_4\" FROM \"Table3\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3_3\") WHERE \"Column3_4\" = 33)\nSELECT * FROM \"cte3\" WHERE \"Column3_4\" = 5", c[EngineCodes.PostgreSql]); - - Assert.Equal("WITH \"CTE1\" AS (SELECT \"COLUMN1\", \"COLUMN2\" FROM \"TABLE1\" WHERE \"COLUMN2\" = 1),\n\"CTE2\" AS (SELECT \"COLUMN3\", \"COLUMN4\" FROM \"TABLE2\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3\") WHERE \"COLUMN4\" = 2),\n\"CTE3\" AS (SELECT \"COLUMN3_3\", \"COLUMN3_4\" FROM \"TABLE3\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3_3\") WHERE \"COLUMN3_4\" = 33)\nSELECT * FROM \"CTE3\" WHERE \"COLUMN3_4\" = 5", c[EngineCodes.Firebird]); - } - - - [Fact] - public void Limit() - { - var q = new Query().From("users").Select("id", "name").Limit(10); - var c = Compile(q); - - // Assert.Equal(c[EngineCodes.SqlServer], "SELECT * FROM (SELECT [id], [name],ROW_NUMBER() OVER (SELECT 0) AS [row_num] FROM [users]) AS [temp_table] WHERE [row_num] >= 10"); - Assert.Equal("SELECT TOP (10) [id], [name] FROM [users]", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT `id`, `name` FROM `users` LIMIT 10", c[EngineCodes.MySql]); - Assert.Equal("SELECT \"id\", \"name\" FROM \"users\" LIMIT 10", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT FIRST 10 \"ID\", \"NAME\" FROM \"USERS\"", c[EngineCodes.Firebird]); - } - - [Fact] - public void Offset() - { - var q = new Query().From("users").Offset(10); - var c = Compile(q); - - Assert.Equal( - "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [users]) AS [results_wrapper] WHERE [row_num] >= 11", - c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM `users` LIMIT 18446744073709551615 OFFSET 10", c[EngineCodes.MySql]); - Assert.Equal("SELECT * FROM \"users\" OFFSET 10", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT SKIP 10 * FROM \"USERS\"", c[EngineCodes.Firebird]); - } - - [Fact] - public void LimitOffset() - { - var q = new Query().From("users").Offset(10).Limit(5); - - var c = Compile(q); - - Assert.Equal( - "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [users]) AS [results_wrapper] WHERE [row_num] BETWEEN 11 AND 15", - c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM `users` LIMIT 5 OFFSET 10", c[EngineCodes.MySql]); - Assert.Equal("SELECT * FROM \"users\" LIMIT 5 OFFSET 10", c[EngineCodes.PostgreSql]); - Assert.Equal("SELECT * FROM \"USERS\" ROWS 11 TO 15", c[EngineCodes.Firebird]); - } - - [Fact] - public void BasicJoin() - { - var q = new Query().From("users").Join("countries", "countries.id", "users.country_id"); - - var c = Compile(q); - - Assert.Equal("SELECT * FROM [users] \nINNER JOIN [countries] ON [countries].[id] = [users].[country_id]", - c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM `users` \nINNER JOIN `countries` ON `countries`.`id` = `users`.`country_id`", - c[EngineCodes.MySql]); - } - - [Theory] - [InlineData("inner join", "INNER JOIN")] - [InlineData("left join", "LEFT JOIN")] - [InlineData("right join", "RIGHT JOIN")] - [InlineData("cross join", "CROSS JOIN")] - public void JoinTypes(string given, string output) - { - var q = new Query().From("users") - .Join("countries", "countries.id", "users.country_id", "=", given); - - var c = Compile(q); - - Assert.Equal($"SELECT * FROM [users] \n{output} [countries] ON [countries].[id] = [users].[country_id]", - c[EngineCodes.SqlServer]); - - Assert.Equal($"SELECT * FROM `users` \n{output} `countries` ON `countries`.`id` = `users`.`country_id`", - c[EngineCodes.MySql]); - - Assert.Equal( - $"SELECT * FROM \"users\" \n{output} \"countries\" ON \"countries\".\"id\" = \"users\".\"country_id\"", - c[EngineCodes.PostgreSql]); - - Assert.Equal( - $"SELECT * FROM \"USERS\" \n{output} \"COUNTRIES\" ON \"COUNTRIES\".\"ID\" = \"USERS\".\"COUNTRY_ID\"", - c[EngineCodes.Firebird]); - } - - [Fact] - public void OrWhereRawEscaped() - { - var query = new Query("Table").WhereRaw("[MyCol] = ANY(?::int\\[\\])", "{1,2,3}"); - - var c = Compile(query); - - Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" = ANY('{1,2,3}'::int[])", c[EngineCodes.PostgreSql]); - } - - [Fact] - public void Having() - { - var q = new Query("Table1") - .Having("Column1", ">", 1); - var c = Compile(q); - - Assert.Equal("SELECT * FROM [Table1] HAVING [Column1] > 1", c[EngineCodes.SqlServer]); - } - - [Fact] - public void MultipleHaving() - { - var q = new Query("Table1") - .Having("Column1", ">", 1) - .Having("Column2", "=", 1); - var c = Compile(q); - - Assert.Equal("SELECT * FROM [Table1] HAVING [Column1] > 1 AND [Column2] = 1", c[EngineCodes.SqlServer]); - } - - [Fact] - public void MultipleOrHaving() - { - var q = new Query("Table1") - .Having("Column1", ">", 1) - .OrHaving("Column2", "=", 1); - var c = Compile(q); - - Assert.Equal("SELECT * FROM [Table1] HAVING [Column1] > 1 OR [Column2] = 1", c[EngineCodes.SqlServer]); - } - - [Fact] - public void ShouldUseILikeOnPostgresWhenNonCaseSensitive() - { - var q = new Query("Table1") - .WhereLike("Column1", "%Upper Word%", false); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%upper word%'", c[EngineCodes.SqlServer]); - Assert.Equal("SELECT * FROM \"Table1\" WHERE \"Column1\" ilike '%Upper Word%'", c[EngineCodes.PostgreSql]); - } - - [Fact] - public void EscapedWhereLike() - { - var q = new Query("Table1") - .WhereLike("Column1", @"TestString\%", false, @"\"); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like 'teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void EscapedWhereStarts() - { - var q = new Query("Table1") - .WhereStarts("Column1", @"TestString\%", false, @"\"); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like 'teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void EscapedWhereEnds() - { - var q = new Query("Table1") - .WhereEnds("Column1", @"TestString\%", false, @"\"); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void EscapedWhereContains() - { - var q = new Query("Table1") - .WhereContains("Column1", @"TestString\%", false, @"\"); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void EscapedHavingLike() - { - var q = new Query("Table1") - .HavingLike("Column1", @"TestString\%", false, @"\"); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like 'teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void EscapedHavingStarts() - { - var q = new Query("Table1") - .HavingStarts("Column1", @"TestString\%", false, @"\"); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like 'teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void EscapedHavingEnds() - { - var q = new Query("Table1") - .HavingEnds("Column1", @"TestString\%", false, @"\"); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like '%teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void EscapedHavingContains() - { - var q = new Query("Table1") - .HavingContains("Column1", @"TestString\%", false, @"\"); - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like '%teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void EscapeClauseThrowsForMultipleCharacters() - { - Assert.ThrowsAny(() => - { - var q = new Query("Table1") - .HavingContains("Column1", @"TestString\%", false, @"\aa"); - }); - } - - - [Fact] - public void BasicSelectRaw_WithNoTable() - { - var q = new Query().SelectRaw("somefunction() as c1"); - - var c = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT somefunction() as c1", c.ToString()); - } - - [Fact] - public void BasicSelect_WithNoTable() - { - var q = new Query().Select("c1"); - var c = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT [c1]", c.ToString()); - } - - [Fact] - public void BasicSelect_WithNoTableAndWhereClause() - { - var q = new Query().Select("c1").Where("p", 1); - var c = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT [c1] WHERE [p] = 1", c.ToString()); - } - - [Fact] - public void BasicSelect_WithNoTableWhereRawClause() - { - var q = new Query().Select("c1").WhereRaw("1 = 1"); - var c = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT [c1] WHERE 1 = 1", c.ToString()); - } - - [Fact] - public void BasicSelectAggregate() - { - var q = new Query("Posts").Select("Title") - .SelectAggregate("sum", "ViewCount"); - - var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT [Title], SUM([ViewCount]) FROM [Posts]", sqlServer.ToString()); - } - - [Fact] - public void SelectAggregateShouldIgnoreEmptyFilter() - { - var q = new Query("Posts").Select("Title") - .SelectAggregate("sum", "ViewCount", q => q); - - var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT [Title], SUM([ViewCount]) FROM [Posts]", sqlServer.ToString()); - } - - [Fact] - public void SelectAggregateShouldIgnoreEmptyQueryFilter() - { - var q = new Query("Posts").Select("Title") - .SelectAggregate("sum", "ViewCount", new Query()); - - var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT [Title], SUM([ViewCount]) FROM [Posts]", sqlServer.ToString()); - } - - [Fact] - public void BasicSelectAggregateWithAlias() - { - var q = new Query("Posts").Select("Title") - .SelectAggregate("sum", "ViewCount as TotalViews"); - - var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT [Title], SUM([ViewCount]) AS [TotalViews] FROM [Posts]", sqlServer.ToString()); - } - - [Fact] - public void SelectWithFilter() - { - var q = new Query("Posts").Select("Title") - .SelectAggregate("sum", "ViewCount as Published_Jan", q => q.Where("Published_Month", "Jan")) - .SelectAggregate("sum", "ViewCount as Published_Feb", q => q.Where("Published_Month", "Feb")); - - var pgSql = Compilers.CompileFor(EngineCodes.PostgreSql, q); - Assert.Equal("SELECT \"Title\", SUM(\"ViewCount\") FILTER (WHERE \"Published_Month\" = 'Jan') AS \"Published_Jan\", SUM(\"ViewCount\") FILTER (WHERE \"Published_Month\" = 'Feb') AS \"Published_Feb\" FROM \"Posts\"", pgSql.ToString()); - - var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT [Title], SUM(CASE WHEN [Published_Month] = 'Jan' THEN [ViewCount] END) AS [Published_Jan], SUM(CASE WHEN [Published_Month] = 'Feb' THEN [ViewCount] END) AS [Published_Feb] FROM [Posts]", sqlServer.ToString()); - } - - [Fact] - public void SelectWithExists() - { - var q = new Query("Posts").WhereExists( - new Query("Comments").WhereColumns("Comments.PostId", "=", "Posts.Id") - ); - - var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal("SELECT * FROM [Posts] WHERE EXISTS (SELECT 1 FROM [Comments] WHERE [Comments].[PostId] = [Posts].[Id])", sqlServer.ToString()); - } - - [Fact] - public void SelectWithExists_OmitSelectIsFalse() - { - var q = new Query("Posts").WhereExists( - new Query("Comments").Select("Id").WhereColumns("Comments.PostId", "=", "Posts.Id") - ); - - - var compiler = new SqlServerCompiler - { - OmitSelectInsideExists = false, - }; - - var sqlServer = compiler.Compile(q).ToString(); - Assert.Equal("SELECT * FROM [Posts] WHERE EXISTS (SELECT [Id] FROM [Comments] WHERE [Comments].[PostId] = [Posts].[Id])", sqlServer.ToString()); - } - - } -} +namespace SqlKata.Tests; + +using SqlKata.Compilers; +using SqlKata.Extensions; +using SqlKata.Tests.Infrastructure; +using System; +using System.Collections.Generic; +using Xunit; + +public class SelectTests : TestSupport { + [Fact] + public void BasicSelect() { + var q = new Query().From("users").Select("id", "name"); + var c = Compile(q); + + Assert.Equal("SELECT [id], [name] FROM [users]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT `id`, `name` FROM `users`", c[EngineCodes.MySql]); + Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"USERS\"", c[EngineCodes.Firebird]); + Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.Oracle]); + } + + [Fact] + public void BasicSelectEnumerable() { + var q = new Query().From("users").Select(new List() { "id", "name" }); + var c = Compile(q); + + Assert.Equal("SELECT [id], [name] FROM [users]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT `id`, `name` FROM `users`", c[EngineCodes.MySql]); + Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"USERS\"", c[EngineCodes.Firebird]); + Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.Oracle]); + } + + [Fact] + public void BasicSelectWhereBindingIsEmptyOrNull() { + var q = new Query() + .From("users") + .Select("id", "name") + .Where("author", "") + .OrWhere("author", null); + + var c = Compile(q); + + Assert.Equal("SELECT [id], [name] FROM [users] WHERE [author] = '' OR [author] IS NULL", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT `id`, `name` FROM `users` WHERE `author` = '' OR `author` IS NULL", c[EngineCodes.MySql]); + Assert.Equal("SELECT \"id\", \"name\" FROM \"users\" WHERE \"author\" = '' OR \"author\" IS NULL", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"USERS\" WHERE \"AUTHOR\" = '' OR \"AUTHOR\" IS NULL", c[EngineCodes.Firebird]); + } + + [Fact] + public void BasicSelectWithAlias() { + var q = new Query().From("users as u").Select("id", "name"); + var c = Compile(q); + + Assert.Equal("SELECT [id], [name] FROM [users] AS [u]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT `id`, `name` FROM `users` AS `u`", c[EngineCodes.MySql]); + Assert.Equal("SELECT \"id\", \"name\" FROM \"users\" AS \"u\"", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT \"ID\", \"NAME\" FROM \"USERS\" AS \"U\"", c[EngineCodes.Firebird]); + } + + [Fact] + public void ExpandedSelect() { + var q = new Query().From("users").Select("users.{id,name, age}"); + var c = Compile(q); + + Assert.Equal("SELECT [users].[id], [users].[name], [users].[age] FROM [users]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT `users`.`id`, `users`.`name`, `users`.`age` FROM `users`", c[EngineCodes.MySql]); + } + + [Fact] + public void ExpandedSelectMultiline() { + var q = new Query().From("users").Select(@"users.{ + id, + name as Name, + age + }"); + var c = Compile(q); + + Assert.Equal("SELECT [users].[id], [users].[name] AS [Name], [users].[age] FROM [users]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT `users`.`id`, `users`.`name` AS `Name`, `users`.`age` FROM `users`", c[EngineCodes.MySql]); + } + + [Fact] + public void ExpandedSelectWithSchema() { + var q = new Query().From("users").Select("dbo.users.{id,name, age}"); + var c = Compile(q); + + Assert.Equal("SELECT [dbo].[users].[id], [dbo].[users].[name], [dbo].[users].[age] FROM [users]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void ExpandedSelectMultilineWithSchema() { + var q = new Query().From("users").Select(@"dbo.users.{ + id, + name as Name, + age + }"); + var c = Compile(q); + + Assert.Equal("SELECT [dbo].[users].[id], [dbo].[users].[name] AS [Name], [dbo].[users].[age] FROM [users]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void NestedEmptyWhereAtFirstCondition() { + var query = new Query("table") + .Where(q => new Query()) + .Where("id", 1); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [table] WHERE [id] = 1", c[EngineCodes.SqlServer]); + + + Assert.Equal("SELECT * FROM \"TABLE\" WHERE \"ID\" = 1", c[EngineCodes.Firebird]); + } + + [Fact] + public void WhereTrue() { + var query = new Query("Table").WhereTrue("IsActive"); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Table] WHERE [IsActive] = cast(1 as bit)", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM `Table` WHERE `IsActive` = true", c[EngineCodes.MySql]); + Assert.Equal("SELECT * FROM \"Table\" WHERE \"IsActive\" = true", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT * FROM \"TABLE\" WHERE \"ISACTIVE\" = 1", c[EngineCodes.Firebird]); + } + + [Fact] + public void WhereFalse() { + var query = new Query("Table").WhereFalse("IsActive"); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Table] WHERE [IsActive] = cast(0 as bit)", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM `Table` WHERE `IsActive` = false", c[EngineCodes.MySql]); + Assert.Equal("SELECT * FROM \"Table\" WHERE \"IsActive\" = false", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT * FROM \"TABLE\" WHERE \"ISACTIVE\" = 0", c[EngineCodes.Firebird]); + } + + [Fact] + public void OrWhereFalse() { + var query = new Query("Table").Where("MyCol", "abc").OrWhereFalse("IsActive"); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Table] WHERE [MyCol] = 'abc' OR [IsActive] = cast(0 as bit)", c[EngineCodes.SqlServer]); + + Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" = 'abc' OR \"IsActive\" = false", c[EngineCodes.PostgreSql]); + + } + + [Fact] + public void OrWhereTrue() { + var query = new Query("Table").Where("MyCol", "abc").OrWhereTrue("IsActive"); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Table] WHERE [MyCol] = 'abc' OR [IsActive] = cast(1 as bit)", c[EngineCodes.SqlServer]); + + Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" = 'abc' OR \"IsActive\" = true", c[EngineCodes.PostgreSql]); + + } + + [Fact] + public void OrWhereNull() { + var query = new Query("Table").Where("MyCol", "abc").OrWhereNull("IsActive"); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Table] WHERE [MyCol] = 'abc' OR [IsActive] IS NULL", c[EngineCodes.SqlServer]); + + Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" = 'abc' OR \"IsActive\" IS NULL", c[EngineCodes.PostgreSql]); + } + + [Fact] + public void WhereSub() { + var subQuery = new Query("Table2").WhereColumns("Table2.Column", "=", "Table.MyCol").AsCount(); + + var query = new Query("Table").WhereSub(subQuery, 1); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Table] WHERE (SELECT COUNT(*) AS [count] FROM [Table2] WHERE [Table2].[Column] = [Table].[MyCol]) = 1", c[EngineCodes.SqlServer]); + + Assert.Equal("SELECT * FROM \"Table\" WHERE (SELECT COUNT(*) AS \"count\" FROM \"Table2\" WHERE \"Table2\".\"Column\" = \"Table\".\"MyCol\") = 1", c[EngineCodes.PostgreSql]); + } + + [Fact] + public void OrWhereSub() { + var subQuery = new Query("Table2").WhereColumns("Table2.Column", "=", "Table.MyCol").AsCount(); + + var query = new Query("Table").WhereNull("MyCol").OrWhereSub(subQuery, "<", 1); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Table] WHERE [MyCol] IS NULL OR (SELECT COUNT(*) AS [count] FROM [Table2] WHERE [Table2].[Column] = [Table].[MyCol]) < 1", c[EngineCodes.SqlServer]); + + Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" IS NULL OR (SELECT COUNT(*) AS \"count\" FROM \"Table2\" WHERE \"Table2\".\"Column\" = \"Table\".\"MyCol\") < 1", c[EngineCodes.PostgreSql]); + } + + [Fact] + public void PassingArrayAsParameter() { + var query = new Query("Table").WhereRaw("[Id] in (?)", new object[] { new object[] { 1, 2, 3 } }); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Table] WHERE [Id] in (1,2,3)", c[EngineCodes.SqlServer]); + } + + [Fact] + public void UsingJsonArray() { + var query = new Query("Table").WhereRaw("[Json]->'address'->>'country' in (?)", new[] { 1, 2, 3, 4 }); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM \"Table\" WHERE \"Json\"->'address'->>'country' in (1,2,3,4)", c[EngineCodes.PostgreSql]); + } + + [Fact] + public void Union() { + var laptops = new Query("Laptops"); + var mobiles = new Query("Phones").Union(laptops); + + var c = Compile(mobiles); + + Assert.Equal("SELECT * FROM [Phones] UNION SELECT * FROM [Laptops]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM \"Phones\" UNION SELECT * FROM \"Laptops\"", c[EngineCodes.Sqlite]); + Assert.Equal("SELECT * FROM \"PHONES\" UNION SELECT * FROM \"LAPTOPS\"", c[EngineCodes.Firebird]); + } + + + [Fact] + public void UnionWithBindings() { + var laptops = new Query("Laptops").Where("Type", "A"); + var mobiles = new Query("Phones").Union(laptops); + + var c = Compile(mobiles); + + Assert.Equal("SELECT * FROM [Phones] UNION SELECT * FROM [Laptops] WHERE [Type] = 'A'", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM \"Phones\" UNION SELECT * FROM \"Laptops\" WHERE \"Type\" = 'A'", c[EngineCodes.Sqlite]); + Assert.Equal("SELECT * FROM `Phones` UNION SELECT * FROM `Laptops` WHERE `Type` = 'A'", c[EngineCodes.MySql]); + Assert.Equal("SELECT * FROM \"PHONES\" UNION SELECT * FROM \"LAPTOPS\" WHERE \"TYPE\" = 'A'", c[EngineCodes.Firebird]); + } + + [Fact] + public void RawUnionWithBindings() { + var mobiles = new Query("Phones").UnionRaw("UNION SELECT * FROM [Laptops] WHERE [Type] = ?", "A"); + + var c = Compile(mobiles); + + Assert.Equal("SELECT * FROM [Phones] UNION SELECT * FROM [Laptops] WHERE [Type] = 'A'", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM `Phones` UNION SELECT * FROM `Laptops` WHERE `Type` = 'A'", c[EngineCodes.MySql]); + } + + [Fact] + public void MultipleUnion() { + var laptops = new Query("Laptops"); + var tablets = new Query("Tablets"); + + var mobiles = new Query("Phones").Union(laptops).Union(tablets); + + var c = Compile(mobiles); + + Assert.Equal("SELECT * FROM [Phones] UNION SELECT * FROM [Laptops] UNION SELECT * FROM [Tablets]", + c[EngineCodes.SqlServer]); + + + Assert.Equal("SELECT * FROM \"PHONES\" UNION SELECT * FROM \"LAPTOPS\" UNION SELECT * FROM \"TABLETS\"", + c[EngineCodes.Firebird]); + } + + [Fact] + public void MultipleUnionWithBindings() { + var laptops = new Query("Laptops").Where("Price", ">", 1000); + var tablets = new Query("Tablets").Where("Price", ">", 2000); + + var mobiles = new Query("Phones").Where("Price", "<", 3000).Union(laptops).Union(tablets); + + var c = Compile(mobiles); + + Assert.Equal( + "SELECT * FROM [Phones] WHERE [Price] < 3000 UNION SELECT * FROM [Laptops] WHERE [Price] > 1000 UNION SELECT * FROM [Tablets] WHERE [Price] > 2000", + c[EngineCodes.SqlServer]); + + + Assert.Equal( + "SELECT * FROM \"PHONES\" WHERE \"PRICE\" < 3000 UNION SELECT * FROM \"LAPTOPS\" WHERE \"PRICE\" > 1000 UNION SELECT * FROM \"TABLETS\" WHERE \"PRICE\" > 2000", + c[EngineCodes.Firebird]); + } + + [Fact] + public void MultipleUnionWithBindingsAndPagination() { + var laptops = new Query("Laptops").Where("Price", ">", 1000); + var tablets = new Query("Tablets").Where("Price", ">", 2000).ForPage(2); + + var mobiles = new Query("Phones").Where("Price", "<", 3000).Union(laptops).UnionAll(tablets); + + + var c = Compile(mobiles); + + Assert.Equal( + "SELECT * FROM [Phones] WHERE [Price] < 3000 UNION SELECT * FROM [Laptops] WHERE [Price] > 1000 UNION ALL SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [Tablets] WHERE [Price] > 2000) AS [results_wrapper] WHERE [row_num] BETWEEN 16 AND 30", + c[EngineCodes.SqlServer]); + + + Assert.Equal( + "SELECT * FROM \"PHONES\" WHERE \"PRICE\" < 3000 UNION SELECT * FROM \"LAPTOPS\" WHERE \"PRICE\" > 1000 UNION ALL SELECT * FROM \"TABLETS\" WHERE \"PRICE\" > 2000 ROWS 16 TO 30", + c[EngineCodes.Firebird]); + } + + [Fact] + public void UnionWithCallbacks() { + var mobiles = new Query("Phones") + .Where("Price", "<", 3000) + .Union(q => q.From("Laptops")) + .UnionAll(q => q.From("Tablets")); + + var c = Compile(mobiles); + + Assert.Equal( + "SELECT * FROM [Phones] WHERE [Price] < 3000 UNION SELECT * FROM [Laptops] UNION ALL SELECT * FROM [Tablets]", + c[EngineCodes.SqlServer]); + + + Assert.Equal( + "SELECT * FROM \"PHONES\" WHERE \"PRICE\" < 3000 UNION SELECT * FROM \"LAPTOPS\" UNION ALL SELECT * FROM \"TABLETS\"", + c[EngineCodes.Firebird]); + } + + [Fact] + public void UnionWithDifferentEngine() { + var mobiles = new Query("Phones") + .Where("Price", "<", 300) + .ForSqlServer(scope => scope.Except(q => q.From("Phones").WhereNot("Os", "iOS"))) + .ForPostgreSql(scope => scope.Union(q => q.From("Laptops").Where("Price", "<", 800))) + .ForMySql(scope => scope.IntersectAll(q => q.From("Watches").Where("Os", "Android"))) + .ForFirebird(scope => scope.Union(q => q.From("Laptops").Where("Price", "<", 800))) + .UnionAll(q => q.From("Tablets").Where("Price", "<", 100)); + + var c = Compile(mobiles); + + Assert.Equal( + "SELECT * FROM [Phones] WHERE [Price] < 300 EXCEPT SELECT * FROM [Phones] WHERE NOT ([Os] = 'iOS') UNION ALL SELECT * FROM [Tablets] WHERE [Price] < 100", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "SELECT * FROM `Phones` WHERE `Price` < 300 INTERSECT ALL SELECT * FROM `Watches` WHERE `Os` = 'Android' UNION ALL SELECT * FROM `Tablets` WHERE `Price` < 100", + c[EngineCodes.MySql]); + + Assert.Equal( + "SELECT * FROM \"Phones\" WHERE \"Price\" < 300 UNION SELECT * FROM \"Laptops\" WHERE \"Price\" < 800 UNION ALL SELECT * FROM \"Tablets\" WHERE \"Price\" < 100", + c[EngineCodes.PostgreSql]); + + Assert.Equal( + "SELECT * FROM \"PHONES\" WHERE \"PRICE\" < 300 UNION SELECT * FROM \"LAPTOPS\" WHERE \"PRICE\" < 800 UNION ALL SELECT * FROM \"TABLETS\" WHERE \"PRICE\" < 100", + c[EngineCodes.Firebird]); + } + + [Fact] + public void CombineRaw() { + var query = new Query("Mobiles").CombineRaw("UNION ALL SELECT * FROM Devices"); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Mobiles] UNION ALL SELECT * FROM Devices", c[EngineCodes.SqlServer]); + } + + [Fact] + public void CombineRawWithPlaceholders() { + var query = new Query("Mobiles").CombineRaw("UNION ALL SELECT * FROM {Devices}"); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [Mobiles] UNION ALL SELECT * FROM [Devices]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM `Mobiles` UNION ALL SELECT * FROM `Devices`", c[EngineCodes.MySql]); + + Assert.Equal("SELECT * FROM \"MOBILES\" UNION ALL SELECT * FROM \"Devices\"", c[EngineCodes.Firebird]); + } + + [Fact] + public void NestedEmptyWhere() { + // Empty nested where should be ignored + var query = new Query("A").Where(q => new Query().Where(q2 => new Query().Where(q3 => new Query()))); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [A]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void NestedQuery() { + var query = new Query("A").Where(q => new Query("B")); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [A]", c[EngineCodes.SqlServer]); + } + + [Fact] + public void NestedQueryAfterNestedJoin() { + // in this test, i am testing the compiler dynamic caching functionality + var query = new Query("users") + .Join("countries", j => j.On("countries.id", "users.country_id")) + .Where(q => new Query()); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM [users] \nINNER JOIN [countries] ON ([countries].[id] = [users].[country_id])", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void MultipleCte() { + var q1 = new Query("A"); + var q2 = new Query("B"); + var q3 = new Query("C"); + + var query = new Query("A") + .With("A", q1) + .With("B", q2) + .With("C", q3); + + var c = Compile(query); + + Assert.Equal( + "WITH [A] AS (SELECT * FROM [A]),\n[B] AS (SELECT * FROM [B]),\n[C] AS (SELECT * FROM [C])\nSELECT * FROM [A]", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void CteAndBindings() { + var query = new Query("Races") + .For("mysql", s => + s.With("range", q => + q.From("seqtbl") + .Select("Id").Where("Id", "<", 33)) + .WhereIn("RaceAuthor", q => q.From("Users") + .Select("Name").Where("Status", "Available") + ) + ) + .For("sqlsrv", s => + s.With("range", + q => q.From("Sequence").Select("Number").Where("Number", "<", 78) + ) + .Limit(25).Offset(20) + ) + .For("postgres", + s => s.With("range", q => q.FromRaw("generate_series(1, 33) as d").Select("d")) + .Where("Name", "3778") + ) + .For("firebird", + s => s.With("range", q => q.FromRaw("generate_series(1, 33) as d").Select("d")) + .Where("Name", "3778") + ) + .Where("Id", ">", 55) + .WhereBetween("Value", 18, 24); + + var c = Compile(query); + + Assert.Equal( + "WITH [range] AS (SELECT [Number] FROM [Sequence] WHERE [Number] < 78)\nSELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [Races] WHERE [Id] > 55 AND [Value] BETWEEN 18 AND 24) AS [results_wrapper] WHERE [row_num] BETWEEN 21 AND 45", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "WITH `range` AS (SELECT `Id` FROM `seqtbl` WHERE `Id` < 33)\nSELECT * FROM `Races` WHERE `RaceAuthor` IN (SELECT `Name` FROM `Users` WHERE `Status` = 'Available') AND `Id` > 55 AND `Value` BETWEEN 18 AND 24", + c[EngineCodes.MySql]); + + Assert.Equal( + "WITH \"range\" AS (SELECT \"d\" FROM generate_series(1, 33) as d)\nSELECT * FROM \"Races\" WHERE \"Name\" = '3778' AND \"Id\" > 55 AND \"Value\" BETWEEN 18 AND 24", + c[EngineCodes.PostgreSql]); + + Assert.Equal( + "WITH \"RANGE\" AS (SELECT \"D\" FROM generate_series(1, 33) as d)\nSELECT * FROM \"RACES\" WHERE \"NAME\" = '3778' AND \"ID\" > 55 AND \"VALUE\" BETWEEN 18 AND 24", + c[EngineCodes.Firebird]); + } + + // test for issue #50 + [Fact] + public void CascadedCteAndBindings() { + var cte1 = new Query("Table1"); + cte1.Select("Column1", "Column2"); + cte1.Where("Column2", 1); + + var cte2 = new Query("Table2"); + cte2.With("cte1", cte1); + cte2.Select("Column3", "Column4"); + cte2.Join("cte1", join => join.On("Column1", "Column3")); + cte2.Where("Column4", 2); + + var mainQuery = new Query("Table3"); + mainQuery.With("cte2", cte2); + mainQuery.Select("*"); + mainQuery.From("cte2"); + mainQuery.Where("Column3", 5); + + var c = Compile(mainQuery); + + Assert.Equal("WITH [cte1] AS (SELECT [Column1], [Column2] FROM [Table1] WHERE [Column2] = 1),\n[cte2] AS (SELECT [Column3], [Column4] FROM [Table2] \nINNER JOIN [cte1] ON ([Column1] = [Column3]) WHERE [Column4] = 2)\nSELECT * FROM [cte2] WHERE [Column3] = 5", c[EngineCodes.SqlServer]); + + Assert.Equal("WITH `cte1` AS (SELECT `Column1`, `Column2` FROM `Table1` WHERE `Column2` = 1),\n`cte2` AS (SELECT `Column3`, `Column4` FROM `Table2` \nINNER JOIN `cte1` ON (`Column1` = `Column3`) WHERE `Column4` = 2)\nSELECT * FROM `cte2` WHERE `Column3` = 5", c[EngineCodes.MySql]); + + Assert.Equal("WITH \"cte1\" AS (SELECT \"Column1\", \"Column2\" FROM \"Table1\" WHERE \"Column2\" = 1),\n\"cte2\" AS (SELECT \"Column3\", \"Column4\" FROM \"Table2\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3\") WHERE \"Column4\" = 2)\nSELECT * FROM \"cte2\" WHERE \"Column3\" = 5", c[EngineCodes.PostgreSql]); + + Assert.Equal("WITH \"CTE1\" AS (SELECT \"COLUMN1\", \"COLUMN2\" FROM \"TABLE1\" WHERE \"COLUMN2\" = 1),\n\"CTE2\" AS (SELECT \"COLUMN3\", \"COLUMN4\" FROM \"TABLE2\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3\") WHERE \"COLUMN4\" = 2)\nSELECT * FROM \"CTE2\" WHERE \"COLUMN3\" = 5", c[EngineCodes.Firebird]); + } + + // test for issue #50 + [Fact] + public void CascadedAndMultiReferencedCteAndBindings() { + var cte1 = new Query("Table1"); + cte1.Select("Column1", "Column2"); + cte1.Where("Column2", 1); + + var cte2 = new Query("Table2"); + cte2.With("cte1", cte1); + cte2.Select("Column3", "Column4"); + cte2.Join("cte1", join => join.On("Column1", "Column3")); + cte2.Where("Column4", 2); + + var cte3 = new Query("Table3"); + cte3.With("cte1", cte1); + cte3.Select("Column3_3", "Column3_4"); + cte3.Join("cte1", join => join.On("Column1", "Column3_3")); + cte3.Where("Column3_4", 33); + + var mainQuery = new Query("Table3"); + mainQuery.With("cte2", cte2); + mainQuery.With("cte3", cte3); + mainQuery.Select("*"); + mainQuery.From("cte2"); + mainQuery.Where("Column3", 5); + + var c = Compile(mainQuery); + + Assert.Equal("WITH [cte1] AS (SELECT [Column1], [Column2] FROM [Table1] WHERE [Column2] = 1),\n[cte2] AS (SELECT [Column3], [Column4] FROM [Table2] \nINNER JOIN [cte1] ON ([Column1] = [Column3]) WHERE [Column4] = 2),\n[cte3] AS (SELECT [Column3_3], [Column3_4] FROM [Table3] \nINNER JOIN [cte1] ON ([Column1] = [Column3_3]) WHERE [Column3_4] = 33)\nSELECT * FROM [cte2] WHERE [Column3] = 5", c[EngineCodes.SqlServer]); + + Assert.Equal("WITH `cte1` AS (SELECT `Column1`, `Column2` FROM `Table1` WHERE `Column2` = 1),\n`cte2` AS (SELECT `Column3`, `Column4` FROM `Table2` \nINNER JOIN `cte1` ON (`Column1` = `Column3`) WHERE `Column4` = 2),\n`cte3` AS (SELECT `Column3_3`, `Column3_4` FROM `Table3` \nINNER JOIN `cte1` ON (`Column1` = `Column3_3`) WHERE `Column3_4` = 33)\nSELECT * FROM `cte2` WHERE `Column3` = 5", c[EngineCodes.MySql]); + + Assert.Equal("WITH \"cte1\" AS (SELECT \"Column1\", \"Column2\" FROM \"Table1\" WHERE \"Column2\" = 1),\n\"cte2\" AS (SELECT \"Column3\", \"Column4\" FROM \"Table2\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3\") WHERE \"Column4\" = 2),\n\"cte3\" AS (SELECT \"Column3_3\", \"Column3_4\" FROM \"Table3\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3_3\") WHERE \"Column3_4\" = 33)\nSELECT * FROM \"cte2\" WHERE \"Column3\" = 5", c[EngineCodes.PostgreSql]); + + Assert.Equal("WITH \"CTE1\" AS (SELECT \"COLUMN1\", \"COLUMN2\" FROM \"TABLE1\" WHERE \"COLUMN2\" = 1),\n\"CTE2\" AS (SELECT \"COLUMN3\", \"COLUMN4\" FROM \"TABLE2\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3\") WHERE \"COLUMN4\" = 2),\n\"CTE3\" AS (SELECT \"COLUMN3_3\", \"COLUMN3_4\" FROM \"TABLE3\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3_3\") WHERE \"COLUMN3_4\" = 33)\nSELECT * FROM \"CTE2\" WHERE \"COLUMN3\" = 5", c[EngineCodes.Firebird]); + } + + // test for issue #50 + [Fact] + public void MultipleCtesAndBindings() { + var cte1 = new Query("Table1"); + cte1.Select("Column1", "Column2"); + cte1.Where("Column2", 1); + + var cte2 = new Query("Table2"); + cte2.Select("Column3", "Column4"); + cte2.Join("cte1", join => join.On("Column1", "Column3")); + cte2.Where("Column4", 2); + + var cte3 = new Query("Table3"); + cte3.Select("Column3_3", "Column3_4"); + cte3.Join("cte1", join => join.On("Column1", "Column3_3")); + cte3.Where("Column3_4", 33); + + var mainQuery = new Query("Table3"); + mainQuery.With("cte1", cte1); + mainQuery.With("cte2", cte2); + mainQuery.With("cte3", cte3); + mainQuery.Select("*"); + mainQuery.From("cte3"); + mainQuery.Where("Column3_4", 5); + + var c = Compile(mainQuery); + + Assert.Equal("WITH [cte1] AS (SELECT [Column1], [Column2] FROM [Table1] WHERE [Column2] = 1),\n[cte2] AS (SELECT [Column3], [Column4] FROM [Table2] \nINNER JOIN [cte1] ON ([Column1] = [Column3]) WHERE [Column4] = 2),\n[cte3] AS (SELECT [Column3_3], [Column3_4] FROM [Table3] \nINNER JOIN [cte1] ON ([Column1] = [Column3_3]) WHERE [Column3_4] = 33)\nSELECT * FROM [cte3] WHERE [Column3_4] = 5", c[EngineCodes.SqlServer]); + + Assert.Equal("WITH `cte1` AS (SELECT `Column1`, `Column2` FROM `Table1` WHERE `Column2` = 1),\n`cte2` AS (SELECT `Column3`, `Column4` FROM `Table2` \nINNER JOIN `cte1` ON (`Column1` = `Column3`) WHERE `Column4` = 2),\n`cte3` AS (SELECT `Column3_3`, `Column3_4` FROM `Table3` \nINNER JOIN `cte1` ON (`Column1` = `Column3_3`) WHERE `Column3_4` = 33)\nSELECT * FROM `cte3` WHERE `Column3_4` = 5", c[EngineCodes.MySql]); + + Assert.Equal("WITH \"cte1\" AS (SELECT \"Column1\", \"Column2\" FROM \"Table1\" WHERE \"Column2\" = 1),\n\"cte2\" AS (SELECT \"Column3\", \"Column4\" FROM \"Table2\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3\") WHERE \"Column4\" = 2),\n\"cte3\" AS (SELECT \"Column3_3\", \"Column3_4\" FROM \"Table3\" \nINNER JOIN \"cte1\" ON (\"Column1\" = \"Column3_3\") WHERE \"Column3_4\" = 33)\nSELECT * FROM \"cte3\" WHERE \"Column3_4\" = 5", c[EngineCodes.PostgreSql]); + + Assert.Equal("WITH \"CTE1\" AS (SELECT \"COLUMN1\", \"COLUMN2\" FROM \"TABLE1\" WHERE \"COLUMN2\" = 1),\n\"CTE2\" AS (SELECT \"COLUMN3\", \"COLUMN4\" FROM \"TABLE2\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3\") WHERE \"COLUMN4\" = 2),\n\"CTE3\" AS (SELECT \"COLUMN3_3\", \"COLUMN3_4\" FROM \"TABLE3\" \nINNER JOIN \"CTE1\" ON (\"COLUMN1\" = \"COLUMN3_3\") WHERE \"COLUMN3_4\" = 33)\nSELECT * FROM \"CTE3\" WHERE \"COLUMN3_4\" = 5", c[EngineCodes.Firebird]); + } + + + [Fact] + public void Limit() { + var q = new Query().From("users").Select("id", "name").Limit(10); + var c = Compile(q); + + // Assert.Equal(c[EngineCodes.SqlServer], "SELECT * FROM (SELECT [id], [name],ROW_NUMBER() OVER (SELECT 0) AS [row_num] FROM [users]) AS [temp_table] WHERE [row_num] >= 10"); + Assert.Equal("SELECT TOP (10) [id], [name] FROM [users]", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT `id`, `name` FROM `users` LIMIT 10", c[EngineCodes.MySql]); + Assert.Equal("SELECT \"id\", \"name\" FROM \"users\" LIMIT 10", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT FIRST 10 \"ID\", \"NAME\" FROM \"USERS\"", c[EngineCodes.Firebird]); + } + + [Fact] + public void Offset() { + var q = new Query().From("users").Offset(10); + var c = Compile(q); + + Assert.Equal( + "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [users]) AS [results_wrapper] WHERE [row_num] >= 11", + c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM `users` LIMIT 18446744073709551615 OFFSET 10", c[EngineCodes.MySql]); + Assert.Equal("SELECT * FROM \"users\" OFFSET 10", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT SKIP 10 * FROM \"USERS\"", c[EngineCodes.Firebird]); + } + + [Fact] + public void LimitOffset() { + var q = new Query().From("users").Offset(10).Limit(5); + + var c = Compile(q); + + Assert.Equal( + "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [users]) AS [results_wrapper] WHERE [row_num] BETWEEN 11 AND 15", + c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM `users` LIMIT 5 OFFSET 10", c[EngineCodes.MySql]); + Assert.Equal("SELECT * FROM \"users\" LIMIT 5 OFFSET 10", c[EngineCodes.PostgreSql]); + Assert.Equal("SELECT * FROM \"USERS\" ROWS 11 TO 15", c[EngineCodes.Firebird]); + } + + [Fact] + public void BasicJoin() { + var q = new Query().From("users").Join("countries", "countries.id", "users.country_id"); + + var c = Compile(q); + + Assert.Equal("SELECT * FROM [users] \nINNER JOIN [countries] ON [countries].[id] = [users].[country_id]", + c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM `users` \nINNER JOIN `countries` ON `countries`.`id` = `users`.`country_id`", + c[EngineCodes.MySql]); + } + + [Theory] + [InlineData("inner join", "INNER JOIN")] + [InlineData("left join", "LEFT JOIN")] + [InlineData("right join", "RIGHT JOIN")] + [InlineData("cross join", "CROSS JOIN")] + public void JoinTypes(string given, string output) { + var q = new Query().From("users") + .Join("countries", "countries.id", "users.country_id", "=", given); + + var c = Compile(q); + + Assert.Equal($"SELECT * FROM [users] \n{output} [countries] ON [countries].[id] = [users].[country_id]", + c[EngineCodes.SqlServer]); + + Assert.Equal($"SELECT * FROM `users` \n{output} `countries` ON `countries`.`id` = `users`.`country_id`", + c[EngineCodes.MySql]); + + Assert.Equal( + $"SELECT * FROM \"users\" \n{output} \"countries\" ON \"countries\".\"id\" = \"users\".\"country_id\"", + c[EngineCodes.PostgreSql]); + + Assert.Equal( + $"SELECT * FROM \"USERS\" \n{output} \"COUNTRIES\" ON \"COUNTRIES\".\"ID\" = \"USERS\".\"COUNTRY_ID\"", + c[EngineCodes.Firebird]); + } + + [Fact] + public void OrWhereRawEscaped() { + var query = new Query("Table").WhereRaw("[MyCol] = ANY(?::int\\[\\])", "{1,2,3}"); + + var c = Compile(query); + + Assert.Equal("SELECT * FROM \"Table\" WHERE \"MyCol\" = ANY('{1,2,3}'::int[])", c[EngineCodes.PostgreSql]); + } + + [Fact] + public void Having() { + var q = new Query("Table1") + .Having("Column1", ">", 1); + var c = Compile(q); + + Assert.Equal("SELECT * FROM [Table1] HAVING [Column1] > 1", c[EngineCodes.SqlServer]); + } + + [Fact] + public void MultipleHaving() { + var q = new Query("Table1") + .Having("Column1", ">", 1) + .Having("Column2", "=", 1); + var c = Compile(q); + + Assert.Equal("SELECT * FROM [Table1] HAVING [Column1] > 1 AND [Column2] = 1", c[EngineCodes.SqlServer]); + } + + [Fact] + public void MultipleOrHaving() { + var q = new Query("Table1") + .Having("Column1", ">", 1) + .OrHaving("Column2", "=", 1); + var c = Compile(q); + + Assert.Equal("SELECT * FROM [Table1] HAVING [Column1] > 1 OR [Column2] = 1", c[EngineCodes.SqlServer]); + } + + [Fact] + public void ShouldUseILikeOnPostgresWhenNonCaseSensitive() { + var q = new Query("Table1") + .WhereLike("Column1", "%Upper Word%", false); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%upper word%'", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM \"Table1\" WHERE \"Column1\" ilike '%Upper Word%'", c[EngineCodes.PostgreSql]); + } + + [Fact] + public void EscapedWhereLike() { + var q = new Query("Table1") + .WhereLike("Column1", @"TestString\%", false, @"\"); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like 'teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void EscapedWhereStarts() { + var q = new Query("Table1") + .WhereStarts("Column1", @"TestString\%", false, @"\"); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like 'teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void EscapedWhereEnds() { + var q = new Query("Table1") + .WhereEnds("Column1", @"TestString\%", false, @"\"); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void EscapedWhereContains() { + var q = new Query("Table1") + .WhereContains("Column1", @"TestString\%", false, @"\"); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void EscapedHavingLike() { + var q = new Query("Table1") + .HavingLike("Column1", @"TestString\%", false, @"\"); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like 'teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void EscapedHavingStarts() { + var q = new Query("Table1") + .HavingStarts("Column1", @"TestString\%", false, @"\"); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like 'teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void EscapedHavingEnds() { + var q = new Query("Table1") + .HavingEnds("Column1", @"TestString\%", false, @"\"); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like '%teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void EscapedHavingContains() { + var q = new Query("Table1") + .HavingContains("Column1", @"TestString\%", false, @"\"); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like '%teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void EscapeClauseThrowsForMultipleCharacters() { + Assert.ThrowsAny(() => { + var q = new Query("Table1") + .HavingContains("Column1", @"TestString\%", false, @"\aa"); + }); + } + + + [Fact] + public void BasicSelectRaw_WithNoTable() { + var q = new Query().SelectRaw("somefunction() as c1"); + + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT somefunction() as c1", c.ToString()); + } + + [Fact] + public void BasicSelect_WithNoTable() { + var q = new Query().Select("c1"); + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [c1]", c.ToString()); + } + + [Fact] + public void BasicSelect_WithNoTableAndWhereClause() { + var q = new Query().Select("c1").Where("p", 1); + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [c1] WHERE [p] = 1", c.ToString()); + } + + [Fact] + public void BasicSelect_WithNoTableWhereRawClause() { + var q = new Query().Select("c1").WhereRaw("1 = 1"); + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [c1] WHERE 1 = 1", c.ToString()); + } + + [Fact] + public void BasicSelectAggregate() { + var q = new Query("Posts").Select("Title") + .SelectAggregate("sum", "ViewCount"); + + var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [Title], SUM([ViewCount]) FROM [Posts]", sqlServer.ToString()); + } + + [Fact] + public void SelectAggregateShouldIgnoreEmptyFilter() { + var q = new Query("Posts").Select("Title") + .SelectAggregate("sum", "ViewCount", q => q); + + var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [Title], SUM([ViewCount]) FROM [Posts]", sqlServer.ToString()); + } + + [Fact] + public void SelectAggregateShouldIgnoreEmptyQueryFilter() { + var q = new Query("Posts").Select("Title") + .SelectAggregate("sum", "ViewCount", new Query()); + + var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [Title], SUM([ViewCount]) FROM [Posts]", sqlServer.ToString()); + } + + [Fact] + public void BasicSelectAggregateWithAlias() { + var q = new Query("Posts").Select("Title") + .SelectAggregate("sum", "ViewCount as TotalViews"); + + var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [Title], SUM([ViewCount]) AS [TotalViews] FROM [Posts]", sqlServer.ToString()); + } + + [Fact] + public void SelectWithFilter() { + var q = new Query("Posts").Select("Title") + .SelectAggregate("sum", "ViewCount as Published_Jan", q => q.Where("Published_Month", "Jan")) + .SelectAggregate("sum", "ViewCount as Published_Feb", q => q.Where("Published_Month", "Feb")); + + var pgSql = Compilers.CompileFor(EngineCodes.PostgreSql, q); + Assert.Equal("SELECT \"Title\", SUM(\"ViewCount\") FILTER (WHERE \"Published_Month\" = 'Jan') AS \"Published_Jan\", SUM(\"ViewCount\") FILTER (WHERE \"Published_Month\" = 'Feb') AS \"Published_Feb\" FROM \"Posts\"", pgSql.ToString()); + + var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [Title], SUM(CASE WHEN [Published_Month] = 'Jan' THEN [ViewCount] END) AS [Published_Jan], SUM(CASE WHEN [Published_Month] = 'Feb' THEN [ViewCount] END) AS [Published_Feb] FROM [Posts]", sqlServer.ToString()); + } + + [Fact] + public void SelectWithExists() { + var q = new Query("Posts").WhereExists( + new Query("Comments").WhereColumns("Comments.PostId", "=", "Posts.Id") + ); + + var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT * FROM [Posts] WHERE EXISTS (SELECT 1 FROM [Comments] WHERE [Comments].[PostId] = [Posts].[Id])", sqlServer.ToString()); + } + + [Fact] + public void SelectWithExists_OmitSelectIsFalse() { + var q = new Query("Posts").WhereExists( + new Query("Comments").Select("Id").WhereColumns("Comments.PostId", "=", "Posts.Id") + ); + + + var compiler = new SqlServerCompiler { + OmitSelectInsideExists = false, + }; + + var sqlServer = compiler.Compile(q).ToString(); + Assert.Equal("SELECT * FROM [Posts] WHERE EXISTS (SELECT [Id] FROM [Comments] WHERE [Comments].[PostId] = [Posts].[Id])", sqlServer.ToString()); + } + +} diff --git a/QueryBuilder.Tests/SqlServer/NestedSelectTests.cs b/QueryBuilder.Tests/SqlServer/NestedSelectTests.cs index 4c591ba2..8775706d 100644 --- a/QueryBuilder.Tests/SqlServer/NestedSelectTests.cs +++ b/QueryBuilder.Tests/SqlServer/NestedSelectTests.cs @@ -1,64 +1,57 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.SqlServer -{ - public class NestedSelectTests : TestSupport - { - private readonly SqlServerCompiler compiler; - - public NestedSelectTests() - { - compiler = Compilers.Get(EngineCodes.SqlServer); - } - - [Fact] - public void Compile_RawSql_WithLimit_ReturnsCorrectQuery() - { - var q = new Query().From("Foo as src").Limit(1); - - var actual = compiler.Compile(q).ToString(); - Assert.Contains("SELECT TOP (1) * FROM [Foo]", actual); - } - - [Fact] - public void SqlCompile_QueryAadNestedLimit_ReturnsQueryWithTop() - { - var q = new Query().From("Foo as src").Select("MyData"); - var n = new Query().From("Bar").Limit(1).Select("MyData"); - q.Select(n, "Bar"); - - var actual = compiler.Compile(q).ToString(); - Assert.Contains("SELECT TOP (1) [MyData] FROM [Bar]", actual); - Assert.Contains("SELECT [MyData], (SELECT TOP (1) [MyData] FROM [Bar]) AS [Bar] FROM [Foo] AS [src]", - actual); - } - - [Fact] - public void SqlCompile_QueryLimitAndNestedLimit_ReturnsQueryWithTop() - { - var q = new Query().From("Foo as src").Limit(1).Select("MyData"); - var n = new Query().From("Bar").Limit(1).Select("MyData"); - q.Select(n, "Bar"); - - - var actual = compiler.Compile(q).ToString(); - Assert.Contains( - "SELECT TOP (1) [MyData], (SELECT TOP (1) [MyData] FROM [Bar]) AS [Bar] FROM [Foo] AS [src]", actual); - } - - [Fact] - public void SqlCompile_QueryLimitAndNestedLimit_BindingValue() - { - var n = new Query().From("Bar"); - var q = new Query().From("Foo").Where("x", true).WhereNotExists(n); - // var q = new Query().From("Foo").Where("C", "c").WhereExists(n).Where("A", "a"); - - var actual = compiler.Compile(q).ToString(); - Assert.Contains("SELECT * FROM [Foo] WHERE [x] = cast(1 as bit) AND NOT EXISTS (SELECT 1 FROM [Bar])", - actual); - // Assert.Contains("SELECT * FROM [Foo] WHERE [C] = 'c' AND EXISTS (SELECT TOP (1) 1 FROM [Bar]) AND [A] = 'a'", actual); - } - } -} +namespace SqlKata.Tests.SqlServer; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class NestedSelectTests : TestSupport { + private readonly SqlServerCompiler compiler; + + public NestedSelectTests() { + compiler = Compilers.Get(EngineCodes.SqlServer); + } + + [Fact] + public void Compile_RawSql_WithLimit_ReturnsCorrectQuery() { + var q = new Query().From("Foo as src").Limit(1); + + var actual = compiler.Compile(q).ToString(); + Assert.Contains("SELECT TOP (1) * FROM [Foo]", actual); + } + + [Fact] + public void SqlCompile_QueryAadNestedLimit_ReturnsQueryWithTop() { + var q = new Query().From("Foo as src").Select("MyData"); + var n = new Query().From("Bar").Limit(1).Select("MyData"); + q.Select(n, "Bar"); + + var actual = compiler.Compile(q).ToString(); + Assert.Contains("SELECT TOP (1) [MyData] FROM [Bar]", actual); + Assert.Contains("SELECT [MyData], (SELECT TOP (1) [MyData] FROM [Bar]) AS [Bar] FROM [Foo] AS [src]", + actual); + } + + [Fact] + public void SqlCompile_QueryLimitAndNestedLimit_ReturnsQueryWithTop() { + var q = new Query().From("Foo as src").Limit(1).Select("MyData"); + var n = new Query().From("Bar").Limit(1).Select("MyData"); + q.Select(n, "Bar"); + + + var actual = compiler.Compile(q).ToString(); + Assert.Contains( + "SELECT TOP (1) [MyData], (SELECT TOP (1) [MyData] FROM [Bar]) AS [Bar] FROM [Foo] AS [src]", actual); + } + + [Fact] + public void SqlCompile_QueryLimitAndNestedLimit_BindingValue() { + var n = new Query().From("Bar"); + var q = new Query().From("Foo").Where("x", true).WhereNotExists(n); + // var q = new Query().From("Foo").Where("C", "c").WhereExists(n).Where("A", "a"); + + var actual = compiler.Compile(q).ToString(); + Assert.Contains("SELECT * FROM [Foo] WHERE [x] = cast(1 as bit) AND NOT EXISTS (SELECT 1 FROM [Bar])", + actual); + // Assert.Contains("SELECT * FROM [Foo] WHERE [C] = 'c' AND EXISTS (SELECT TOP (1) 1 FROM [Bar]) AND [A] = 'a'", actual); + } +} diff --git a/QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs b/QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs index 1cc58060..c3485f25 100644 --- a/QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs +++ b/QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs @@ -1,78 +1,68 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.SqlServer -{ - public class SqlServerLegacyLimitTests : TestSupport - { - private readonly SqlServerCompiler compiler; - - public SqlServerLegacyLimitTests() - { - compiler = Compilers.Get(EngineCodes.SqlServer); - compiler.UseLegacyPagination = true; - } - - [Fact] - public void NoLimitNorOffset() - { - var query = new Query("Table"); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void LimitOnly() - { - var query = new Query("Table").Limit(10); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void OffsetOnly() - { - var query = new Query("Table").Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void LimitAndOffset() - { - var query = new Query("Table").Limit(5).Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void ShouldEmulateOrderByIfNoOrderByProvided() - { - var query = new Query("Table").Limit(5).Offset(20); - - Assert.Contains("ORDER BY (SELECT 0)", compiler.Compile(query).ToString()); - } - - [Fact] - public void ShouldKeepTheOrdersAsIsIfNoPaginationProvided() - { - var query = new Query("Table").OrderBy("Id"); - - Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); - } - - [Fact] - public void ShouldKeepTheOrdersAsIsIfPaginationProvided() - { - var query = new Query("Table").Offset(10).Limit(20).OrderBy("Id"); - - Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); - Assert.DoesNotContain("(SELECT 0)", compiler.Compile(query).ToString()); - } - } -} +namespace SqlKata.Tests.SqlServer; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class SqlServerLegacyLimitTests : TestSupport { + private readonly SqlServerCompiler compiler; + + public SqlServerLegacyLimitTests() { + compiler = Compilers.Get(EngineCodes.SqlServer); + compiler.UseLegacyPagination = true; + } + + [Fact] + public void NoLimitNorOffset() { + var query = new Query("Table"); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void LimitOnly() { + var query = new Query("Table").Limit(10); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void OffsetOnly() { + var query = new Query("Table").Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void LimitAndOffset() { + var query = new Query("Table").Limit(5).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void ShouldEmulateOrderByIfNoOrderByProvided() { + var query = new Query("Table").Limit(5).Offset(20); + + Assert.Contains("ORDER BY (SELECT 0)", compiler.Compile(query).ToString()); + } + + [Fact] + public void ShouldKeepTheOrdersAsIsIfNoPaginationProvided() { + var query = new Query("Table").OrderBy("Id"); + + Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); + } + + [Fact] + public void ShouldKeepTheOrdersAsIsIfPaginationProvided() { + var query = new Query("Table").Offset(10).Limit(20).OrderBy("Id"); + + Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); + Assert.DoesNotContain("(SELECT 0)", compiler.Compile(query).ToString()); + } +} diff --git a/QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs b/QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs index e6fa416b..9cdb3d4b 100644 --- a/QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs +++ b/QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs @@ -1,88 +1,78 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.SqlServer -{ - public class SqlServerLimitTests : TestSupport - { - private readonly SqlServerCompiler compiler; - - public SqlServerLimitTests() - { - compiler = Compilers.Get(EngineCodes.SqlServer); - compiler.UseLegacyPagination = false; - } - - [Fact] - public void NoLimitNorOffset() - { - var query = new Query("Table"); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void LimitOnly() - { - var query = new Query("Table").Limit(10); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); - Assert.Equal(2, ctx.Bindings.Count); - Assert.Equal(0L, ctx.Bindings[0]); - Assert.Equal(10, ctx.Bindings[1]); - } - - [Fact] - public void OffsetOnly() - { - var query = new Query("Table").Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.EndsWith("OFFSET ? ROWS", compiler.CompileLimit(ctx)); - - Assert.Single(ctx.Bindings); - Assert.Equal(20L, ctx.Bindings[0]); - } - - [Fact] - public void LimitAndOffset() - { - var query = new Query("Table").Limit(5).Offset(20); - var ctx = new SqlResult("?", "\\") {Query = query}; - - Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); - - Assert.Equal(2, ctx.Bindings.Count); - Assert.Equal(20L, ctx.Bindings[0]); - Assert.Equal(5, ctx.Bindings[1]); - } - - [Fact] - public void ShouldEmulateOrderByIfNoOrderByProvided() - { - var query = new Query("Table").Limit(5).Offset(20); - - Assert.Contains("ORDER BY (SELECT 0)", compiler.Compile(query).ToString()); - } - - [Fact] - public void ShouldKeepTheOrdersAsIsIfNoPaginationProvided() - { - var query = new Query("Table").OrderBy("Id"); - - Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); - } - - [Fact] - public void ShouldKeepTheOrdersAsIsIfPaginationProvided() - { - var query = new Query("Table").Offset(10).Limit(20).OrderBy("Id"); - - Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); - Assert.DoesNotContain("(SELECT 0)", compiler.Compile(query).ToString()); - } - } -} +namespace SqlKata.Tests.SqlServer; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class SqlServerLimitTests : TestSupport { + private readonly SqlServerCompiler compiler; + + public SqlServerLimitTests() { + compiler = Compilers.Get(EngineCodes.SqlServer); + compiler.UseLegacyPagination = false; + } + + [Fact] + public void NoLimitNorOffset() { + var query = new Query("Table"); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void LimitOnly() { + var query = new Query("Table").Limit(10); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); + Assert.Equal(2, ctx.Bindings.Count); + Assert.Equal(0L, ctx.Bindings[0]); + Assert.Equal(10, ctx.Bindings[1]); + } + + [Fact] + public void OffsetOnly() { + var query = new Query("Table").Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.EndsWith("OFFSET ? ROWS", compiler.CompileLimit(ctx)); + + Assert.Single(ctx.Bindings); + Assert.Equal(20L, ctx.Bindings[0]); + } + + [Fact] + public void LimitAndOffset() { + var query = new Query("Table").Limit(5).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); + + Assert.Equal(2, ctx.Bindings.Count); + Assert.Equal(20L, ctx.Bindings[0]); + Assert.Equal(5, ctx.Bindings[1]); + } + + [Fact] + public void ShouldEmulateOrderByIfNoOrderByProvided() { + var query = new Query("Table").Limit(5).Offset(20); + + Assert.Contains("ORDER BY (SELECT 0)", compiler.Compile(query).ToString()); + } + + [Fact] + public void ShouldKeepTheOrdersAsIsIfNoPaginationProvided() { + var query = new Query("Table").OrderBy("Id"); + + Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); + } + + [Fact] + public void ShouldKeepTheOrdersAsIsIfPaginationProvided() { + var query = new Query("Table").Offset(10).Limit(20).OrderBy("Id"); + + Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); + Assert.DoesNotContain("(SELECT 0)", compiler.Compile(query).ToString()); + } +} diff --git a/QueryBuilder.Tests/SqlServer/SqlServerTests.cs b/QueryBuilder.Tests/SqlServer/SqlServerTests.cs index 5ab15634..ec487317 100644 --- a/QueryBuilder.Tests/SqlServer/SqlServerTests.cs +++ b/QueryBuilder.Tests/SqlServer/SqlServerTests.cs @@ -1,78 +1,69 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.SqlServer -{ - public class SqlServerTests : TestSupport - { - private readonly SqlServerCompiler compiler; - - public SqlServerTests() - { - compiler = Compilers.Get(EngineCodes.SqlServer); - } - - - [Fact] - public void SqlServerTop() - { - var query = new Query("table").Limit(1); - var result = compiler.Compile(query); - Assert.Equal("SELECT TOP (@p0) * FROM [table]", result.Sql); - } - - - [Fact] - public void SqlServerSelectWithParameterPlaceHolder() - { - var query = new Query("table").Select("Column\\?"); - var result = compiler.Compile(query); - Assert.Equal("SELECT [Column\\?] FROM [table]", result.Sql); - } - - [Fact] - public void SqlServerTopWithDistinct() - { - var query = new Query("table").Limit(1).Distinct(); - var result = compiler.Compile(query); - Assert.Equal("SELECT DISTINCT TOP (@p0) * FROM [table]", result.Sql); - } - - - [Theory()] - [InlineData(-100)] - [InlineData(0)] - public void OffsetSqlServer_Should_Be_Ignored_If_Zero_Or_Negative(int offset) - { - var q = new Query().From("users").Offset(offset); - var c = Compilers.CompileFor(EngineCodes.SqlServer, q); - - Assert.Equal("SELECT * FROM [users]", c.ToString()); - } - - [Fact] - public void SqlServerSelectWithParameterPlaceHolderEscaped() - { - var query = new Query("table").Select("Column\\?"); - var result = compiler.Compile(query); - Assert.Equal("SELECT [Column?] FROM [table]", result.ToString()); - } - - [Theory()] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(100)] - [InlineData(1000000)] - public void OffsetSqlServer_Should_Be_Incremented_By_One(int offset) - { - var q = new Query().From("users").Offset(offset); - var c = Compilers.CompileFor(EngineCodes.SqlServer, q); - Assert.Equal( - "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [users]) AS [results_wrapper] WHERE [row_num] >= " + - (offset + 1), c.ToString()); - } - } -} +namespace SqlKata.Tests.SqlServer; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class SqlServerTests : TestSupport { + private readonly SqlServerCompiler compiler; + + public SqlServerTests() { + compiler = Compilers.Get(EngineCodes.SqlServer); + } + + + [Fact] + public void SqlServerTop() { + var query = new Query("table").Limit(1); + var result = compiler.Compile(query); + Assert.Equal("SELECT TOP (@p0) * FROM [table]", result.Sql); + } + + + [Fact] + public void SqlServerSelectWithParameterPlaceHolder() { + var query = new Query("table").Select("Column\\?"); + var result = compiler.Compile(query); + Assert.Equal("SELECT [Column\\?] FROM [table]", result.Sql); + } + + [Fact] + public void SqlServerTopWithDistinct() { + var query = new Query("table").Limit(1).Distinct(); + var result = compiler.Compile(query); + Assert.Equal("SELECT DISTINCT TOP (@p0) * FROM [table]", result.Sql); + } + + + [Theory()] + [InlineData(-100)] + [InlineData(0)] + public void OffsetSqlServer_Should_Be_Ignored_If_Zero_Or_Negative(int offset) { + var q = new Query().From("users").Offset(offset); + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + + Assert.Equal("SELECT * FROM [users]", c.ToString()); + } + + [Fact] + public void SqlServerSelectWithParameterPlaceHolderEscaped() { + var query = new Query("table").Select("Column\\?"); + var result = compiler.Compile(query); + Assert.Equal("SELECT [Column?] FROM [table]", result.ToString()); + } + + [Theory()] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(100)] + [InlineData(1000000)] + public void OffsetSqlServer_Should_Be_Incremented_By_One(int offset) { + var q = new Query().From("users").Offset(offset); + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal( + "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [users]) AS [results_wrapper] WHERE [row_num] >= " + + (offset + 1), c.ToString()); + } +} diff --git a/QueryBuilder.Tests/Sqlite/SqliteLimitTests.cs b/QueryBuilder.Tests/Sqlite/SqliteLimitTests.cs index 837a6654..3af56558 100644 --- a/QueryBuilder.Tests/Sqlite/SqliteLimitTests.cs +++ b/QueryBuilder.Tests/Sqlite/SqliteLimitTests.cs @@ -1,58 +1,51 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests.Sqlite -{ - public class SqliteLimitTests : TestSupport - { - private readonly SqliteCompiler compiler; - - public SqliteLimitTests() - { - compiler = Compilers.Get(EngineCodes.Sqlite); - } - - [Fact] - public void WithNoLimitNorOffset() - { - var query = new Query("Table"); - var ctx = new SqlResult("?", "\\") { Query = query }; - - Assert.Null(compiler.CompileLimit(ctx)); - } - - [Fact] - public void WithNoOffset() - { - var query = new Query("Table").Limit(10); - var ctx = new SqlResult("?", "\\") { Query = query }; - - Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); - Assert.Equal(10, ctx.Bindings[0]); - } - - [Fact] - public void WithNoLimit() - { - var query = new Query("Table").Offset(20); - var ctx = new SqlResult("?", "\\") { Query = query }; - - Assert.Equal("LIMIT -1 OFFSET ?", compiler.CompileLimit(ctx)); - Assert.Equal(20L, ctx.Bindings[0]); - Assert.Single(ctx.Bindings); - } - - [Fact] - public void WithLimitAndOffset() - { - var query = new Query("Table").Limit(5).Offset(20); - var ctx = new SqlResult("?", "\\") { Query = query }; - - Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); - Assert.Equal(5, ctx.Bindings[0]); - Assert.Equal(20L, ctx.Bindings[1]); - Assert.Equal(2, ctx.Bindings.Count); - } - } -} +namespace SqlKata.Tests.Sqlite; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class SqliteLimitTests : TestSupport { + private readonly SqliteCompiler compiler; + + public SqliteLimitTests() { + compiler = Compilers.Get(EngineCodes.Sqlite); + } + + [Fact] + public void WithNoLimitNorOffset() { + var query = new Query("Table"); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void WithNoOffset() { + var query = new Query("Table").Limit(10); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); + Assert.Equal(10, ctx.Bindings[0]); + } + + [Fact] + public void WithNoLimit() { + var query = new Query("Table").Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("LIMIT -1 OFFSET ?", compiler.CompileLimit(ctx)); + Assert.Equal(20L, ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void WithLimitAndOffset() { + var query = new Query("Table").Limit(5).Offset(20); + var ctx = new SqlResult("?", "\\") { Query = query }; + + Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); + Assert.Equal(5, ctx.Bindings[0]); + Assert.Equal(20L, ctx.Bindings[1]); + Assert.Equal(2, ctx.Bindings.Count); + } +} diff --git a/QueryBuilder.Tests/UpdateTests.cs b/QueryBuilder.Tests/UpdateTests.cs index bf3dd7d9..c0b0b470 100644 --- a/QueryBuilder.Tests/UpdateTests.cs +++ b/QueryBuilder.Tests/UpdateTests.cs @@ -1,319 +1,291 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Dynamic; -using System.Linq; -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests -{ - public class UpdateTests : TestSupport - { - private class Book - { - public Book(string name, string author, decimal price = 1.0m, string color = null) - { - this.Name = name ?? throw new ArgumentNullException(nameof(name)); - this.BookPrice = price; - this.color = color; - this.BookAuthor = author; - } - - public string Name { get; set; } - - [Column("Author")] - public string BookAuthor { get; set; } - - [Column("Price")] - public decimal BookPrice { get; set; } - - [Ignore] - public string color { get; set; } - } - - private class OrderProductComposite - { - public OrderProductComposite(string orderid, string productid, int quantity) - { - OrderId = orderid; - ProductId = productid; - Quantity = quantity; - Foo = "baz"; - } - - [Key("OrdId")] - public string OrderId { get; set; } - - [Key] - public string ProductId { get; set; } - - public int Quantity { get; set; } - - [Column("Faa")] - public string Foo { get; set; } - } - - [Fact] - public void UpdateObject() - { - var query = new Query("Table").AsUpdate(new - { - Name = "The User", - Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc), - }); - - var c = Compile(query); - - Assert.Equal( - "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "UPDATE \"TABLE\" SET \"NAME\" = 'The User', \"AGE\" = '2018-01-01'", - c[EngineCodes.Firebird]); - } - - [Fact] - public void UpdateWithNullValues() - { - var query = new Query("Books").Where("Id", 1).AsUpdate( - new[] { "Author", "Date", "Version" }, - new object[] { "Author 1", null, null } - ); - - var c = Compile(query); - - Assert.Equal( - "UPDATE [Books] SET [Author] = 'Author 1', [Date] = NULL, [Version] = NULL WHERE [Id] = 1", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "UPDATE \"BOOKS\" SET \"AUTHOR\" = 'Author 1', \"DATE\" = NULL, \"VERSION\" = NULL WHERE \"ID\" = 1", - c[EngineCodes.Firebird]); - } - - [Fact] - public void UpdateWithEmptyString() - { - var query = new Query("Books").Where("Id", 1).AsUpdate( - new[] { "Author", "Description" }, - new object[] { "Author 1", "" } - ); - - var c = Compile(query); - - Assert.Equal("UPDATE [Books] SET [Author] = 'Author 1', [Description] = '' WHERE [Id] = 1", c[EngineCodes.SqlServer]); - - Assert.Equal("UPDATE \"BOOKS\" SET \"AUTHOR\" = 'Author 1', \"DESCRIPTION\" = '' WHERE \"ID\" = 1", c[EngineCodes.Firebird]); - } - - [Fact] - public void UpdateWithCte() - { - var now = DateTime.UtcNow.ToString("yyyy-MM-dd"); - - var query = new Query("Books") - .With("OldBooks", q => q.From("Books").Where("Date", "<", now)) - .Where("Price", ">", 100) - .AsUpdate(new Dictionary - { - {"Price", "150"} - }); - - var c = Compile(query); - - Assert.Equal( - $"WITH [OldBooks] AS (SELECT * FROM [Books] WHERE [Date] < '{now}')\nUPDATE [Books] SET [Price] = '150' WHERE [Price] > 100", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void UpdateWithIgnoreAndColumnProperties() - { - var book = new Book(name: $"SqlKataBook", author: "Kata", color: $"red", price: 100m); - var query = new Query("Book").AsUpdate(book); - - var c = Compile(query); - - Assert.Equal( - "UPDATE [Book] SET [Name] = 'SqlKataBook', [Author] = 'Kata', [Price] = 100", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "UPDATE \"BOOK\" SET \"NAME\" = 'SqlKataBook', \"AUTHOR\" = 'Kata', \"PRICE\" = 100", - c[EngineCodes.Firebird]); - } - - [Fact] - public void UpdateWithKeyAttribute() - { - var order = new OrderProductComposite("ORD01", "PROD02", 20); - var query = new Query("OrderProductComposite").AsUpdate(order); - - var c = Compile(query); - - Assert.Equal( - "UPDATE [OrderProductComposite] SET [OrdId] = 'ORD01', [ProductId] = 'PROD02', [Quantity] = 20, [Faa] = 'baz' WHERE [OrdId] = 'ORD01' AND [ProductId] = 'PROD02'", - c[EngineCodes.SqlServer]); - - Assert.Equal( - "UPDATE \"ORDERPRODUCTCOMPOSITE\" SET \"ORDID\" = 'ORD01', \"PRODUCTID\" = 'PROD02', \"QUANTITY\" = 20, \"FAA\" = 'baz' WHERE \"ORDID\" = 'ORD01' AND \"PRODUCTID\" = 'PROD02'", - c[EngineCodes.Firebird]); - } - - [Fact] - public void UpdateFromRaw() - { - var query = new Query().FromRaw("Table.With.Dots").AsUpdate(new - { - Name = "The User", - }); - - var c = Compile(query); - - Assert.Equal( - "UPDATE Table.With.Dots SET [Name] = 'The User'", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void UpdateFromQueryShouldFail() - { - var query = new Query().From(new Query("InnerTable")).AsUpdate(new - { - Name = "The User", - }); - - Assert.Throws(() => - { - Compile(query); - }); - } - - [Fact] - public void update_should_compile_literal_without_parameters_holders() - { - var query = new Query("MyTable").AsUpdate(new - { - Name = "The User", - Address = new UnsafeLiteral("@address") - }); - - var compiler = new SqlServerCompiler(); - var result = compiler.Compile(query); - - Assert.Equal( - "UPDATE [MyTable] SET [Name] = ?, [Address] = @address", - result.RawSql); - } - - [Fact] - public void UpdateUsingKeyValuePairs() - { - var dictionaryUser = new Dictionary - { - { "Name", "The User" }, - { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, - } - .ToArray(); - - var query = new Query("Table") - .AsUpdate(dictionaryUser); - - var c = Compile(query); - - Assert.Equal( - "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void UpdateUsingDictionary() - { - var dictionaryUser = new Dictionary { - { "Name", "The User" }, - { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, - }; - - var query = new Query("Table") - .AsUpdate(dictionaryUser); - - var c = Compile(query); - - Assert.Equal( - "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void UpdateUsingReadOnlyDictionary() - { - var dictionaryUser = new ReadOnlyDictionary( - new Dictionary - { - { "Name", "The User" }, - { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, - }); - - var query = new Query("Table") - .AsUpdate(dictionaryUser); - - var c = Compile(query); - - Assert.Equal( - "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void UpdateUsingExpandoObject() - { - dynamic expandoUser = new ExpandoObject(); - expandoUser.Name = "The User"; - expandoUser.Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - var query = new Query("Table") - .AsUpdate(expandoUser); - - var c = Compile(query); - - Assert.Equal( - "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", - c[EngineCodes.SqlServer]); - } - - [Fact] - public void IncrementUpdate() - { - var query = new Query("Table").AsIncrement("Total"); - var c = Compile(query); - Assert.Equal("UPDATE [Table] SET [Total] = [Total] + 1", c[EngineCodes.SqlServer]); - } - - [Fact] - public void IncrementUpdateWithValue() - { - var query = new Query("Table").AsIncrement("Total", 2); - var c = Compile(query); - Assert.Equal("UPDATE [Table] SET [Total] = [Total] + 2", c[EngineCodes.SqlServer]); - } - - [Fact] - public void IncrementUpdateWithWheres() - { - var query = new Query("Table").Where("Name", "A").AsIncrement("Total", 2); - var c = Compile(query); - Assert.Equal("UPDATE [Table] SET [Total] = [Total] + 2 WHERE [Name] = 'A'", c[EngineCodes.SqlServer]); - } - - [Fact] - public void DecrementUpdate() - { - var query = new Query("Table").Where("Name", "A").AsDecrement("Total", 2); - var c = Compile(query); - Assert.Equal("UPDATE [Table] SET [Total] = [Total] - 2 WHERE [Name] = 'A'", c[EngineCodes.SqlServer]); - } - } -} +namespace SqlKata.Tests; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Dynamic; +using System.Linq; +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class UpdateTests : TestSupport { + private class Book { + public Book(string name, string author, decimal price = 1.0m, string color = null) { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.BookPrice = price; + this.color = color; + this.BookAuthor = author; + } + + public string Name { get; set; } + + [Column("Author")] + public string BookAuthor { get; set; } + + [Column("Price")] + public decimal BookPrice { get; set; } + + [Ignore] + public string color { get; set; } + } + + private class OrderProductComposite { + public OrderProductComposite(string orderid, string productid, int quantity) { + OrderId = orderid; + ProductId = productid; + Quantity = quantity; + Foo = "baz"; + } + + [Key("OrdId")] + public string OrderId { get; set; } + + [Key] + public string ProductId { get; set; } + + public int Quantity { get; set; } + + [Column("Faa")] + public string Foo { get; set; } + } + + [Fact] + public void UpdateObject() { + var query = new Query("Table").AsUpdate(new { + Name = "The User", + Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc), + }); + + var c = Compile(query); + + Assert.Equal( + "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "UPDATE \"TABLE\" SET \"NAME\" = 'The User', \"AGE\" = '2018-01-01'", + c[EngineCodes.Firebird]); + } + + [Fact] + public void UpdateWithNullValues() { + var query = new Query("Books").Where("Id", 1).AsUpdate( + new[] { "Author", "Date", "Version" }, + new object[] { "Author 1", null, null } + ); + + var c = Compile(query); + + Assert.Equal( + "UPDATE [Books] SET [Author] = 'Author 1', [Date] = NULL, [Version] = NULL WHERE [Id] = 1", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "UPDATE \"BOOKS\" SET \"AUTHOR\" = 'Author 1', \"DATE\" = NULL, \"VERSION\" = NULL WHERE \"ID\" = 1", + c[EngineCodes.Firebird]); + } + + [Fact] + public void UpdateWithEmptyString() { + var query = new Query("Books").Where("Id", 1).AsUpdate( + new[] { "Author", "Description" }, + new object[] { "Author 1", "" } + ); + + var c = Compile(query); + + Assert.Equal("UPDATE [Books] SET [Author] = 'Author 1', [Description] = '' WHERE [Id] = 1", c[EngineCodes.SqlServer]); + + Assert.Equal("UPDATE \"BOOKS\" SET \"AUTHOR\" = 'Author 1', \"DESCRIPTION\" = '' WHERE \"ID\" = 1", c[EngineCodes.Firebird]); + } + + [Fact] + public void UpdateWithCte() { + var now = DateTime.UtcNow.ToString("yyyy-MM-dd"); + + var query = new Query("Books") + .With("OldBooks", q => q.From("Books").Where("Date", "<", now)) + .Where("Price", ">", 100) + .AsUpdate(new Dictionary + { + {"Price", "150"} + }); + + var c = Compile(query); + + Assert.Equal( + $"WITH [OldBooks] AS (SELECT * FROM [Books] WHERE [Date] < '{now}')\nUPDATE [Books] SET [Price] = '150' WHERE [Price] > 100", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void UpdateWithIgnoreAndColumnProperties() { + var book = new Book(name: $"SqlKataBook", author: "Kata", color: $"red", price: 100m); + var query = new Query("Book").AsUpdate(book); + + var c = Compile(query); + + Assert.Equal( + "UPDATE [Book] SET [Name] = 'SqlKataBook', [Author] = 'Kata', [Price] = 100", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "UPDATE \"BOOK\" SET \"NAME\" = 'SqlKataBook', \"AUTHOR\" = 'Kata', \"PRICE\" = 100", + c[EngineCodes.Firebird]); + } + + [Fact] + public void UpdateWithKeyAttribute() { + var order = new OrderProductComposite("ORD01", "PROD02", 20); + var query = new Query("OrderProductComposite").AsUpdate(order); + + var c = Compile(query); + + Assert.Equal( + "UPDATE [OrderProductComposite] SET [OrdId] = 'ORD01', [ProductId] = 'PROD02', [Quantity] = 20, [Faa] = 'baz' WHERE [OrdId] = 'ORD01' AND [ProductId] = 'PROD02'", + c[EngineCodes.SqlServer]); + + Assert.Equal( + "UPDATE \"ORDERPRODUCTCOMPOSITE\" SET \"ORDID\" = 'ORD01', \"PRODUCTID\" = 'PROD02', \"QUANTITY\" = 20, \"FAA\" = 'baz' WHERE \"ORDID\" = 'ORD01' AND \"PRODUCTID\" = 'PROD02'", + c[EngineCodes.Firebird]); + } + + [Fact] + public void UpdateFromRaw() { + var query = new Query().FromRaw("Table.With.Dots").AsUpdate(new { + Name = "The User", + }); + + var c = Compile(query); + + Assert.Equal( + "UPDATE Table.With.Dots SET [Name] = 'The User'", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void UpdateFromQueryShouldFail() { + var query = new Query().From(new Query("InnerTable")).AsUpdate(new { + Name = "The User", + }); + + Assert.Throws(() => { + Compile(query); + }); + } + + [Fact] + public void update_should_compile_literal_without_parameters_holders() { + var query = new Query("MyTable").AsUpdate(new { + Name = "The User", + Address = new UnsafeLiteral("@address") + }); + + var compiler = new SqlServerCompiler(); + var result = compiler.Compile(query); + + Assert.Equal( + "UPDATE [MyTable] SET [Name] = ?, [Address] = @address", + result.RawSql); + } + + [Fact] + public void UpdateUsingKeyValuePairs() { + var dictionaryUser = new Dictionary + { + { "Name", "The User" }, + { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, + } + .ToArray(); + + var query = new Query("Table") + .AsUpdate(dictionaryUser); + + var c = Compile(query); + + Assert.Equal( + "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void UpdateUsingDictionary() { + var dictionaryUser = new Dictionary { + { "Name", "The User" }, + { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, + }; + + var query = new Query("Table") + .AsUpdate(dictionaryUser); + + var c = Compile(query); + + Assert.Equal( + "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void UpdateUsingReadOnlyDictionary() { + var dictionaryUser = new ReadOnlyDictionary( + new Dictionary + { + { "Name", "The User" }, + { "Age", new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, + }); + + var query = new Query("Table") + .AsUpdate(dictionaryUser); + + var c = Compile(query); + + Assert.Equal( + "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void UpdateUsingExpandoObject() { + dynamic expandoUser = new ExpandoObject(); + expandoUser.Name = "The User"; + expandoUser.Age = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + var query = new Query("Table") + .AsUpdate(expandoUser); + + var c = Compile(query); + + Assert.Equal( + "UPDATE [Table] SET [Name] = 'The User', [Age] = '2018-01-01'", + c[EngineCodes.SqlServer]); + } + + [Fact] + public void IncrementUpdate() { + var query = new Query("Table").AsIncrement("Total"); + var c = Compile(query); + Assert.Equal("UPDATE [Table] SET [Total] = [Total] + 1", c[EngineCodes.SqlServer]); + } + + [Fact] + public void IncrementUpdateWithValue() { + var query = new Query("Table").AsIncrement("Total", 2); + var c = Compile(query); + Assert.Equal("UPDATE [Table] SET [Total] = [Total] + 2", c[EngineCodes.SqlServer]); + } + + [Fact] + public void IncrementUpdateWithWheres() { + var query = new Query("Table").Where("Name", "A").AsIncrement("Total", 2); + var c = Compile(query); + Assert.Equal("UPDATE [Table] SET [Total] = [Total] + 2 WHERE [Name] = 'A'", c[EngineCodes.SqlServer]); + } + + [Fact] + public void DecrementUpdate() { + var query = new Query("Table").Where("Name", "A").AsDecrement("Total", 2); + var c = Compile(query); + Assert.Equal("UPDATE [Table] SET [Total] = [Total] - 2 WHERE [Name] = 'A'", c[EngineCodes.SqlServer]); + } +} diff --git a/QueryBuilder.Tests/WhereTests.cs b/QueryBuilder.Tests/WhereTests.cs index 0c1254b2..3c8d8210 100644 --- a/QueryBuilder.Tests/WhereTests.cs +++ b/QueryBuilder.Tests/WhereTests.cs @@ -1,33 +1,29 @@ -using SqlKata.Compilers; -using SqlKata.Tests.Infrastructure; -using Xunit; - -namespace SqlKata.Tests -{ - public class WhereTests : TestSupport - { - [Fact] - public void GroupedWhereFilters() - { - var q = new Query("Table1") - .Where(q => q.Or().Where("Column1", 10).Or().Where("Column2", 20)) - .Where("Column3", 30); - - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM ""Table1"" WHERE (""Column1"" = 10 OR ""Column2"" = 20) AND ""Column3"" = 30", c[EngineCodes.PostgreSql]); - } - - [Fact] - public void GroupedHavingFilters() - { - var q = new Query("Table1") - .Having(q => q.Or().HavingRaw("SUM([Column1]) = ?", 10).Or().HavingRaw("SUM([Column2]) = ?", 20)) - .HavingRaw("SUM([Column3]) = ?", 30); - - var c = Compile(q); - - Assert.Equal(@"SELECT * FROM ""Table1"" HAVING (SUM(""Column1"") = 10 OR SUM(""Column2"") = 20) AND SUM(""Column3"") = 30", c[EngineCodes.PostgreSql]); - } - } -} +namespace SqlKata.Tests; + +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +public class WhereTests : TestSupport { + [Fact] + public void GroupedWhereFilters() { + var q = new Query("Table1") + .Where(q => q.Or().Where("Column1", 10).Or().Where("Column2", 20)) + .Where("Column3", 30); + + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM ""Table1"" WHERE (""Column1"" = 10 OR ""Column2"" = 20) AND ""Column3"" = 30", c[EngineCodes.PostgreSql]); + } + + [Fact] + public void GroupedHavingFilters() { + var q = new Query("Table1") + .Having(q => q.Or().HavingRaw("SUM([Column1]) = ?", 10).Or().HavingRaw("SUM([Column2]) = ?", 20)) + .HavingRaw("SUM([Column3]) = ?", 30); + + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM ""Table1"" HAVING (SUM(""Column1"") = 10 OR SUM(""Column2"") = 20) AND SUM(""Column3"") = 30", c[EngineCodes.PostgreSql]); + } +} diff --git a/QueryBuilder/Base.Where.cs b/QueryBuilder/Base.Where.cs index c40cc40a..fe6b6ae2 100644 --- a/QueryBuilder/Base.Where.cs +++ b/QueryBuilder/Base.Where.cs @@ -1,699 +1,570 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace SqlKata -{ - public abstract partial class BaseQuery - { - public Q Where(string column, string op, object value) - { - - // If the value is "null", we will just assume the developer wants to add a - // where null clause to the query. So, we will allow a short-cut here to - // that method for convenience so the developer doesn't have to check. - if (value == null) - { - return Not(op != "=").WhereNull(column); - } - - if (value is bool boolValue) - { - if (op != "=") - { - Not(); - } - - return boolValue ? WhereTrue(column) : WhereFalse(column); - } - - return AddComponent("where", new BasicCondition - { - Column = column, - Operator = op, - Value = value, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q WhereNot(string column, string op, object value) - { - return Not().Where(column, op, value); - } - - public Q OrWhere(string column, string op, object value) - { - return Or().Where(column, op, value); - } - - public Q OrWhereNot(string column, string op, object value) - { - return this.Or().Not().Where(column, op, value); - } - - public Q Where(string column, object value) - { - return Where(column, "=", value); - } - public Q WhereNot(string column, object value) - { - return WhereNot(column, "=", value); - } - public Q OrWhere(string column, object value) - { - return OrWhere(column, "=", value); - } - public Q OrWhereNot(string column, object value) - { - return OrWhereNot(column, "=", value); - } - - /// - /// Perform a where constraint - /// - /// - /// - public Q Where(object constraints) - { - var dictionary = new Dictionary(); - - foreach (var item in constraints.GetType().GetRuntimeProperties()) - { - dictionary.Add(item.Name, item.GetValue(constraints)); - } - - return Where(dictionary); - } - - public Q Where(IEnumerable> values) - { - var query = (Q)this; - var orFlag = GetOr(); - var notFlag = GetNot(); - - foreach (var tuple in values) - { - if (orFlag) - { - query = query.Or(); - } - else - { - query.And(); - } - - query = this.Not(notFlag).Where(tuple.Key, tuple.Value); - } - - return query; - } - - public Q WhereRaw(string sql, params object[] bindings) - { - return AddComponent("where", new RawCondition - { - Expression = sql, - Bindings = bindings, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q OrWhereRaw(string sql, params object[] bindings) - { - return Or().WhereRaw(sql, bindings); - } - - /// - /// Apply a nested where clause - /// - /// - /// - public Q Where(Func callback) - { - var query = callback.Invoke(NewChild()); - - // omit empty queries - if (!query.Clauses.Where(x => x.Component == "where").Any()) - { - return (Q)this; - } - - return AddComponent("where", new NestedCondition - { - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); - } - - public Q WhereNot(Func callback) - { - return Not().Where(callback); - } - - public Q OrWhere(Func callback) - { - return Or().Where(callback); - } - - public Q OrWhereNot(Func callback) - { - return Not().Or().Where(callback); - } - - public Q WhereColumns(string first, string op, string second) - { - return AddComponent("where", new TwoColumnsCondition - { - First = first, - Second = second, - Operator = op, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q OrWhereColumns(string first, string op, string second) - { - return Or().WhereColumns(first, op, second); - } - - public Q WhereNull(string column) - { - return AddComponent("where", new NullCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q WhereNotNull(string column) - { - return Not().WhereNull(column); - } - - public Q OrWhereNull(string column) - { - return this.Or().WhereNull(column); - } - - public Q OrWhereNotNull(string column) - { - return Or().Not().WhereNull(column); - } - - public Q WhereTrue(string column) - { - return AddComponent("where", new BooleanCondition - { - Column = column, - Value = true, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q OrWhereTrue(string column) - { - return Or().WhereTrue(column); - } - - public Q WhereFalse(string column) - { - return AddComponent("where", new BooleanCondition - { - Column = column, - Value = false, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q OrWhereFalse(string column) - { - return Or().WhereFalse(column); - } - - public Q WhereLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return AddComponent("where", new BasicStringCondition - { - Operator = "like", - Column = column, - Value = value, - CaseSensitive = caseSensitive, - EscapeCharacter = escapeCharacter, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q WhereNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Not().WhereLike(column, value, caseSensitive, escapeCharacter); - } - - public Q OrWhereLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().WhereLike(column, value, caseSensitive, escapeCharacter); - } - - public Q OrWhereNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().Not().WhereLike(column, value, caseSensitive, escapeCharacter); - } - public Q WhereStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return AddComponent("where", new BasicStringCondition - { - Operator = "starts", - Column = column, - Value = value, - CaseSensitive = caseSensitive, - EscapeCharacter = escapeCharacter, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q WhereNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Not().WhereStarts(column, value, caseSensitive, escapeCharacter); - } - - public Q OrWhereStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().WhereStarts(column, value, caseSensitive, escapeCharacter); - } - - public Q OrWhereNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().Not().WhereStarts(column, value, caseSensitive, escapeCharacter); - } - - public Q WhereEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return AddComponent("where", new BasicStringCondition - { - Operator = "ends", - Column = column, - Value = value, - CaseSensitive = caseSensitive, - EscapeCharacter = escapeCharacter, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q WhereNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Not().WhereEnds(column, value, caseSensitive, escapeCharacter); - } - - public Q OrWhereEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().WhereEnds(column, value, caseSensitive, escapeCharacter); - } - - public Q OrWhereNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().Not().WhereEnds(column, value, caseSensitive, escapeCharacter); - } - - public Q WhereContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return AddComponent("where", new BasicStringCondition - { - Operator = "contains", - Column = column, - Value = value, - CaseSensitive = caseSensitive, - EscapeCharacter = escapeCharacter, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Q WhereNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Not().WhereContains(column, value, caseSensitive, escapeCharacter); - } - - public Q OrWhereContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().WhereContains(column, value, caseSensitive, escapeCharacter); - } - - public Q OrWhereNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().Not().WhereContains(column, value, caseSensitive, escapeCharacter); - } - - public Q WhereBetween(string column, T lower, T higher) - { - return AddComponent("where", new BetweenCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Lower = lower, - Higher = higher - }); - } - - public Q OrWhereBetween(string column, T lower, T higher) - { - return Or().WhereBetween(column, lower, higher); - } - public Q WhereNotBetween(string column, T lower, T higher) - { - return Not().WhereBetween(column, lower, higher); - } - public Q OrWhereNotBetween(string column, T lower, T higher) - { - return Or().Not().WhereBetween(column, lower, higher); - } - - public Q WhereIn(string column, IEnumerable values) - { - - // If the developer has passed a string they most likely want a List - // since string is considered as List - if (values is string) - { - string val = values as string; - - return AddComponent("where", new InCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Values = new List { val } - }); - } - - return AddComponent("where", new InCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Values = values.Distinct().ToList() - }); - - - } - - public Q OrWhereIn(string column, IEnumerable values) - { - return Or().WhereIn(column, values); - } - - public Q WhereNotIn(string column, IEnumerable values) - { - return Not().WhereIn(column, values); - } - - public Q OrWhereNotIn(string column, IEnumerable values) - { - return Or().Not().WhereIn(column, values); - } - - - public Q WhereIn(string column, Query query) - { - return AddComponent("where", new InQueryCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Query = query, - }); - } - public Q WhereIn(string column, Func callback) - { - var query = callback.Invoke(new Query().SetParent(this)); - - return WhereIn(column, query); - } - - public Q OrWhereIn(string column, Query query) - { - return Or().WhereIn(column, query); - } - - public Q OrWhereIn(string column, Func callback) - { - return Or().WhereIn(column, callback); - } - public Q WhereNotIn(string column, Query query) - { - return Not().WhereIn(column, query); - } - - public Q WhereNotIn(string column, Func callback) - { - return Not().WhereIn(column, callback); - } - - public Q OrWhereNotIn(string column, Query query) - { - return Or().Not().WhereIn(column, query); - } - - public Q OrWhereNotIn(string column, Func callback) - { - return Or().Not().WhereIn(column, callback); - } - - - /// - /// Perform a sub query where clause - /// - /// - /// - /// - /// - public Q Where(string column, string op, Func callback) - { - var query = callback.Invoke(NewChild()); - - return Where(column, op, query); - } - - public Q Where(string column, string op, Query query) - { - return AddComponent("where", new QueryCondition - { - Column = column, - Operator = op, - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); - } - - public Q WhereSub(Query query, object value) - { - return WhereSub(query, "=", value); - } - - public Q WhereSub(Query query, string op, object value) - { - return AddComponent("where", new SubQueryCondition - { - Value = value, - Operator = op, - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); - } - - public Q OrWhereSub(Query query, object value) - { - return Or().WhereSub(query, value); - } - - public Q OrWhereSub(Query query, string op, object value) - { - return Or().WhereSub(query, op, value); - } - - public Q OrWhere(string column, string op, Query query) - { - return Or().Where(column, op, query); - } - public Q OrWhere(string column, string op, Func callback) - { - return Or().Where(column, op, callback); - } - - public Q WhereExists(Query query) - { - if (!query.HasComponent("from")) - { - throw new ArgumentException($"'{nameof(FromClause)}' cannot be empty if used inside a '{nameof(WhereExists)}' condition"); - } - - return AddComponent("where", new ExistsCondition - { - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); - } - public Q WhereExists(Func callback) - { - var childQuery = new Query().SetParent(this); - return WhereExists(callback.Invoke(childQuery)); - } - - public Q WhereNotExists(Query query) - { - return Not().WhereExists(query); - } - - public Q WhereNotExists(Func callback) - { - return Not().WhereExists(callback); - } - - public Q OrWhereExists(Query query) - { - return Or().WhereExists(query); - } - public Q OrWhereExists(Func callback) - { - return Or().WhereExists(callback); - } - public Q OrWhereNotExists(Query query) - { - return Or().Not().WhereExists(query); - } - public Q OrWhereNotExists(Func callback) - { - return Or().Not().WhereExists(callback); - } - - #region date - public Q WhereDatePart(string part, string column, string op, object value) - { - return AddComponent("where", new BasicDateCondition - { - Operator = op, - Column = column, - Value = value, - Part = part?.ToLowerInvariant(), - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - public Q WhereNotDatePart(string part, string column, string op, object value) - { - return Not().WhereDatePart(part, column, op, value); - } - - public Q OrWhereDatePart(string part, string column, string op, object value) - { - return Or().WhereDatePart(part, column, op, value); - } - - public Q OrWhereNotDatePart(string part, string column, string op, object value) - { - return Or().Not().WhereDatePart(part, column, op, value); - } - - public Q WhereDate(string column, string op, object value) - { - return WhereDatePart("date", column, op, value); - } - public Q WhereNotDate(string column, string op, object value) - { - return Not().WhereDate(column, op, value); - } - public Q OrWhereDate(string column, string op, object value) - { - return Or().WhereDate(column, op, value); - } - public Q OrWhereNotDate(string column, string op, object value) - { - return Or().Not().WhereDate(column, op, value); - } - - public Q WhereTime(string column, string op, object value) - { - return WhereDatePart("time", column, op, value); - } - public Q WhereNotTime(string column, string op, object value) - { - return Not().WhereTime(column, op, value); - } - public Q OrWhereTime(string column, string op, object value) - { - return Or().WhereTime(column, op, value); - } - public Q OrWhereNotTime(string column, string op, object value) - { - return Or().Not().WhereTime(column, op, value); - } - - public Q WhereDatePart(string part, string column, object value) - { - return WhereDatePart(part, column, "=", value); - } - public Q WhereNotDatePart(string part, string column, object value) - { - return WhereNotDatePart(part, column, "=", value); - } - - public Q OrWhereDatePart(string part, string column, object value) - { - return OrWhereDatePart(part, column, "=", value); - } - - public Q OrWhereNotDatePart(string part, string column, object value) - { - return OrWhereNotDatePart(part, column, "=", value); - } - - public Q WhereDate(string column, object value) - { - return WhereDate(column, "=", value); - } - public Q WhereNotDate(string column, object value) - { - return WhereNotDate(column, "=", value); - } - public Q OrWhereDate(string column, object value) - { - return OrWhereDate(column, "=", value); - } - public Q OrWhereNotDate(string column, object value) - { - return OrWhereNotDate(column, "=", value); - } - - public Q WhereTime(string column, object value) - { - return WhereTime(column, "=", value); - } - public Q WhereNotTime(string column, object value) - { - return WhereNotTime(column, "=", value); - } - public Q OrWhereTime(string column, object value) - { - return OrWhereTime(column, "=", value); - } - public Q OrWhereNotTime(string column, object value) - { - return OrWhereNotTime(column, "=", value); - } - - #endregion - } -} +namespace SqlKata; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +public abstract partial class BaseQuery { + public Q Where(string column, string op, object value) { + + // If the value is "null", we will just assume the developer wants to add a + // where null clause to the query. So, we will allow a short-cut here to + // that method for convenience so the developer doesn't have to check. + if (value == null) { + return Not(op != "=").WhereNull(column); + } + + if (value is bool boolValue) { + if (op != "=") { + Not(); + } + + return boolValue ? WhereTrue(column) : WhereFalse(column); + } + + return AddComponent("where", new BasicCondition { + Column = column, + Operator = op, + Value = value, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q WhereNot(string column, string op, object value) { + return Not().Where(column, op, value); + } + + public Q OrWhere(string column, string op, object value) { + return Or().Where(column, op, value); + } + + public Q OrWhereNot(string column, string op, object value) { + return this.Or().Not().Where(column, op, value); + } + + public Q Where(string column, object value) { + return Where(column, "=", value); + } + public Q WhereNot(string column, object value) { + return WhereNot(column, "=", value); + } + public Q OrWhere(string column, object value) { + return OrWhere(column, "=", value); + } + public Q OrWhereNot(string column, object value) { + return OrWhereNot(column, "=", value); + } + + /// + /// Perform a where constraint + /// + /// + /// + public Q Where(object constraints) { + var dictionary = new Dictionary(); + + foreach (var item in constraints.GetType().GetRuntimeProperties()) { + dictionary.Add(item.Name, item.GetValue(constraints)); + } + + return Where(dictionary); + } + + public Q Where(IEnumerable> values) { + var query = (Q)this; + var orFlag = GetOr(); + var notFlag = GetNot(); + + foreach (var tuple in values) { + if (orFlag) { + query = query.Or(); + } + else { + query.And(); + } + + query = this.Not(notFlag).Where(tuple.Key, tuple.Value); + } + + return query; + } + + public Q WhereRaw(string sql, params object[] bindings) { + return AddComponent("where", new RawCondition { + Expression = sql, + Bindings = bindings, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q OrWhereRaw(string sql, params object[] bindings) { + return Or().WhereRaw(sql, bindings); + } + + /// + /// Apply a nested where clause + /// + /// + /// + public Q Where(Func callback) { + var query = callback.Invoke(NewChild()); + + // omit empty queries + if (!query.Clauses.Where(x => x.Component == "where").Any()) { + return (Q)this; + } + + return AddComponent("where", new NestedCondition { + Query = query, + IsNot = GetNot(), + IsOr = GetOr(), + }); + } + + public Q WhereNot(Func callback) { + return Not().Where(callback); + } + + public Q OrWhere(Func callback) { + return Or().Where(callback); + } + + public Q OrWhereNot(Func callback) { + return Not().Or().Where(callback); + } + + public Q WhereColumns(string first, string op, string second) { + return AddComponent("where", new TwoColumnsCondition { + First = first, + Second = second, + Operator = op, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q OrWhereColumns(string first, string op, string second) { + return Or().WhereColumns(first, op, second); + } + + public Q WhereNull(string column) { + return AddComponent("where", new NullCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q WhereNotNull(string column) { + return Not().WhereNull(column); + } + + public Q OrWhereNull(string column) { + return this.Or().WhereNull(column); + } + + public Q OrWhereNotNull(string column) { + return Or().Not().WhereNull(column); + } + + public Q WhereTrue(string column) { + return AddComponent("where", new BooleanCondition { + Column = column, + Value = true, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q OrWhereTrue(string column) { + return Or().WhereTrue(column); + } + + public Q WhereFalse(string column) { + return AddComponent("where", new BooleanCondition { + Column = column, + Value = false, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q OrWhereFalse(string column) { + return Or().WhereFalse(column); + } + + public Q WhereLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return AddComponent("where", new BasicStringCondition { + Operator = "like", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q WhereNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Not().WhereLike(column, value, caseSensitive, escapeCharacter); + } + + public Q OrWhereLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().WhereLike(column, value, caseSensitive, escapeCharacter); + } + + public Q OrWhereNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().Not().WhereLike(column, value, caseSensitive, escapeCharacter); + } + public Q WhereStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return AddComponent("where", new BasicStringCondition { + Operator = "starts", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q WhereNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Not().WhereStarts(column, value, caseSensitive, escapeCharacter); + } + + public Q OrWhereStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().WhereStarts(column, value, caseSensitive, escapeCharacter); + } + + public Q OrWhereNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().Not().WhereStarts(column, value, caseSensitive, escapeCharacter); + } + + public Q WhereEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return AddComponent("where", new BasicStringCondition { + Operator = "ends", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q WhereNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Not().WhereEnds(column, value, caseSensitive, escapeCharacter); + } + + public Q OrWhereEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().WhereEnds(column, value, caseSensitive, escapeCharacter); + } + + public Q OrWhereNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().Not().WhereEnds(column, value, caseSensitive, escapeCharacter); + } + + public Q WhereContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return AddComponent("where", new BasicStringCondition { + Operator = "contains", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Q WhereNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Not().WhereContains(column, value, caseSensitive, escapeCharacter); + } + + public Q OrWhereContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().WhereContains(column, value, caseSensitive, escapeCharacter); + } + + public Q OrWhereNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().Not().WhereContains(column, value, caseSensitive, escapeCharacter); + } + + public Q WhereBetween(string column, T lower, T higher) { + return AddComponent("where", new BetweenCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + Lower = lower, + Higher = higher + }); + } + + public Q OrWhereBetween(string column, T lower, T higher) { + return Or().WhereBetween(column, lower, higher); + } + public Q WhereNotBetween(string column, T lower, T higher) { + return Not().WhereBetween(column, lower, higher); + } + public Q OrWhereNotBetween(string column, T lower, T higher) { + return Or().Not().WhereBetween(column, lower, higher); + } + + public Q WhereIn(string column, IEnumerable values) { + + // If the developer has passed a string they most likely want a List + // since string is considered as List + if (values is string) { + string val = values as string; + + return AddComponent("where", new InCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + Values = new List { val } + }); + } + + return AddComponent("where", new InCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + Values = values.Distinct().ToList() + }); + + + } + + public Q OrWhereIn(string column, IEnumerable values) { + return Or().WhereIn(column, values); + } + + public Q WhereNotIn(string column, IEnumerable values) { + return Not().WhereIn(column, values); + } + + public Q OrWhereNotIn(string column, IEnumerable values) { + return Or().Not().WhereIn(column, values); + } + + + public Q WhereIn(string column, Query query) { + return AddComponent("where", new InQueryCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + Query = query, + }); + } + public Q WhereIn(string column, Func callback) { + var query = callback.Invoke(new Query().SetParent(this)); + + return WhereIn(column, query); + } + + public Q OrWhereIn(string column, Query query) { + return Or().WhereIn(column, query); + } + + public Q OrWhereIn(string column, Func callback) { + return Or().WhereIn(column, callback); + } + public Q WhereNotIn(string column, Query query) { + return Not().WhereIn(column, query); + } + + public Q WhereNotIn(string column, Func callback) { + return Not().WhereIn(column, callback); + } + + public Q OrWhereNotIn(string column, Query query) { + return Or().Not().WhereIn(column, query); + } + + public Q OrWhereNotIn(string column, Func callback) { + return Or().Not().WhereIn(column, callback); + } + + + /// + /// Perform a sub query where clause + /// + /// + /// + /// + /// + public Q Where(string column, string op, Func callback) { + var query = callback.Invoke(NewChild()); + + return Where(column, op, query); + } + + public Q Where(string column, string op, Query query) { + return AddComponent("where", new QueryCondition { + Column = column, + Operator = op, + Query = query, + IsNot = GetNot(), + IsOr = GetOr(), + }); + } + + public Q WhereSub(Query query, object value) { + return WhereSub(query, "=", value); + } + + public Q WhereSub(Query query, string op, object value) { + return AddComponent("where", new SubQueryCondition { + Value = value, + Operator = op, + Query = query, + IsNot = GetNot(), + IsOr = GetOr(), + }); + } + + public Q OrWhereSub(Query query, object value) { + return Or().WhereSub(query, value); + } + + public Q OrWhereSub(Query query, string op, object value) { + return Or().WhereSub(query, op, value); + } + + public Q OrWhere(string column, string op, Query query) { + return Or().Where(column, op, query); + } + public Q OrWhere(string column, string op, Func callback) { + return Or().Where(column, op, callback); + } + + public Q WhereExists(Query query) { + if (!query.HasComponent("from")) { + throw new ArgumentException($"'{nameof(FromClause)}' cannot be empty if used inside a '{nameof(WhereExists)}' condition"); + } + + return AddComponent("where", new ExistsCondition { + Query = query, + IsNot = GetNot(), + IsOr = GetOr(), + }); + } + public Q WhereExists(Func callback) { + var childQuery = new Query().SetParent(this); + return WhereExists(callback.Invoke(childQuery)); + } + + public Q WhereNotExists(Query query) { + return Not().WhereExists(query); + } + + public Q WhereNotExists(Func callback) { + return Not().WhereExists(callback); + } + + public Q OrWhereExists(Query query) { + return Or().WhereExists(query); + } + public Q OrWhereExists(Func callback) { + return Or().WhereExists(callback); + } + public Q OrWhereNotExists(Query query) { + return Or().Not().WhereExists(query); + } + public Q OrWhereNotExists(Func callback) { + return Or().Not().WhereExists(callback); + } + + #region date + public Q WhereDatePart(string part, string column, string op, object value) { + return AddComponent("where", new BasicDateCondition { + Operator = op, + Column = column, + Value = value, + Part = part?.ToLowerInvariant(), + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + public Q WhereNotDatePart(string part, string column, string op, object value) { + return Not().WhereDatePart(part, column, op, value); + } + + public Q OrWhereDatePart(string part, string column, string op, object value) { + return Or().WhereDatePart(part, column, op, value); + } + + public Q OrWhereNotDatePart(string part, string column, string op, object value) { + return Or().Not().WhereDatePart(part, column, op, value); + } + + public Q WhereDate(string column, string op, object value) { + return WhereDatePart("date", column, op, value); + } + public Q WhereNotDate(string column, string op, object value) { + return Not().WhereDate(column, op, value); + } + public Q OrWhereDate(string column, string op, object value) { + return Or().WhereDate(column, op, value); + } + public Q OrWhereNotDate(string column, string op, object value) { + return Or().Not().WhereDate(column, op, value); + } + + public Q WhereTime(string column, string op, object value) { + return WhereDatePart("time", column, op, value); + } + public Q WhereNotTime(string column, string op, object value) { + return Not().WhereTime(column, op, value); + } + public Q OrWhereTime(string column, string op, object value) { + return Or().WhereTime(column, op, value); + } + public Q OrWhereNotTime(string column, string op, object value) { + return Or().Not().WhereTime(column, op, value); + } + + public Q WhereDatePart(string part, string column, object value) { + return WhereDatePart(part, column, "=", value); + } + public Q WhereNotDatePart(string part, string column, object value) { + return WhereNotDatePart(part, column, "=", value); + } + + public Q OrWhereDatePart(string part, string column, object value) { + return OrWhereDatePart(part, column, "=", value); + } + + public Q OrWhereNotDatePart(string part, string column, object value) { + return OrWhereNotDatePart(part, column, "=", value); + } + + public Q WhereDate(string column, object value) { + return WhereDate(column, "=", value); + } + public Q WhereNotDate(string column, object value) { + return WhereNotDate(column, "=", value); + } + public Q OrWhereDate(string column, object value) { + return OrWhereDate(column, "=", value); + } + public Q OrWhereNotDate(string column, object value) { + return OrWhereNotDate(column, "=", value); + } + + public Q WhereTime(string column, object value) { + return WhereTime(column, "=", value); + } + public Q WhereNotTime(string column, object value) { + return WhereNotTime(column, "=", value); + } + public Q OrWhereTime(string column, object value) { + return OrWhereTime(column, "=", value); + } + public Q OrWhereNotTime(string column, object value) { + return OrWhereNotTime(column, "=", value); + } + + #endregion +} diff --git a/QueryBuilder/BaseQuery.cs b/QueryBuilder/BaseQuery.cs index 86b44a23..f147bb92 100644 --- a/QueryBuilder/BaseQuery.cs +++ b/QueryBuilder/BaseQuery.cs @@ -1,310 +1,275 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace SqlKata -{ - public abstract class AbstractQuery - { - public AbstractQuery Parent; - } - - public abstract partial class BaseQuery : AbstractQuery where Q : BaseQuery - { - public List Clauses { get; set; } = new List(); - - private bool orFlag = false; - private bool notFlag = false; - public string EngineScope = null; - - public Q SetEngineScope(string engine) - { - this.EngineScope = engine; - - return (Q)this; - } - - public BaseQuery() - { - } - - /// - /// Return a cloned copy of the current query. - /// - /// - public virtual Q Clone() - { - var q = NewQuery(); - - q.Clauses = this.Clauses.Select(x => x.Clone()).ToList(); - - return q; - } - - public Q SetParent(AbstractQuery parent) - { - if (this == parent) - { - throw new ArgumentException($"Cannot set the same {nameof(AbstractQuery)} as a parent of itself"); - } - - this.Parent = parent; - return (Q)this; - } - - public abstract Q NewQuery(); - - public Q NewChild() - { - var newQuery = NewQuery().SetParent((Q)this); - newQuery.EngineScope = this.EngineScope; - return newQuery; - } - - /// - /// Add a component clause to the query. - /// - /// - /// - /// - /// - public Q AddComponent(string component, AbstractClause clause, string engineCode = null) - { - if (engineCode == null) - { - engineCode = EngineScope; - } - - clause.Engine = engineCode; - clause.Component = component; - Clauses.Add(clause); - - return (Q)this; - } - - /// - /// If the query already contains a clause for the given component - /// and engine, replace it with the specified clause. Otherwise, just - /// add the clause. - /// - /// - /// - /// - /// - public Q AddOrReplaceComponent(string component, AbstractClause clause, string engineCode = null) - { - engineCode = engineCode ?? EngineScope; - - var current = GetComponents(component).SingleOrDefault(c => c.Engine == engineCode); - if (current != null) - Clauses.Remove(current); - - return AddComponent(component, clause, engineCode); - } - - - - /// - /// Get the list of clauses for a component. - /// - /// - public List GetComponents(string component, string engineCode = null) where C : AbstractClause - { - if (engineCode == null) - { - engineCode = EngineScope; - } - - var clauses = Clauses - .Where(x => x.Component == component) - .Where(x => engineCode == null || x.Engine == null || engineCode == x.Engine) - .Cast(); - - return clauses.ToList(); - } - - /// - /// Get the list of clauses for a component. - /// - /// - /// - /// - public List GetComponents(string component, string engineCode = null) - { - if (engineCode == null) - { - engineCode = EngineScope; - } - - return GetComponents(component, engineCode); - } - - /// - /// Get a single component clause from the query. - /// - /// - public C GetOneComponent(string component, string engineCode = null) where C : AbstractClause - { - engineCode = engineCode ?? EngineScope; - - var all = GetComponents(component, engineCode); - return all.FirstOrDefault(c => c.Engine == engineCode) ?? all.FirstOrDefault(c => c.Engine == null); - } - - /// - /// Get a single component clause from the query. - /// - /// - /// - /// - public AbstractClause GetOneComponent(string component, string engineCode = null) - { - if (engineCode == null) - { - engineCode = EngineScope; - } - - return GetOneComponent(component, engineCode); - } - - /// - /// Return whether the query has clauses for a component. - /// - /// - /// - /// - public bool HasComponent(string component, string engineCode = null) - { - if (engineCode == null) - { - engineCode = EngineScope; - } - - return GetComponents(component, engineCode).Any(); - } - - /// - /// Remove all clauses for a component. - /// - /// - /// - /// - public Q ClearComponent(string component, string engineCode = null) - { - if (engineCode == null) - { - engineCode = EngineScope; - } - - Clauses = Clauses - .Where(x => !(x.Component == component && (engineCode == null || x.Engine == null || engineCode == x.Engine))) - .ToList(); - - return (Q)this; - } - - /// - /// Set the next boolean operator to "and" for the "where" clause. - /// - /// - protected Q And() - { - orFlag = false; - return (Q)this; - } - - /// - /// Set the next boolean operator to "or" for the "where" clause. - /// - /// - public Q Or() - { - orFlag = true; - return (Q)this; - } - - /// - /// Set the next "not" operator for the "where" clause. - /// - /// - public Q Not(bool flag = true) - { - notFlag = flag; - return (Q)this; - } - - /// - /// Get the boolean operator and reset it to "and" - /// - /// - protected bool GetOr() - { - var ret = orFlag; - - // reset the flag - orFlag = false; - return ret; - } - - /// - /// Get the "not" operator and clear it - /// - /// - protected bool GetNot() - { - var ret = notFlag; - - // reset the flag - notFlag = false; - return ret; - } - - /// - /// Add a from Clause - /// - /// - /// - public Q From(string table) - { - return AddOrReplaceComponent("from", new FromClause - { - Table = table, - }); - } - - public Q From(Query query, string alias = null) - { - query = query.Clone(); - query.SetParent((Q)this); - - if (alias != null) - { - query.As(alias); - }; - - return AddOrReplaceComponent("from", new QueryFromClause - { - Query = query - }); - } - - public Q FromRaw(string sql, params object[] bindings) - { - return AddOrReplaceComponent("from", new RawFromClause - { - Expression = sql, - Bindings = bindings, - }); - } - - public Q From(Func callback, string alias = null) - { - var query = new Query(); - - query.SetParent((Q)this); - - return From(callback.Invoke(query), alias); - } - - } -} +namespace SqlKata; + +using System; +using System.Collections.Generic; +using System.Linq; + +public abstract class AbstractQuery { + public AbstractQuery Parent; +} + +public abstract partial class BaseQuery : AbstractQuery where Q : BaseQuery { + public List Clauses { get; set; } = new List(); + + private bool orFlag = false; + private bool notFlag = false; + public string EngineScope = null; + + public Q SetEngineScope(string engine) { + this.EngineScope = engine; + + return (Q)this; + } + + public BaseQuery() { + } + + /// + /// Return a cloned copy of the current query. + /// + /// + public virtual Q Clone() { + var q = NewQuery(); + + q.Clauses = this.Clauses.Select(x => x.Clone()).ToList(); + + return q; + } + + public Q SetParent(AbstractQuery parent) { + if (this == parent) { + throw new ArgumentException($"Cannot set the same {nameof(AbstractQuery)} as a parent of itself"); + } + + this.Parent = parent; + return (Q)this; + } + + public abstract Q NewQuery(); + + public Q NewChild() { + var newQuery = NewQuery().SetParent((Q)this); + newQuery.EngineScope = this.EngineScope; + return newQuery; + } + + /// + /// Add a component clause to the query. + /// + /// + /// + /// + /// + public Q AddComponent(string component, AbstractClause clause, string engineCode = null) { + if (engineCode == null) { + engineCode = EngineScope; + } + + clause.Engine = engineCode; + clause.Component = component; + Clauses.Add(clause); + + return (Q)this; + } + + /// + /// If the query already contains a clause for the given component + /// and engine, replace it with the specified clause. Otherwise, just + /// add the clause. + /// + /// + /// + /// + /// + public Q AddOrReplaceComponent(string component, AbstractClause clause, string engineCode = null) { + engineCode = engineCode ?? EngineScope; + + var current = GetComponents(component).SingleOrDefault(c => c.Engine == engineCode); + if (current != null) + Clauses.Remove(current); + + return AddComponent(component, clause, engineCode); + } + + + + /// + /// Get the list of clauses for a component. + /// + /// + public List GetComponents(string component, string engineCode = null) where C : AbstractClause { + if (engineCode == null) { + engineCode = EngineScope; + } + + var clauses = Clauses + .Where(x => x.Component == component) + .Where(x => engineCode == null || x.Engine == null || engineCode == x.Engine) + .Cast(); + + return clauses.ToList(); + } + + /// + /// Get the list of clauses for a component. + /// + /// + /// + /// + public List GetComponents(string component, string engineCode = null) { + if (engineCode == null) { + engineCode = EngineScope; + } + + return GetComponents(component, engineCode); + } + + /// + /// Get a single component clause from the query. + /// + /// + public C GetOneComponent(string component, string engineCode = null) where C : AbstractClause { + engineCode = engineCode ?? EngineScope; + + var all = GetComponents(component, engineCode); + return all.FirstOrDefault(c => c.Engine == engineCode) ?? all.FirstOrDefault(c => c.Engine == null); + } + + /// + /// Get a single component clause from the query. + /// + /// + /// + /// + public AbstractClause GetOneComponent(string component, string engineCode = null) { + if (engineCode == null) { + engineCode = EngineScope; + } + + return GetOneComponent(component, engineCode); + } + + /// + /// Return whether the query has clauses for a component. + /// + /// + /// + /// + public bool HasComponent(string component, string engineCode = null) { + if (engineCode == null) { + engineCode = EngineScope; + } + + return GetComponents(component, engineCode).Any(); + } + + /// + /// Remove all clauses for a component. + /// + /// + /// + /// + public Q ClearComponent(string component, string engineCode = null) { + if (engineCode == null) { + engineCode = EngineScope; + } + + Clauses = Clauses + .Where(x => !(x.Component == component && (engineCode == null || x.Engine == null || engineCode == x.Engine))) + .ToList(); + + return (Q)this; + } + + /// + /// Set the next boolean operator to "and" for the "where" clause. + /// + /// + protected Q And() { + orFlag = false; + return (Q)this; + } + + /// + /// Set the next boolean operator to "or" for the "where" clause. + /// + /// + public Q Or() { + orFlag = true; + return (Q)this; + } + + /// + /// Set the next "not" operator for the "where" clause. + /// + /// + public Q Not(bool flag = true) { + notFlag = flag; + return (Q)this; + } + + /// + /// Get the boolean operator and reset it to "and" + /// + /// + protected bool GetOr() { + var ret = orFlag; + + // reset the flag + orFlag = false; + return ret; + } + + /// + /// Get the "not" operator and clear it + /// + /// + protected bool GetNot() { + var ret = notFlag; + + // reset the flag + notFlag = false; + return ret; + } + + /// + /// Add a from Clause + /// + /// + /// + public Q From(string table) { + return AddOrReplaceComponent("from", new FromClause { + Table = table, + }); + } + + public Q From(Query query, string alias = null) { + query = query.Clone(); + query.SetParent((Q)this); + + if (alias != null) { + query.As(alias); + } + ; + + return AddOrReplaceComponent("from", new QueryFromClause { + Query = query + }); + } + + public Q FromRaw(string sql, params object[] bindings) { + return AddOrReplaceComponent("from", new RawFromClause { + Expression = sql, + Bindings = bindings, + }); + } + + public Q From(Func callback, string alias = null) { + var query = new Query(); + + query.SetParent((Q)this); + + return From(callback.Invoke(query), alias); + } + +} diff --git a/QueryBuilder/Clauses/AbstractClause.cs b/QueryBuilder/Clauses/AbstractClause.cs index 1f286594..ad105864 100644 --- a/QueryBuilder/Clauses/AbstractClause.cs +++ b/QueryBuilder/Clauses/AbstractClause.cs @@ -1,22 +1,19 @@ -namespace SqlKata -{ - public abstract class AbstractClause - { - /// - /// Gets or sets the SQL engine. - /// - /// - /// The SQL engine. - /// - public string Engine { get; set; } = null; - - /// - /// Gets or sets the component name. - /// - /// - /// The component name. - /// - public string Component { get; set; } - public abstract AbstractClause Clone(); - } -} +namespace SqlKata; +public abstract class AbstractClause { + /// + /// Gets or sets the SQL engine. + /// + /// + /// The SQL engine. + /// + public string Engine { get; set; } = null; + + /// + /// Gets or sets the component name. + /// + /// + /// The component name. + /// + public string Component { get; set; } + public abstract AbstractClause Clone(); +} diff --git a/QueryBuilder/Clauses/AggregateClause.cs b/QueryBuilder/Clauses/AggregateClause.cs index 2d18d78e..f8ae0ef5 100644 --- a/QueryBuilder/Clauses/AggregateClause.cs +++ b/QueryBuilder/Clauses/AggregateClause.cs @@ -1,39 +1,35 @@ -using System.Collections.Generic; - -namespace SqlKata -{ - /// - /// Represents aggregate clause like "COUNT", "MAX" or etc. - /// - /// - public class AggregateClause : AbstractClause - { - /// - /// Gets or sets columns that used in aggregate clause. - /// - /// - /// The columns to be aggregated. - /// - public List Columns { get; set; } - - /// - /// Gets or sets the type of aggregate function. - /// - /// - /// The type of aggregate function, e.g. "MAX", "MIN", etc. - /// - public string Type { get; set; } - - /// - public override AbstractClause Clone() - { - return new AggregateClause - { - Engine = Engine, - Type = Type, - Columns = new List(Columns), - Component = Component, - }; - } - } -} +namespace SqlKata; + +using System.Collections.Generic; + +/// +/// Represents aggregate clause like "COUNT", "MAX" or etc. +/// +/// +public class AggregateClause : AbstractClause { + /// + /// Gets or sets columns that used in aggregate clause. + /// + /// + /// The columns to be aggregated. + /// + public List Columns { get; set; } + + /// + /// Gets or sets the type of aggregate function. + /// + /// + /// The type of aggregate function, e.g. "MAX", "MIN", etc. + /// + public string Type { get; set; } + + /// + public override AbstractClause Clone() { + return new AggregateClause { + Engine = Engine, + Type = Type, + Columns = new List(Columns), + Component = Component, + }; + } +} diff --git a/QueryBuilder/Clauses/ColumnClause.cs b/QueryBuilder/Clauses/ColumnClause.cs index dd51a85e..ab991ced 100644 --- a/QueryBuilder/Clauses/ColumnClause.cs +++ b/QueryBuilder/Clauses/ColumnClause.cs @@ -1,109 +1,94 @@ -namespace SqlKata -{ - public abstract class AbstractColumn : AbstractClause - { - } - - /// - /// Represents "column" or "column as alias" clause. - /// - /// - public class Column : AbstractColumn - { - /// - /// Gets or sets the column name. Can be "columnName" or "columnName as columnAlias". - /// - /// - /// The column name. - /// - public string Name { get; set; } - - /// - public override AbstractClause Clone() - { - return new Column - { - Engine = Engine, - Name = Name, - Component = Component, - }; - } - } - - /// - /// Represents column clause calculated using query. - /// - /// - public class QueryColumn : AbstractColumn - { - /// - /// Gets or sets the query that will be used for column value calculation. - /// - /// - /// The query for column value calculation. - /// - public Query Query { get; set; } - public override AbstractClause Clone() - { - return new QueryColumn - { - Engine = Engine, - Query = Query.Clone(), - Component = Component, - }; - } - } - - public class RawColumn : AbstractColumn - { - /// - /// Gets or sets the RAW expression. - /// - /// - /// The RAW expression. - /// - public string Expression { get; set; } - public object[] Bindings { set; get; } - - /// - public override AbstractClause Clone() - { - return new RawColumn - { - Engine = Engine, - Expression = Expression, - Bindings = Bindings, - Component = Component, - }; - } - } - - /// - /// Represents an aggregated column clause with an optional filter - /// - /// - public class AggregatedColumn : AbstractColumn - { - /// - /// Gets or sets the a query that used to filter the data, - /// the compiler will consider only the `Where` clause. - /// - /// - /// The filter query. - /// - public Query Filter { get; set; } = null; - public string Aggregate { get; set; } - public AbstractColumn Column { get; set; } - public override AbstractClause Clone() - { - return new AggregatedColumn - { - Engine = Engine, - Filter = Filter?.Clone(), - Column = Column.Clone() as AbstractColumn, - Aggregate = Aggregate, - Component = Component, - }; - } - } -} +namespace SqlKata; +public abstract class AbstractColumn : AbstractClause { +} + +/// +/// Represents "column" or "column as alias" clause. +/// +/// +public class Column : AbstractColumn { + /// + /// Gets or sets the column name. Can be "columnName" or "columnName as columnAlias". + /// + /// + /// The column name. + /// + public string Name { get; set; } + + /// + public override AbstractClause Clone() { + return new Column { + Engine = Engine, + Name = Name, + Component = Component, + }; + } +} + +/// +/// Represents column clause calculated using query. +/// +/// +public class QueryColumn : AbstractColumn { + /// + /// Gets or sets the query that will be used for column value calculation. + /// + /// + /// The query for column value calculation. + /// + public Query Query { get; set; } + public override AbstractClause Clone() { + return new QueryColumn { + Engine = Engine, + Query = Query.Clone(), + Component = Component, + }; + } +} + +public class RawColumn : AbstractColumn { + /// + /// Gets or sets the RAW expression. + /// + /// + /// The RAW expression. + /// + public string Expression { get; set; } + public object[] Bindings { set; get; } + + /// + public override AbstractClause Clone() { + return new RawColumn { + Engine = Engine, + Expression = Expression, + Bindings = Bindings, + Component = Component, + }; + } +} + +/// +/// Represents an aggregated column clause with an optional filter +/// +/// +public class AggregatedColumn : AbstractColumn { + /// + /// Gets or sets the a query that used to filter the data, + /// the compiler will consider only the `Where` clause. + /// + /// + /// The filter query. + /// + public Query Filter { get; set; } = null; + public string Aggregate { get; set; } + public AbstractColumn Column { get; set; } + public override AbstractClause Clone() { + return new AggregatedColumn { + Engine = Engine, + Filter = Filter?.Clone(), + Column = Column.Clone() as AbstractColumn, + Aggregate = Aggregate, + Component = Component, + }; + } +} diff --git a/QueryBuilder/Clauses/Combine.cs b/QueryBuilder/Clauses/Combine.cs index da0f5e37..d8376e9d 100644 --- a/QueryBuilder/Clauses/Combine.cs +++ b/QueryBuilder/Clauses/Combine.cs @@ -1,66 +1,58 @@ -using System.Linq; - -namespace SqlKata -{ - public abstract class AbstractCombine : AbstractClause - { - - } - - public class Combine : AbstractCombine - { - /// - /// Gets or sets the query to be combined with. - /// - /// - /// The query that will be combined. - /// - public Query Query { get; set; } - - /// - /// Gets or sets the combine operation, e.g. "UNION", etc. - /// - /// - /// The combine operation. - /// - public string Operation { get; set; } - - /// - /// Gets or sets a value indicating whether this clause will combine all. - /// - /// - /// true if all; otherwise, false. - /// - public bool All { get; set; } = false; - - public override AbstractClause Clone() - { - return new Combine - { - Engine = Engine, - Operation = Operation, - Component = Component, - Query = Query, - All = All, - }; - } - } - - public class RawCombine : AbstractCombine - { - public string Expression { get; set; } - - public object[] Bindings { get; set; } - - public override AbstractClause Clone() - { - return new RawCombine - { - Engine = Engine, - Component = Component, - Expression = Expression, - Bindings = Bindings, - }; - } - } -} +namespace SqlKata; + +using System.Linq; + +public abstract class AbstractCombine : AbstractClause { + +} + +public class Combine : AbstractCombine { + /// + /// Gets or sets the query to be combined with. + /// + /// + /// The query that will be combined. + /// + public Query Query { get; set; } + + /// + /// Gets or sets the combine operation, e.g. "UNION", etc. + /// + /// + /// The combine operation. + /// + public string Operation { get; set; } + + /// + /// Gets or sets a value indicating whether this clause will combine all. + /// + /// + /// true if all; otherwise, false. + /// + public bool All { get; set; } = false; + + public override AbstractClause Clone() { + return new Combine { + Engine = Engine, + Operation = Operation, + Component = Component, + Query = Query, + All = All, + }; + } +} + +public class RawCombine : AbstractCombine { + public string Expression { get; set; } + + public object[] Bindings { get; set; } + + public override AbstractClause Clone() { + return new RawCombine { + Engine = Engine, + Component = Component, + Expression = Expression, + Bindings = Bindings, + }; + } +} diff --git a/QueryBuilder/Clauses/ConditionClause.cs b/QueryBuilder/Clauses/ConditionClause.cs index 287149e5..4e7f479f 100644 --- a/QueryBuilder/Clauses/ConditionClause.cs +++ b/QueryBuilder/Clauses/ConditionClause.cs @@ -1,339 +1,293 @@ -using System; -using System.Collections.Generic; - -namespace SqlKata -{ - public abstract class AbstractCondition : AbstractClause - { - public bool IsOr { get; set; } = false; - public bool IsNot { get; set; } = false; - } - - /// - /// Represents a comparison between a column and a value. - /// - public class BasicCondition : AbstractCondition - { - public string Column { get; set; } - public string Operator { get; set; } - public virtual object Value { get; set; } - - /// - public override AbstractClause Clone() - { - return new BasicCondition - { - Engine = Engine, - Column = Column, - Operator = Operator, - Value = Value, - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - public class BasicStringCondition : BasicCondition - { - - public bool CaseSensitive { get; set; } = false; - - private string escapeCharacter = null; - public string EscapeCharacter - { - get => escapeCharacter; - set - { - if (string.IsNullOrWhiteSpace(value)) - value = null; - else if (value.Length > 1) - throw new ArgumentOutOfRangeException($"The {nameof(EscapeCharacter)} can only contain a single character!"); - escapeCharacter = value; - } - } - /// - public override AbstractClause Clone() - { - return new BasicStringCondition - { - Engine = Engine, - Column = Column, - Operator = Operator, - Value = Value, - IsOr = IsOr, - IsNot = IsNot, - CaseSensitive = CaseSensitive, - EscapeCharacter = EscapeCharacter, - Component = Component, - }; - } - } - - public class BasicDateCondition : BasicCondition - { - public string Part { get; set; } - - /// - public override AbstractClause Clone() - { - return new BasicDateCondition - { - Engine = Engine, - Column = Column, - Operator = Operator, - Value = Value, - IsOr = IsOr, - IsNot = IsNot, - Part = Part, - Component = Component, - }; - } - } - - /// - /// Represents a comparison between two columns. - /// - public class TwoColumnsCondition : AbstractCondition - { - public string First { get; set; } - public string Operator { get; set; } - public string Second { get; set; } - - /// - public override AbstractClause Clone() - { - return new TwoColumnsCondition - { - Engine = Engine, - First = First, - Operator = Operator, - Second = Second, - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - /// - /// Represents a comparison between a column and a full "subquery". - /// - public class QueryCondition : AbstractCondition where T : BaseQuery - { - public string Column { get; set; } - public string Operator { get; set; } - public Query Query { get; set; } - - /// - public override AbstractClause Clone() - { - return new QueryCondition - { - Engine = Engine, - Column = Column, - Operator = Operator, - Query = Query.Clone(), - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - /// - /// Represents a comparison between a full "subquery" and a value. - /// - public class SubQueryCondition : AbstractCondition where T : BaseQuery - { - public object Value { get; set; } - public string Operator { get; set; } - public Query Query { get; set; } - - /// - public override AbstractClause Clone() - { - return new SubQueryCondition - { - Engine = Engine, - Value = Value, - Operator = Operator, - Query = Query.Clone(), - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - /// - /// Represents a "is in" condition. - /// - public class InCondition : AbstractCondition - { - public string Column { get; set; } - public IEnumerable Values { get; set; } - public override AbstractClause Clone() - { - return new InCondition - { - Engine = Engine, - Column = Column, - Values = new List(Values), - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - - } - - /// - /// Represents a "is in subquery" condition. - /// - public class InQueryCondition : AbstractCondition - { - public Query Query { get; set; } - public string Column { get; set; } - public override AbstractClause Clone() - { - return new InQueryCondition - { - Engine = Engine, - Column = Column, - Query = Query.Clone(), - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - /// - /// Represents a "is between" condition. - /// - public class BetweenCondition : AbstractCondition - { - public string Column { get; set; } - public T Higher { get; set; } - public T Lower { get; set; } - public override AbstractClause Clone() - { - return new BetweenCondition - { - Engine = Engine, - Column = Column, - Higher = Higher, - Lower = Lower, - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - /// - /// Represents an "is null" condition. - /// - public class NullCondition : AbstractCondition - { - public string Column { get; set; } - - /// - public override AbstractClause Clone() - { - return new NullCondition - { - Engine = Engine, - Column = Column, - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - /// - /// Represents a boolean (true/false) condition. - /// - public class BooleanCondition : AbstractCondition - { - public string Column { get; set; } - public bool Value { get; set; } - - /// - public override AbstractClause Clone() - { - return new BooleanCondition - { - Engine = Engine, - Column = Column, - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - Value = Value, - }; - } - } - - /// - /// Represents a "nested" clause condition. - /// i.e OR (myColumn = "A") - /// - public class NestedCondition : AbstractCondition where T : BaseQuery - { - public T Query { get; set; } - public override AbstractClause Clone() - { - return new NestedCondition - { - Engine = Engine, - Query = Query.Clone(), - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - /// - /// Represents an "exists sub query" clause condition. - /// - public class ExistsCondition : AbstractCondition - { - public Query Query { get; set; } - - /// - public override AbstractClause Clone() - { - return new ExistsCondition - { - Engine = Engine, - Query = Query.Clone(), - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } - - public class RawCondition : AbstractCondition - { - public string Expression { get; set; } - public object[] Bindings { set; get; } - - /// - public override AbstractClause Clone() - { - return new RawCondition - { - Engine = Engine, - Expression = Expression, - Bindings = Bindings, - IsOr = IsOr, - IsNot = IsNot, - Component = Component, - }; - } - } -} +namespace SqlKata; + +using System; +using System.Collections.Generic; + +public abstract class AbstractCondition : AbstractClause { + public bool IsOr { get; set; } = false; + public bool IsNot { get; set; } = false; +} + +/// +/// Represents a comparison between a column and a value. +/// +public class BasicCondition : AbstractCondition { + public string Column { get; set; } + public string Operator { get; set; } + public virtual object Value { get; set; } + + /// + public override AbstractClause Clone() { + return new BasicCondition { + Engine = Engine, + Column = Column, + Operator = Operator, + Value = Value, + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +public class BasicStringCondition : BasicCondition { + + public bool CaseSensitive { get; set; } = false; + + private string escapeCharacter = null; + public string EscapeCharacter { + get => escapeCharacter; + set { + if (string.IsNullOrWhiteSpace(value)) + value = null; + else if (value.Length > 1) + throw new ArgumentOutOfRangeException($"The {nameof(EscapeCharacter)} can only contain a single character!"); + escapeCharacter = value; + } + } + /// + public override AbstractClause Clone() { + return new BasicStringCondition { + Engine = Engine, + Column = Column, + Operator = Operator, + Value = Value, + IsOr = IsOr, + IsNot = IsNot, + CaseSensitive = CaseSensitive, + EscapeCharacter = EscapeCharacter, + Component = Component, + }; + } +} + +public class BasicDateCondition : BasicCondition { + public string Part { get; set; } + + /// + public override AbstractClause Clone() { + return new BasicDateCondition { + Engine = Engine, + Column = Column, + Operator = Operator, + Value = Value, + IsOr = IsOr, + IsNot = IsNot, + Part = Part, + Component = Component, + }; + } +} + +/// +/// Represents a comparison between two columns. +/// +public class TwoColumnsCondition : AbstractCondition { + public string First { get; set; } + public string Operator { get; set; } + public string Second { get; set; } + + /// + public override AbstractClause Clone() { + return new TwoColumnsCondition { + Engine = Engine, + First = First, + Operator = Operator, + Second = Second, + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +/// +/// Represents a comparison between a column and a full "subquery". +/// +public class QueryCondition : AbstractCondition where T : BaseQuery { + public string Column { get; set; } + public string Operator { get; set; } + public Query Query { get; set; } + + /// + public override AbstractClause Clone() { + return new QueryCondition { + Engine = Engine, + Column = Column, + Operator = Operator, + Query = Query.Clone(), + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +/// +/// Represents a comparison between a full "subquery" and a value. +/// +public class SubQueryCondition : AbstractCondition where T : BaseQuery { + public object Value { get; set; } + public string Operator { get; set; } + public Query Query { get; set; } + + /// + public override AbstractClause Clone() { + return new SubQueryCondition { + Engine = Engine, + Value = Value, + Operator = Operator, + Query = Query.Clone(), + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +/// +/// Represents a "is in" condition. +/// +public class InCondition : AbstractCondition { + public string Column { get; set; } + public IEnumerable Values { get; set; } + public override AbstractClause Clone() { + return new InCondition { + Engine = Engine, + Column = Column, + Values = new List(Values), + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } + +} + +/// +/// Represents a "is in subquery" condition. +/// +public class InQueryCondition : AbstractCondition { + public Query Query { get; set; } + public string Column { get; set; } + public override AbstractClause Clone() { + return new InQueryCondition { + Engine = Engine, + Column = Column, + Query = Query.Clone(), + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +/// +/// Represents a "is between" condition. +/// +public class BetweenCondition : AbstractCondition { + public string Column { get; set; } + public T Higher { get; set; } + public T Lower { get; set; } + public override AbstractClause Clone() { + return new BetweenCondition { + Engine = Engine, + Column = Column, + Higher = Higher, + Lower = Lower, + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +/// +/// Represents an "is null" condition. +/// +public class NullCondition : AbstractCondition { + public string Column { get; set; } + + /// + public override AbstractClause Clone() { + return new NullCondition { + Engine = Engine, + Column = Column, + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +/// +/// Represents a boolean (true/false) condition. +/// +public class BooleanCondition : AbstractCondition { + public string Column { get; set; } + public bool Value { get; set; } + + /// + public override AbstractClause Clone() { + return new BooleanCondition { + Engine = Engine, + Column = Column, + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + Value = Value, + }; + } +} + +/// +/// Represents a "nested" clause condition. +/// i.e OR (myColumn = "A") +/// +public class NestedCondition : AbstractCondition where T : BaseQuery { + public T Query { get; set; } + public override AbstractClause Clone() { + return new NestedCondition { + Engine = Engine, + Query = Query.Clone(), + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +/// +/// Represents an "exists sub query" clause condition. +/// +public class ExistsCondition : AbstractCondition { + public Query Query { get; set; } + + /// + public override AbstractClause Clone() { + return new ExistsCondition { + Engine = Engine, + Query = Query.Clone(), + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} + +public class RawCondition : AbstractCondition { + public string Expression { get; set; } + public object[] Bindings { set; get; } + + /// + public override AbstractClause Clone() { + return new RawCondition { + Engine = Engine, + Expression = Expression, + Bindings = Bindings, + IsOr = IsOr, + IsNot = IsNot, + Component = Component, + }; + } +} diff --git a/QueryBuilder/Clauses/FromClause.cs b/QueryBuilder/Clauses/FromClause.cs index 1410facf..68557493 100644 --- a/QueryBuilder/Clauses/FromClause.cs +++ b/QueryBuilder/Clauses/FromClause.cs @@ -1,119 +1,100 @@ -using System; -using System.Collections.Generic; - -namespace SqlKata -{ - public abstract class AbstractFrom : AbstractClause - { - protected string _alias; - - /// - /// Try to extract the Alias for the current clause. - /// - /// - public virtual string Alias { get => _alias; set => _alias = value; } - } - - /// - /// Represents a "from" clause. - /// - public class FromClause : AbstractFrom - { - public string Table { get; set; } - - public override string Alias - { - get - { - if (Table.IndexOf(" as ", StringComparison.OrdinalIgnoreCase) >= 0) - { - var segments = Table.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - - return segments[2]; - } - - return Table; - } - } - - /// - public override AbstractClause Clone() - { - return new FromClause - { - Engine = Engine, - Alias = Alias, - Table = Table, - Component = Component, - }; - } - } - - /// - /// Represents a "from subquery" clause. - /// - public class QueryFromClause : AbstractFrom - { - public Query Query { get; set; } - - public override string Alias - { - get - { - return string.IsNullOrEmpty(_alias) ? Query.QueryAlias : _alias; - } - } - - /// - public override AbstractClause Clone() - { - return new QueryFromClause - { - Engine = Engine, - Alias = Alias, - Query = Query.Clone(), - Component = Component, - }; - } - } - - public class RawFromClause : AbstractFrom - { - public string Expression { get; set; } - public object[] Bindings { set; get; } - - /// - public override AbstractClause Clone() - { - return new RawFromClause - { - Engine = Engine, - Alias = Alias, - Expression = Expression, - Bindings = Bindings, - Component = Component, - }; - } - } - - /// - /// Represents a FROM clause that is an ad-hoc table built with predefined values. - /// - public class AdHocTableFromClause : AbstractFrom - { - public List Columns { get; set; } - public List Values { get; set; } - - public override AbstractClause Clone() - { - return new AdHocTableFromClause - { - Engine = Engine, - Alias = Alias, - Columns = Columns, - Values = Values, - Component = Component, - }; - } - } -} \ No newline at end of file +namespace SqlKata; + +using System; +using System.Collections.Generic; + +public abstract class AbstractFrom : AbstractClause { + protected string _alias; + + /// + /// Try to extract the Alias for the current clause. + /// + /// + public virtual string Alias { get => _alias; set => _alias = value; } +} + +/// +/// Represents a "from" clause. +/// +public class FromClause : AbstractFrom { + public string Table { get; set; } + + public override string Alias { + get { + if (Table.IndexOf(" as ", StringComparison.OrdinalIgnoreCase) >= 0) { + var segments = Table.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + return segments[2]; + } + + return Table; + } + } + + /// + public override AbstractClause Clone() { + return new FromClause { + Engine = Engine, + Alias = Alias, + Table = Table, + Component = Component, + }; + } +} + +/// +/// Represents a "from subquery" clause. +/// +public class QueryFromClause : AbstractFrom { + public Query Query { get; set; } + + public override string Alias { + get { + return string.IsNullOrEmpty(_alias) ? Query.QueryAlias : _alias; + } + } + + /// + public override AbstractClause Clone() { + return new QueryFromClause { + Engine = Engine, + Alias = Alias, + Query = Query.Clone(), + Component = Component, + }; + } +} + +public class RawFromClause : AbstractFrom { + public string Expression { get; set; } + public object[] Bindings { set; get; } + + /// + public override AbstractClause Clone() { + return new RawFromClause { + Engine = Engine, + Alias = Alias, + Expression = Expression, + Bindings = Bindings, + Component = Component, + }; + } +} + +/// +/// Represents a FROM clause that is an ad-hoc table built with predefined values. +/// +public class AdHocTableFromClause : AbstractFrom { + public List Columns { get; set; } + public List Values { get; set; } + + public override AbstractClause Clone() { + return new AdHocTableFromClause { + Engine = Engine, + Alias = Alias, + Columns = Columns, + Values = Values, + Component = Component, + }; + } +} diff --git a/QueryBuilder/Clauses/IncrementClause.cs b/QueryBuilder/Clauses/IncrementClause.cs index 4ee5a194..24c5baab 100644 --- a/QueryBuilder/Clauses/IncrementClause.cs +++ b/QueryBuilder/Clauses/IncrementClause.cs @@ -1,19 +1,14 @@ -namespace SqlKata -{ - public class IncrementClause : InsertClause - { - public string Column { get; set; } - public int Value { get; set; } = 1; - - public override AbstractClause Clone() - { - return new IncrementClause - { - Engine = Engine, - Component = Component, - Column = Column, - Value = Value - }; - } - } -} \ No newline at end of file +namespace SqlKata; +public class IncrementClause : InsertClause { + public string Column { get; set; } + public int Value { get; set; } = 1; + + public override AbstractClause Clone() { + return new IncrementClause { + Engine = Engine, + Component = Component, + Column = Column, + Value = Value + }; + } +} diff --git a/QueryBuilder/Clauses/InsertClause.cs b/QueryBuilder/Clauses/InsertClause.cs index 14ece2d2..186d3ad8 100644 --- a/QueryBuilder/Clauses/InsertClause.cs +++ b/QueryBuilder/Clauses/InsertClause.cs @@ -1,45 +1,37 @@ -using System.Collections.Generic; - -namespace SqlKata -{ - public abstract class AbstractInsertClause : AbstractClause - { - - } - - public class InsertClause : AbstractInsertClause - { - public List Columns { get; set; } - public List Values { get; set; } - public bool ReturnId { get; set; } = false; - - public override AbstractClause Clone() - { - return new InsertClause - { - Engine = Engine, - Component = Component, - Columns = Columns, - Values = Values, - ReturnId = ReturnId, - }; - } - } - - public class InsertQueryClause : AbstractInsertClause - { - public List Columns { get; set; } - public Query Query { get; set; } - - public override AbstractClause Clone() - { - return new InsertQueryClause - { - Engine = Engine, - Component = Component, - Columns = Columns, - Query = Query.Clone(), - }; - } - } -} +namespace SqlKata; + +using System.Collections.Generic; + +public abstract class AbstractInsertClause : AbstractClause { + +} + +public class InsertClause : AbstractInsertClause { + public List Columns { get; set; } + public List Values { get; set; } + public bool ReturnId { get; set; } = false; + + public override AbstractClause Clone() { + return new InsertClause { + Engine = Engine, + Component = Component, + Columns = Columns, + Values = Values, + ReturnId = ReturnId, + }; + } +} + +public class InsertQueryClause : AbstractInsertClause { + public List Columns { get; set; } + public Query Query { get; set; } + + public override AbstractClause Clone() { + return new InsertQueryClause { + Engine = Engine, + Component = Component, + Columns = Columns, + Query = Query.Clone(), + }; + } +} diff --git a/QueryBuilder/Clauses/JoinClause.cs b/QueryBuilder/Clauses/JoinClause.cs index 94c6b322..31cbe374 100644 --- a/QueryBuilder/Clauses/JoinClause.cs +++ b/QueryBuilder/Clauses/JoinClause.cs @@ -1,50 +1,42 @@ -using System; - -namespace SqlKata -{ - public abstract class AbstractJoin : AbstractClause - { - - } - - public class BaseJoin : AbstractJoin - { - public Join Join { get; set; } - - public override AbstractClause Clone() - { - return new BaseJoin - { - Engine = Engine, - Join = Join.Clone(), - Component = Component, - }; - } - } - - public class DeepJoin : AbstractJoin - { - public string Type { get; set; } - public string Expression { get; set; } - public string SourceKeySuffix { get; set; } - public string TargetKey { get; set; } - public Func SourceKeyGenerator { get; set; } - public Func TargetKeyGenerator { get; set; } - - /// - public override AbstractClause Clone() - { - return new DeepJoin - { - Engine = Engine, - Component = Component, - Type = Type, - Expression = Expression, - SourceKeySuffix = SourceKeySuffix, - TargetKey = TargetKey, - SourceKeyGenerator = SourceKeyGenerator, - TargetKeyGenerator = TargetKeyGenerator, - }; - } - } -} +namespace SqlKata; + +using System; + +public abstract class AbstractJoin : AbstractClause { + +} + +public class BaseJoin : AbstractJoin { + public Join Join { get; set; } + + public override AbstractClause Clone() { + return new BaseJoin { + Engine = Engine, + Join = Join.Clone(), + Component = Component, + }; + } +} + +public class DeepJoin : AbstractJoin { + public string Type { get; set; } + public string Expression { get; set; } + public string SourceKeySuffix { get; set; } + public string TargetKey { get; set; } + public Func SourceKeyGenerator { get; set; } + public Func TargetKeyGenerator { get; set; } + + /// + public override AbstractClause Clone() { + return new DeepJoin { + Engine = Engine, + Component = Component, + Type = Type, + Expression = Expression, + SourceKeySuffix = SourceKeySuffix, + TargetKey = TargetKey, + SourceKeyGenerator = SourceKeyGenerator, + TargetKeyGenerator = TargetKeyGenerator, + }; + } +} diff --git a/QueryBuilder/Clauses/LimitClause.cs b/QueryBuilder/Clauses/LimitClause.cs index 89defc99..c0cfba9f 100644 --- a/QueryBuilder/Clauses/LimitClause.cs +++ b/QueryBuilder/Clauses/LimitClause.cs @@ -1,35 +1,27 @@ -namespace SqlKata -{ - public class LimitClause : AbstractClause - { - private int _limit; - - public int Limit - { - get => _limit; - set => _limit = value > 0 ? value : _limit; - } - - public bool HasLimit() - { - return _limit > 0; - } - - public LimitClause Clear() - { - _limit = 0; - return this; - } - - /// - public override AbstractClause Clone() - { - return new LimitClause - { - Engine = Engine, - Limit = Limit, - Component = Component, - }; - } - } -} +namespace SqlKata; +public class LimitClause : AbstractClause { + private int _limit; + + public int Limit { + get => _limit; + set => _limit = value > 0 ? value : _limit; + } + + public bool HasLimit() { + return _limit > 0; + } + + public LimitClause Clear() { + _limit = 0; + return this; + } + + /// + public override AbstractClause Clone() { + return new LimitClause { + Engine = Engine, + Limit = Limit, + Component = Component, + }; + } +} diff --git a/QueryBuilder/Clauses/OffsetClause.cs b/QueryBuilder/Clauses/OffsetClause.cs index a33a7f47..bbad866f 100644 --- a/QueryBuilder/Clauses/OffsetClause.cs +++ b/QueryBuilder/Clauses/OffsetClause.cs @@ -1,35 +1,27 @@ -namespace SqlKata -{ - public class OffsetClause : AbstractClause - { - private long _offset; - - public long Offset - { - get => _offset; - set => _offset = value > 0 ? value : _offset; - } - - public bool HasOffset() - { - return _offset > 0; - } - - public OffsetClause Clear() - { - _offset = 0; - return this; - } - - /// - public override AbstractClause Clone() - { - return new OffsetClause - { - Engine = Engine, - Offset = Offset, - Component = Component, - }; - } - } -} +namespace SqlKata; +public class OffsetClause : AbstractClause { + private long _offset; + + public long Offset { + get => _offset; + set => _offset = value > 0 ? value : _offset; + } + + public bool HasOffset() { + return _offset > 0; + } + + public OffsetClause Clear() { + _offset = 0; + return this; + } + + /// + public override AbstractClause Clone() { + return new OffsetClause { + Engine = Engine, + Offset = Offset, + Component = Component, + }; + } +} diff --git a/QueryBuilder/Clauses/OrderClause.cs b/QueryBuilder/Clauses/OrderClause.cs index 69af772e..a66fb051 100644 --- a/QueryBuilder/Clauses/OrderClause.cs +++ b/QueryBuilder/Clauses/OrderClause.cs @@ -1,55 +1,43 @@ -namespace SqlKata -{ - public abstract class AbstractOrderBy : AbstractClause - { - - } - - public class OrderBy : AbstractOrderBy - { - public string Column { get; set; } - public bool Ascending { get; set; } = true; - - /// - public override AbstractClause Clone() - { - return new OrderBy - { - Engine = Engine, - Component = Component, - Column = Column, - Ascending = Ascending - }; - } - } - - public class RawOrderBy : AbstractOrderBy - { - public string Expression { get; set; } - public object[] Bindings { set; get; } - - /// - public override AbstractClause Clone() - { - return new RawOrderBy - { - Engine = Engine, - Component = Component, - Expression = Expression, - Bindings = Bindings, - }; - } - } - - public class OrderByRandom : AbstractOrderBy - { - /// - public override AbstractClause Clone() - { - return new OrderByRandom - { - Engine = Engine, - }; - } - } -} +namespace SqlKata; +public abstract class AbstractOrderBy : AbstractClause { + +} + +public class OrderBy : AbstractOrderBy { + public string Column { get; set; } + public bool Ascending { get; set; } = true; + + /// + public override AbstractClause Clone() { + return new OrderBy { + Engine = Engine, + Component = Component, + Column = Column, + Ascending = Ascending + }; + } +} + +public class RawOrderBy : AbstractOrderBy { + public string Expression { get; set; } + public object[] Bindings { set; get; } + + /// + public override AbstractClause Clone() { + return new RawOrderBy { + Engine = Engine, + Component = Component, + Expression = Expression, + Bindings = Bindings, + }; + } +} + +public class OrderByRandom : AbstractOrderBy { + /// + public override AbstractClause Clone() { + return new OrderByRandom { + Engine = Engine, + }; + } +} diff --git a/QueryBuilder/ColumnAttribute.cs b/QueryBuilder/ColumnAttribute.cs index 35e96343..a7b944a6 100644 --- a/QueryBuilder/ColumnAttribute.cs +++ b/QueryBuilder/ColumnAttribute.cs @@ -1,34 +1,28 @@ -using System; - -namespace SqlKata -{ - /// - /// This class is used as metadata on a property to generate different name in the output query. - /// - public class ColumnAttribute : Attribute - { - public string Name { get; private set; } - public ColumnAttribute(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - Name = name; - } - } - - /// - /// This class is used as metadata on a property to determine if it is a primary key - /// - public class KeyAttribute : ColumnAttribute - { - public KeyAttribute([System.Runtime.CompilerServices.CallerMemberName] string name = "") - : base(name) - { - - } - - } -} +namespace SqlKata; + +using System; + +/// +/// This class is used as metadata on a property to generate different name in the output query. +/// +public class ColumnAttribute : Attribute { + public string Name { get; private set; } + public ColumnAttribute(string name) { + if (string.IsNullOrEmpty(name)) { + throw new ArgumentNullException(nameof(name)); + } + + Name = name; + } +} + +/// +/// This class is used as metadata on a property to determine if it is a primary key +/// +public class KeyAttribute : ColumnAttribute { + public KeyAttribute([System.Runtime.CompilerServices.CallerMemberName] string name = "") + : base(name) { + + } + +} diff --git a/QueryBuilder/Compilers/Compiler.Conditions.cs b/QueryBuilder/Compilers/Compiler.Conditions.cs index 6615c246..2e8b13b0 100644 --- a/QueryBuilder/Compilers/Compiler.Conditions.cs +++ b/QueryBuilder/Compilers/Compiler.Conditions.cs @@ -1,266 +1,232 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace SqlKata.Compilers -{ - public partial class Compiler - { - protected virtual MethodInfo FindCompilerMethodInfo(Type clauseType, string methodName) - { - return _compileConditionMethodsProvider.GetMethodInfo(clauseType, methodName); - } - - protected virtual string CompileCondition(SqlResult ctx, AbstractCondition clause) - { - var clauseType = clause.GetType(); - - var name = clauseType.Name; - - name = name.Substring(0, name.IndexOf("Condition")); - - var methodName = "Compile" + name + "Condition"; - - var methodInfo = FindCompilerMethodInfo(clauseType, methodName); - - try - { - - var result = methodInfo.Invoke(this, new object[] { - ctx, - clause - }); - - return result as string; - } - catch (Exception ex) - { - throw new Exception($"Failed to invoke '{methodName}'", ex); - } - - } - - protected virtual string CompileConditions(SqlResult ctx, List conditions) - { - var result = new List(); - - for (var i = 0; i < conditions.Count; i++) - { - var compiled = CompileCondition(ctx, conditions[i]); - - if (string.IsNullOrEmpty(compiled)) - { - continue; - } - - var boolOperator = i == 0 ? "" : (conditions[i].IsOr ? "OR " : "AND "); - - result.Add(boolOperator + compiled); - } - - return string.Join(" ", result); - } - - protected virtual string CompileRawCondition(SqlResult ctx, RawCondition x) - { - ctx.Bindings.AddRange(x.Bindings); - return WrapIdentifiers(x.Expression); - } - - protected virtual string CompileQueryCondition(SqlResult ctx, QueryCondition x) where T : BaseQuery - { - var subCtx = CompileSelectQuery(x.Query); - - ctx.Bindings.AddRange(subCtx.Bindings); - - return Wrap(x.Column) + " " + checkOperator(x.Operator) + " (" + subCtx.RawSql + ")"; - } - - protected virtual string CompileSubQueryCondition(SqlResult ctx, SubQueryCondition x) where T : BaseQuery - { - var subCtx = CompileSelectQuery(x.Query); - - ctx.Bindings.AddRange(subCtx.Bindings); - - return "(" + subCtx.RawSql + ") " + checkOperator(x.Operator) + " " + Parameter(ctx, x.Value); - } - - protected virtual string CompileBasicCondition(SqlResult ctx, BasicCondition x) - { - var sql = $"{Wrap(x.Column)} {checkOperator(x.Operator)} {Parameter(ctx, x.Value)}"; - - if (x.IsNot) - { - return $"NOT ({sql})"; - } - - return sql; - } - - protected virtual string CompileBasicStringCondition(SqlResult ctx, BasicStringCondition x) - { - - var column = Wrap(x.Column); - - var value = Resolve(ctx, x.Value) as string; - - if (value == null) - { - throw new ArgumentException("Expecting a non nullable string"); - } - - var method = x.Operator; - - if (new[] { "starts", "ends", "contains", "like" }.Contains(x.Operator)) - { - - method = "LIKE"; - - switch (x.Operator) - { - case "starts": - value = $"{value}%"; - break; - case "ends": - value = $"%{value}"; - break; - case "contains": - value = $"%{value}%"; - break; - } - } - - string sql; - - - if (!x.CaseSensitive) - { - column = CompileLower(column); - value = value.ToLowerInvariant(); - } - - if (x.Value is UnsafeLiteral) - { - sql = $"{column} {checkOperator(method)} {value}"; - } - else - { - sql = $"{column} {checkOperator(method)} {Parameter(ctx, value)}"; - } - - if (!string.IsNullOrEmpty(x.EscapeCharacter)) - { - sql = $"{sql} ESCAPE '{x.EscapeCharacter}'"; - } - - return x.IsNot ? $"NOT ({sql})" : sql; - - } - - protected virtual string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition x) - { - var column = Wrap(x.Column); - var op = checkOperator(x.Operator); - - var sql = $"{x.Part.ToUpperInvariant()}({column}) {op} {Parameter(ctx, x.Value)}"; - - return x.IsNot ? $"NOT ({sql})" : sql; - } - - protected virtual string CompileNestedCondition(SqlResult ctx, NestedCondition x) where Q : BaseQuery - { - if (!(x.Query.HasComponent("where", EngineCode) || x.Query.HasComponent("having", EngineCode))) - { - return null; - } - - var clause = x.Query.HasComponent("where", EngineCode) ? "where" : "having"; - - var clauses = x.Query.GetComponents(clause, EngineCode); - - var sql = CompileConditions(ctx, clauses); - - return x.IsNot ? $"NOT ({sql})" : $"({sql})"; - } - - protected string CompileTwoColumnsCondition(SqlResult ctx, TwoColumnsCondition clause) - { - var op = clause.IsNot ? "NOT " : ""; - return $"{op}{Wrap(clause.First)} {checkOperator(clause.Operator)} {Wrap(clause.Second)}"; - } - - protected virtual string CompileBetweenCondition(SqlResult ctx, BetweenCondition item) - { - var between = item.IsNot ? "NOT BETWEEN" : "BETWEEN"; - var lower = Parameter(ctx, item.Lower); - var higher = Parameter(ctx, item.Higher); - - return Wrap(item.Column) + $" {between} {lower} AND {higher}"; - } - - protected virtual string CompileInCondition(SqlResult ctx, InCondition item) - { - var column = Wrap(item.Column); - - if (!item.Values.Any()) - { - return item.IsNot ? $"1 = 1 /* NOT IN [empty list] */" : "1 = 0 /* IN [empty list] */"; - } - - var inOperator = item.IsNot ? "NOT IN" : "IN"; - - var values = Parameterize(ctx, item.Values); - - return column + $" {inOperator} ({values})"; - } - - protected virtual string CompileInQueryCondition(SqlResult ctx, InQueryCondition item) - { - - var subCtx = CompileSelectQuery(item.Query); - - ctx.Bindings.AddRange(subCtx.Bindings); - - var inOperator = item.IsNot ? "NOT IN" : "IN"; - - return Wrap(item.Column) + $" {inOperator} ({subCtx.RawSql})"; - } - - protected virtual string CompileNullCondition(SqlResult ctx, NullCondition item) - { - var op = item.IsNot ? "IS NOT NULL" : "IS NULL"; - return Wrap(item.Column) + " " + op; - } - - protected virtual string CompileBooleanCondition(SqlResult ctx, BooleanCondition item) - { - var column = Wrap(item.Column); - var value = item.Value ? CompileTrue() : CompileFalse(); - - var op = item.IsNot ? "!=" : "="; - - return $"{column} {op} {value}"; - } - - protected virtual string CompileExistsCondition(SqlResult ctx, ExistsCondition item) - { - var op = item.IsNot ? "NOT EXISTS" : "EXISTS"; - - - // remove unneeded components - var query = item.Query.Clone(); - - if (OmitSelectInsideExists) - { - query.ClearComponent("select").SelectRaw("1"); - } - - var subCtx = CompileSelectQuery(query); - - ctx.Bindings.AddRange(subCtx.Bindings); - - return $"{op} ({subCtx.RawSql})"; - } - } -} +namespace SqlKata.Compilers; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +public partial class Compiler { + protected virtual MethodInfo FindCompilerMethodInfo(Type clauseType, string methodName) { + return _compileConditionMethodsProvider.GetMethodInfo(clauseType, methodName); + } + + protected virtual string CompileCondition(SqlResult ctx, AbstractCondition clause) { + var clauseType = clause.GetType(); + + var name = clauseType.Name; + + name = name.Substring(0, name.IndexOf("Condition")); + + var methodName = "Compile" + name + "Condition"; + + var methodInfo = FindCompilerMethodInfo(clauseType, methodName); + + try { + + var result = methodInfo.Invoke(this, new object[] { + ctx, + clause + }); + + return result as string; + } + catch (Exception ex) { + throw new Exception($"Failed to invoke '{methodName}'", ex); + } + + } + + protected virtual string CompileConditions(SqlResult ctx, List conditions) { + var result = new List(); + + for (var i = 0; i < conditions.Count; i++) { + var compiled = CompileCondition(ctx, conditions[i]); + + if (string.IsNullOrEmpty(compiled)) { + continue; + } + + var boolOperator = i == 0 ? "" : (conditions[i].IsOr ? "OR " : "AND "); + + result.Add(boolOperator + compiled); + } + + return string.Join(" ", result); + } + + protected virtual string CompileRawCondition(SqlResult ctx, RawCondition x) { + ctx.Bindings.AddRange(x.Bindings); + return WrapIdentifiers(x.Expression); + } + + protected virtual string CompileQueryCondition(SqlResult ctx, QueryCondition x) where T : BaseQuery { + var subCtx = CompileSelectQuery(x.Query); + + ctx.Bindings.AddRange(subCtx.Bindings); + + return Wrap(x.Column) + " " + checkOperator(x.Operator) + " (" + subCtx.RawSql + ")"; + } + + protected virtual string CompileSubQueryCondition(SqlResult ctx, SubQueryCondition x) where T : BaseQuery { + var subCtx = CompileSelectQuery(x.Query); + + ctx.Bindings.AddRange(subCtx.Bindings); + + return "(" + subCtx.RawSql + ") " + checkOperator(x.Operator) + " " + Parameter(ctx, x.Value); + } + + protected virtual string CompileBasicCondition(SqlResult ctx, BasicCondition x) { + var sql = $"{Wrap(x.Column)} {checkOperator(x.Operator)} {Parameter(ctx, x.Value)}"; + + if (x.IsNot) { + return $"NOT ({sql})"; + } + + return sql; + } + + protected virtual string CompileBasicStringCondition(SqlResult ctx, BasicStringCondition x) { + + var column = Wrap(x.Column); + + var value = Resolve(ctx, x.Value) as string; + + if (value == null) { + throw new ArgumentException("Expecting a non nullable string"); + } + + var method = x.Operator; + + if (new[] { "starts", "ends", "contains", "like" }.Contains(x.Operator)) { + + method = "LIKE"; + + switch (x.Operator) { + case "starts": + value = $"{value}%"; + break; + case "ends": + value = $"%{value}"; + break; + case "contains": + value = $"%{value}%"; + break; + } + } + + string sql; + + + if (!x.CaseSensitive) { + column = CompileLower(column); + value = value.ToLowerInvariant(); + } + + if (x.Value is UnsafeLiteral) { + sql = $"{column} {checkOperator(method)} {value}"; + } + else { + sql = $"{column} {checkOperator(method)} {Parameter(ctx, value)}"; + } + + if (!string.IsNullOrEmpty(x.EscapeCharacter)) { + sql = $"{sql} ESCAPE '{x.EscapeCharacter}'"; + } + + return x.IsNot ? $"NOT ({sql})" : sql; + + } + + protected virtual string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition x) { + var column = Wrap(x.Column); + var op = checkOperator(x.Operator); + + var sql = $"{x.Part.ToUpperInvariant()}({column}) {op} {Parameter(ctx, x.Value)}"; + + return x.IsNot ? $"NOT ({sql})" : sql; + } + + protected virtual string CompileNestedCondition(SqlResult ctx, NestedCondition x) where Q : BaseQuery { + if (!(x.Query.HasComponent("where", EngineCode) || x.Query.HasComponent("having", EngineCode))) { + return null; + } + + var clause = x.Query.HasComponent("where", EngineCode) ? "where" : "having"; + + var clauses = x.Query.GetComponents(clause, EngineCode); + + var sql = CompileConditions(ctx, clauses); + + return x.IsNot ? $"NOT ({sql})" : $"({sql})"; + } + + protected string CompileTwoColumnsCondition(SqlResult ctx, TwoColumnsCondition clause) { + var op = clause.IsNot ? "NOT " : ""; + return $"{op}{Wrap(clause.First)} {checkOperator(clause.Operator)} {Wrap(clause.Second)}"; + } + + protected virtual string CompileBetweenCondition(SqlResult ctx, BetweenCondition item) { + var between = item.IsNot ? "NOT BETWEEN" : "BETWEEN"; + var lower = Parameter(ctx, item.Lower); + var higher = Parameter(ctx, item.Higher); + + return Wrap(item.Column) + $" {between} {lower} AND {higher}"; + } + + protected virtual string CompileInCondition(SqlResult ctx, InCondition item) { + var column = Wrap(item.Column); + + if (!item.Values.Any()) { + return item.IsNot ? $"1 = 1 /* NOT IN [empty list] */" : "1 = 0 /* IN [empty list] */"; + } + + var inOperator = item.IsNot ? "NOT IN" : "IN"; + + var values = Parameterize(ctx, item.Values); + + return column + $" {inOperator} ({values})"; + } + + protected virtual string CompileInQueryCondition(SqlResult ctx, InQueryCondition item) { + + var subCtx = CompileSelectQuery(item.Query); + + ctx.Bindings.AddRange(subCtx.Bindings); + + var inOperator = item.IsNot ? "NOT IN" : "IN"; + + return Wrap(item.Column) + $" {inOperator} ({subCtx.RawSql})"; + } + + protected virtual string CompileNullCondition(SqlResult ctx, NullCondition item) { + var op = item.IsNot ? "IS NOT NULL" : "IS NULL"; + return Wrap(item.Column) + " " + op; + } + + protected virtual string CompileBooleanCondition(SqlResult ctx, BooleanCondition item) { + var column = Wrap(item.Column); + var value = item.Value ? CompileTrue() : CompileFalse(); + + var op = item.IsNot ? "!=" : "="; + + return $"{column} {op} {value}"; + } + + protected virtual string CompileExistsCondition(SqlResult ctx, ExistsCondition item) { + var op = item.IsNot ? "NOT EXISTS" : "EXISTS"; + + + // remove unneeded components + var query = item.Query.Clone(); + + if (OmitSelectInsideExists) { + query.ClearComponent("select").SelectRaw("1"); + } + + var subCtx = CompileSelectQuery(query); + + ctx.Bindings.AddRange(subCtx.Bindings); + + return $"{op} ({subCtx.RawSql})"; + } +} diff --git a/QueryBuilder/Compilers/Compiler.cs b/QueryBuilder/Compilers/Compiler.cs index 5ac6c080..205f988b 100644 --- a/QueryBuilder/Compilers/Compiler.cs +++ b/QueryBuilder/Compilers/Compiler.cs @@ -1,1062 +1,930 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SqlKata.Compilers -{ - public partial class Compiler - { - private readonly ConditionsCompilerProvider _compileConditionMethodsProvider; - protected virtual string parameterPlaceholder { get; set; } = "?"; - protected virtual string parameterPrefix { get; set; } = "@p"; - protected virtual string OpeningIdentifier { get; set; } = "\""; - protected virtual string ClosingIdentifier { get; set; } = "\""; - protected virtual string ColumnAsKeyword { get; set; } = "AS "; - protected virtual string TableAsKeyword { get; set; } = "AS "; - protected virtual string LastId { get; set; } = ""; - protected virtual string EscapeCharacter { get; set; } = "\\"; - - - protected virtual string SingleInsertStartClause { get; set; } = "INSERT INTO"; - protected virtual string MultiInsertStartClause { get; set; } = "INSERT INTO"; - - - protected Compiler() - { - _compileConditionMethodsProvider = new ConditionsCompilerProvider(this); - } - - public virtual string EngineCode { get; } - - /// - /// Whether the compiler supports the `SELECT ... FILTER` syntax - /// - /// - public virtual bool SupportsFilterClause { get; set; } = false; - - /// - /// If true the compiler will remove the SELECT clause for the query used inside WHERE EXISTS - /// - /// - public virtual bool OmitSelectInsideExists { get; set; } = true; - - protected virtual string SingleRowDummyTableName { get => null; } - - /// - /// A list of white-listed operators - /// - /// - protected readonly HashSet operators = new HashSet - { - "=", "<", ">", "<=", ">=", "<>", "!=", "<=>", - "like", "not like", - "ilike", "not ilike", - "like binary", "not like binary", - "rlike", "not rlike", - "regexp", "not regexp", - "similar to", "not similar to" - }; - - protected HashSet userOperators = new HashSet - { - - }; - - protected Dictionary generateNamedBindings(object[] bindings) - { - return Helper.Flatten(bindings).Select((v, i) => new { i, v }) - .ToDictionary(x => parameterPrefix + x.i, x => x.v); - } - - protected SqlResult PrepareResult(SqlResult ctx) - { - ctx.NamedBindings = generateNamedBindings(ctx.Bindings.ToArray()); - ctx.Sql = Helper.ReplaceAll(ctx.RawSql, parameterPlaceholder, EscapeCharacter, i => parameterPrefix + i); - return ctx; - } - - - private Query TransformAggregateQuery(Query query) - { - var clause = query.GetOneComponent("aggregate", EngineCode); - - if (clause.Columns.Count == 1 && !query.IsDistinct) return query; - - if (query.IsDistinct) - { - query.ClearComponent("aggregate", EngineCode); - query.ClearComponent("select", EngineCode); - query.Select(clause.Columns.ToArray()); - } - else - { - foreach (var column in clause.Columns) - { - query.WhereNotNull(column); - } - } - - var outerClause = new AggregateClause() - { - Columns = new List { "*" }, - Type = clause.Type - }; - - return new Query() - .AddComponent("aggregate", outerClause) - .From(query, $"{clause.Type}Query"); - } - - protected virtual SqlResult CompileRaw(Query query) - { - SqlResult ctx; - - if (query.Method == "insert") - { - ctx = CompileInsertQuery(query); - } - else if (query.Method == "update") - { - ctx = CompileUpdateQuery(query); - } - else if (query.Method == "delete") - { - ctx = CompileDeleteQuery(query); - } - else - { - if (query.Method == "aggregate") - { - query.ClearComponent("limit") - .ClearComponent("order") - .ClearComponent("group"); - - query = TransformAggregateQuery(query); - } - - ctx = CompileSelectQuery(query); - } - - // handle CTEs - if (query.HasComponent("cte", EngineCode)) - { - ctx = CompileCteQuery(ctx, query); - } - - ctx.RawSql = Helper.ExpandParameters(ctx.RawSql, parameterPlaceholder, EscapeCharacter, ctx.Bindings.ToArray()); - - return ctx; - } - - /// - /// Add the passed operator(s) to the white list so they can be used with - /// the Where/Having methods, this prevent passing arbitrary operators - /// that opens the door for SQL injections. - /// - /// - /// - public Compiler Whitelist(params string[] operators) - { - foreach (var op in operators) - { - this.userOperators.Add(op); - } - - return this; - } - - public virtual SqlResult Compile(Query query) - { - var ctx = CompileRaw(query); - - ctx = PrepareResult(ctx); - - return ctx; - } - - public virtual SqlResult Compile(IEnumerable queries) - { - var compiled = queries.Select(CompileRaw).ToArray(); - var bindings = compiled.Select(r => r.Bindings).ToArray(); - var totalBindingsCount = bindings.Select(b => b.Count).Aggregate((a, b) => a + b); - - var combinedBindings = new List(totalBindingsCount); - foreach (var cb in bindings) - { - combinedBindings.AddRange(cb); - } - - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) - { - RawSql = compiled.Select(r => r.RawSql).Aggregate((a, b) => a + ";\n" + b), - Bindings = combinedBindings, - }; - - ctx = PrepareResult(ctx); - - return ctx; - } - - protected virtual SqlResult CompileSelectQuery(Query query) - { - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) - { - Query = query.Clone(), - }; - - var results = new[] { - this.CompileColumns(ctx), - this.CompileFrom(ctx), - this.CompileJoins(ctx), - this.CompileWheres(ctx), - this.CompileGroups(ctx), - this.CompileHaving(ctx), - this.CompileOrders(ctx), - this.CompileLimit(ctx), - this.CompileUnion(ctx), - } - .Where(x => x != null) - .Where(x => !string.IsNullOrEmpty(x)) - .ToList(); - - string sql = string.Join(" ", results); - - ctx.RawSql = sql; - - return ctx; - } - - protected virtual SqlResult CompileAdHocQuery(AdHocTableFromClause adHoc) - { - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter); - - var row = "SELECT " + string.Join(", ", adHoc.Columns.Select(col => $"{parameterPlaceholder} AS {Wrap(col)}")); - - var fromTable = SingleRowDummyTableName; - - if (fromTable != null) - { - row += $" FROM {fromTable}"; - } - - var rows = string.Join(" UNION ALL ", Enumerable.Repeat(row, adHoc.Values.Count / adHoc.Columns.Count)); - - ctx.RawSql = rows; - ctx.Bindings = adHoc.Values; - - return ctx; - } - - protected virtual SqlResult CompileDeleteQuery(Query query) - { - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) - { - Query = query - }; - - if (!ctx.Query.HasComponent("from", EngineCode)) - { - throw new InvalidOperationException("No table set to delete"); - } - - var fromClause = ctx.Query.GetOneComponent("from", EngineCode); - - string table = null; - - if (fromClause is FromClause fromClauseCast) - { - table = Wrap(fromClauseCast.Table); - } - - if (fromClause is RawFromClause rawFromClause) - { - table = WrapIdentifiers(rawFromClause.Expression); - ctx.Bindings.AddRange(rawFromClause.Bindings); - } - - if (table is null) - { - throw new InvalidOperationException("Invalid table expression"); - } - - var joins = CompileJoins(ctx); - - var where = CompileWheres(ctx); - - if (!string.IsNullOrEmpty(where)) - { - where = " " + where; - } - - if (string.IsNullOrEmpty(joins)) - { - ctx.RawSql = $"DELETE FROM {table}{where}"; - } - else - { - // check if we have alias - if (fromClause is FromClause && !string.IsNullOrEmpty(fromClause.Alias)) - { - ctx.RawSql = $"DELETE {Wrap(fromClause.Alias)} FROM {table} {joins}{where}"; - } - else - { - ctx.RawSql = $"DELETE {table} FROM {table} {joins}{where}"; - } - - } - - return ctx; - } - - protected virtual SqlResult CompileUpdateQuery(Query query) - { - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) - { - Query = query - }; - - if (!ctx.Query.HasComponent("from", EngineCode)) - { - throw new InvalidOperationException("No table set to update"); - } - - var fromClause = ctx.Query.GetOneComponent("from", EngineCode); - - string table = null; - - if (fromClause is FromClause fromClauseCast) - { - table = Wrap(fromClauseCast.Table); - } - - if (fromClause is RawFromClause rawFromClause) - { - table = WrapIdentifiers(rawFromClause.Expression); - ctx.Bindings.AddRange(rawFromClause.Bindings); - } - - if (table is null) - { - throw new InvalidOperationException("Invalid table expression"); - } - - // check for increment statements - var clause = ctx.Query.GetOneComponent("update", EngineCode); - - string wheres; - - if (clause != null && clause is IncrementClause increment) - { - var column = Wrap(increment.Column); - var value = Parameter(ctx, Math.Abs(increment.Value)); - var sign = increment.Value >= 0 ? "+" : "-"; - - wheres = CompileWheres(ctx); - - if (!string.IsNullOrEmpty(wheres)) - { - wheres = " " + wheres; - } - - ctx.RawSql = $"UPDATE {table} SET {column} = {column} {sign} {value}{wheres}"; - - return ctx; - } - - - var toUpdate = ctx.Query.GetOneComponent("update", EngineCode); - var parts = new List(); - - for (var i = 0; i < toUpdate.Columns.Count; i++) - { - parts.Add(Wrap(toUpdate.Columns[i]) + " = " + Parameter(ctx, toUpdate.Values[i])); - } - - var sets = string.Join(", ", parts); - - wheres = CompileWheres(ctx); - - if (!string.IsNullOrEmpty(wheres)) - { - wheres = " " + wheres; - } - - ctx.RawSql = $"UPDATE {table} SET {sets}{wheres}"; - - return ctx; - } - - protected virtual SqlResult CompileInsertQuery(Query query) - { - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) - { - Query = query - }; - - if (!ctx.Query.HasComponent("from", EngineCode)) - throw new InvalidOperationException("No table set to insert"); - - var fromClause = ctx.Query.GetOneComponent("from", EngineCode); - if (fromClause is null) - throw new InvalidOperationException("Invalid table expression"); - - string table = null; - if (fromClause is FromClause fromClauseCast) - table = Wrap(fromClauseCast.Table); - if (fromClause is RawFromClause rawFromClause) - { - table = WrapIdentifiers(rawFromClause.Expression); - ctx.Bindings.AddRange(rawFromClause.Bindings); - } - - if (table is null) - throw new InvalidOperationException("Invalid table expression"); - - var inserts = ctx.Query.GetComponents("insert", EngineCode); - if (inserts[0] is InsertQueryClause insertQueryClause) - return CompileInsertQueryClause(ctx, table, insertQueryClause); - else - return CompileValueInsertClauses(ctx, table, inserts.Cast()); - } - - protected virtual SqlResult CompileInsertQueryClause( - SqlResult ctx, string table, InsertQueryClause clause) - { - string columns = GetInsertColumnsList(clause.Columns); - - var subCtx = CompileSelectQuery(clause.Query); - ctx.Bindings.AddRange(subCtx.Bindings); - - ctx.RawSql = $"{SingleInsertStartClause} {table}{columns} {subCtx.RawSql}"; - - return ctx; - } - - protected virtual SqlResult CompileValueInsertClauses( - SqlResult ctx, string table, IEnumerable insertClauses) - { - bool isMultiValueInsert = insertClauses.Skip(1).Any(); - - var insertInto = (isMultiValueInsert) ? MultiInsertStartClause : SingleInsertStartClause; - - var firstInsert = insertClauses.First(); - string columns = GetInsertColumnsList(firstInsert.Columns); - var values = string.Join(", ", Parameterize(ctx, firstInsert.Values)); - - ctx.RawSql = $"{insertInto} {table}{columns} VALUES ({values})"; - - if (isMultiValueInsert) - return CompileRemainingInsertClauses(ctx, table, insertClauses); - - if (firstInsert.ReturnId && !string.IsNullOrEmpty(LastId)) - ctx.RawSql += ";" + LastId; - - return ctx; - } - - protected virtual SqlResult CompileRemainingInsertClauses(SqlResult ctx, string table, IEnumerable inserts) - { - foreach (var insert in inserts.Skip(1)) - { - string values = string.Join(", ", Parameterize(ctx, insert.Values)); - ctx.RawSql += $", ({values})"; - } - return ctx; - } - - protected string GetInsertColumnsList(List columnList) - { - var columns = ""; - if (columnList.Any()) - columns = $" ({string.Join(", ", WrapArray(columnList))})"; - - return columns; - } - - protected virtual SqlResult CompileCteQuery(SqlResult ctx, Query query) - { - var cteFinder = new CteFinder(query, EngineCode); - var cteSearchResult = cteFinder.Find(); - - var rawSql = new StringBuilder("WITH "); - var cteBindings = new List(); - - foreach (var cte in cteSearchResult) - { - var cteCtx = CompileCte(cte); - - cteBindings.AddRange(cteCtx.Bindings); - rawSql.Append(cteCtx.RawSql.Trim()); - rawSql.Append(",\n"); - } - - rawSql.Length -= 2; // remove last comma - rawSql.Append('\n'); - rawSql.Append(ctx.RawSql); - - ctx.Bindings.InsertRange(0, cteBindings); - ctx.RawSql = rawSql.ToString(); - - return ctx; - } - - /// - /// Compile a single column clause - /// - /// - /// - /// - public virtual string CompileColumn(SqlResult ctx, AbstractColumn column) - { - if (column is RawColumn raw) - { - ctx.Bindings.AddRange(raw.Bindings); - return WrapIdentifiers(raw.Expression); - } - - if (column is QueryColumn queryColumn) - { - var alias = ""; - - if (!string.IsNullOrWhiteSpace(queryColumn.Query.QueryAlias)) - { - alias = $" {ColumnAsKeyword}{WrapValue(queryColumn.Query.QueryAlias)}"; - } - - var subCtx = CompileSelectQuery(queryColumn.Query); - - ctx.Bindings.AddRange(subCtx.Bindings); - - return "(" + subCtx.RawSql + $"){alias}"; - } - - if (column is AggregatedColumn aggregatedColumn) - { - string agg = aggregatedColumn.Aggregate.ToUpperInvariant(); - - var (col, alias) = SplitAlias(CompileColumn(ctx, aggregatedColumn.Column)); - - alias = string.IsNullOrEmpty(alias) ? "" : $" {ColumnAsKeyword}{alias}"; - - string filterCondition = CompileFilterConditions(ctx, aggregatedColumn); - - if (string.IsNullOrEmpty(filterCondition)) - { - return $"{agg}({col}){alias}"; - } - - if (SupportsFilterClause) - { - return $"{agg}({col}) FILTER (WHERE {filterCondition}){alias}"; - } - - return $"{agg}(CASE WHEN {filterCondition} THEN {col} END){alias}"; - } - - return Wrap((column as Column).Name); - - } - - protected virtual string CompileFilterConditions(SqlResult ctx, AggregatedColumn aggregatedColumn) - { - if (aggregatedColumn.Filter == null) - { - return null; - } - - var wheres = aggregatedColumn.Filter.GetComponents("where"); - - return CompileConditions(ctx, wheres); - } - - public virtual SqlResult CompileCte(AbstractFrom cte) - { - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter); - - if (null == cte) - { - return ctx; - } - - if (cte is RawFromClause raw) - { - ctx.Bindings.AddRange(raw.Bindings); - ctx.RawSql = $"{WrapValue(raw.Alias)} AS ({WrapIdentifiers(raw.Expression)})"; - } - else if (cte is QueryFromClause queryFromClause) - { - var subCtx = CompileSelectQuery(queryFromClause.Query); - ctx.Bindings.AddRange(subCtx.Bindings); - - ctx.RawSql = $"{WrapValue(queryFromClause.Alias)} AS ({subCtx.RawSql})"; - } - else if (cte is AdHocTableFromClause adHoc) - { - var subCtx = CompileAdHocQuery(adHoc); - ctx.Bindings.AddRange(subCtx.Bindings); - - ctx.RawSql = $"{WrapValue(adHoc.Alias)} AS ({subCtx.RawSql})"; - } - - return ctx; - } - - protected virtual SqlResult OnBeforeSelect(SqlResult ctx) - { - return ctx; - } - - protected virtual string CompileColumns(SqlResult ctx) - { - if (ctx.Query.HasComponent("aggregate", EngineCode)) - { - var aggregate = ctx.Query.GetOneComponent("aggregate", EngineCode); - - var aggregateColumns = aggregate.Columns - .Select(x => CompileColumn(ctx, new Column { Name = x })) - .ToList(); - - string sql = string.Empty; - - if (aggregateColumns.Count == 1) - { - sql = string.Join(", ", aggregateColumns); - - if (ctx.Query.IsDistinct) - { - sql = "DISTINCT " + sql; - } - - return "SELECT " + aggregate.Type.ToUpperInvariant() + "(" + sql + $") {ColumnAsKeyword}" + WrapValue(aggregate.Type); - } - - return "SELECT 1"; - } - - var columns = ctx.Query - .GetComponents("select", EngineCode) - .Select(x => CompileColumn(ctx, x)) - .ToList(); - - var distinct = ctx.Query.IsDistinct ? "DISTINCT " : ""; - - var select = columns.Any() ? string.Join(", ", columns) : "*"; - - return $"SELECT {distinct}{select}"; - - } - - public virtual string CompileUnion(SqlResult ctx) - { - - // Handle UNION, EXCEPT and INTERSECT - if (!ctx.Query.GetComponents("combine", EngineCode).Any()) - { - return null; - } - - var combinedQueries = new List(); - - var clauses = ctx.Query.GetComponents("combine", EngineCode); - - foreach (var clause in clauses) - { - if (clause is Combine combineClause) - { - var combineOperator = combineClause.Operation.ToUpperInvariant() + " " + (combineClause.All ? "ALL " : ""); - - var subCtx = CompileSelectQuery(combineClause.Query); - - ctx.Bindings.AddRange(subCtx.Bindings); - - combinedQueries.Add($"{combineOperator}{subCtx.RawSql}"); - } - else - { - var combineRawClause = clause as RawCombine; - - ctx.Bindings.AddRange(combineRawClause.Bindings); - - combinedQueries.Add(WrapIdentifiers(combineRawClause.Expression)); - - } - } - - return string.Join(" ", combinedQueries); - - } - - public virtual string CompileTableExpression(SqlResult ctx, AbstractFrom from) - { - if (from is RawFromClause raw) - { - ctx.Bindings.AddRange(raw.Bindings); - return WrapIdentifiers(raw.Expression); - } - - if (from is QueryFromClause queryFromClause) - { - var fromQuery = queryFromClause.Query; - - var alias = string.IsNullOrEmpty(fromQuery.QueryAlias) ? "" : $" {TableAsKeyword}" + WrapValue(fromQuery.QueryAlias); - - var subCtx = CompileSelectQuery(fromQuery); - - ctx.Bindings.AddRange(subCtx.Bindings); - - return "(" + subCtx.RawSql + ")" + alias; - } - - if (from is FromClause fromClause) - { - return Wrap(fromClause.Table); - } - - throw InvalidClauseException("TableExpression", from); - } - - public virtual string CompileFrom(SqlResult ctx) - { - if (ctx.Query.HasComponent("from", EngineCode)) - { - var from = ctx.Query.GetOneComponent("from", EngineCode); - - return "FROM " + CompileTableExpression(ctx, from); - } - - return string.Empty; - } - - public virtual string CompileJoins(SqlResult ctx) - { - if (!ctx.Query.HasComponent("join", EngineCode)) - { - return null; - } - - var joins = ctx.Query - .GetComponents("join", EngineCode) - .Select(x => CompileJoin(ctx, x.Join)); - - return "\n" + string.Join("\n", joins); - } - - public virtual string CompileJoin(SqlResult ctx, Join join, bool isNested = false) - { - - var from = join.GetOneComponent("from", EngineCode); - var conditions = join.GetComponents("where", EngineCode); - - var joinTable = CompileTableExpression(ctx, from); - var constraints = CompileConditions(ctx, conditions); - - var onClause = conditions.Any() ? $" ON {constraints}" : ""; - - return $"{join.Type} {joinTable}{onClause}"; - } - - public virtual string CompileWheres(SqlResult ctx) - { - if (!ctx.Query.HasComponent("where", EngineCode)) - { - return null; - } - - var conditions = ctx.Query.GetComponents("where", EngineCode); - var sql = CompileConditions(ctx, conditions).Trim(); - - return string.IsNullOrEmpty(sql) ? null : $"WHERE {sql}"; - } - - public virtual string CompileGroups(SqlResult ctx) - { - if (!ctx.Query.HasComponent("group", EngineCode)) - { - return null; - } - - var columns = ctx.Query - .GetComponents("group", EngineCode) - .Select(x => CompileColumn(ctx, x)); - - return "GROUP BY " + string.Join(", ", columns); - } - - public virtual string CompileOrders(SqlResult ctx) - { - if (!ctx.Query.HasComponent("order", EngineCode)) - { - return null; - } - - var columns = ctx.Query - .GetComponents("order", EngineCode) - .Select(x => - { - - if (x is RawOrderBy raw) - { - ctx.Bindings.AddRange(raw.Bindings); - return WrapIdentifiers(raw.Expression); - } - - var direction = (x as OrderBy).Ascending ? "" : " DESC"; - - return Wrap((x as OrderBy).Column) + direction; - }); - - return "ORDER BY " + string.Join(", ", columns); - } - - public virtual string CompileHaving(SqlResult ctx) - { - if (!ctx.Query.HasComponent("having", EngineCode)) - { - return null; - } - - var sql = new List(); - string boolOperator; - - var having = ctx.Query.GetComponents("having", EngineCode) - .Cast() - .ToList(); - - for (var i = 0; i < having.Count; i++) - { - var compiled = CompileCondition(ctx, having[i]); - - if (!string.IsNullOrEmpty(compiled)) - { - boolOperator = i > 0 ? having[i].IsOr ? "OR " : "AND " : ""; - - sql.Add(boolOperator + compiled); - } - } - - return $"HAVING {string.Join(" ", sql)}"; - } - - public virtual string CompileLimit(SqlResult ctx) - { - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - if (limit == 0 && offset == 0) - { - return null; - } - - if (offset == 0) - { - ctx.Bindings.Add(limit); - return $"LIMIT {parameterPlaceholder}"; - } - - if (limit == 0) - { - ctx.Bindings.Add(offset); - return $"OFFSET {parameterPlaceholder}"; - } - - ctx.Bindings.Add(limit); - ctx.Bindings.Add(offset); - - return $"LIMIT {parameterPlaceholder} OFFSET {parameterPlaceholder}"; - } - - /// - /// Compile the random statement into SQL. - /// - /// - /// - public virtual string CompileRandom(string seed) - { - return "RANDOM()"; - } - - public virtual string CompileLower(string value) - { - return $"LOWER({value})"; - } - - public virtual string CompileUpper(string value) - { - return $"UPPER({value})"; - } - - public virtual string CompileTrue() - { - return "true"; - } - - public virtual string CompileFalse() - { - return "false"; - } - - private InvalidCastException InvalidClauseException(string section, AbstractClause clause) - { - return new InvalidCastException($"Invalid type \"{clause.GetType().Name}\" provided for the \"{section}\" clause."); - } - - protected string checkOperator(string op) - { - op = op.ToLowerInvariant(); - - var valid = operators.Contains(op) || userOperators.Contains(op); - - if (!valid) - { - throw new InvalidOperationException($"The operator '{op}' cannot be used. Please consider white listing it before using it."); - } - - return op; - } - - /// - /// Wrap a single string in a column identifier. - /// - /// - /// - public virtual string Wrap(string value) - { - - if (value.ToLowerInvariant().Contains(" as ")) - { - var (before, after) = SplitAlias(value); - - return Wrap(before) + $" {ColumnAsKeyword}" + WrapValue(after); - } - - if (value.Contains(".")) - { - return string.Join(".", value.Split('.').Select((x, index) => - { - return WrapValue(x); - })); - } - - // If we reach here then the value does not contain an "AS" alias - // nor dot "." expression, so wrap it as regular value. - return WrapValue(value); - } - - public virtual (string, string) SplitAlias(string value) - { - var index = value.LastIndexOf(" as ", StringComparison.OrdinalIgnoreCase); - - if (index > 0) - { - var before = value.Substring(0, index); - var after = value.Substring(index + 4); - return (before, after); - } - - return (value, null); - } - - /// - /// Wrap a single string in keyword identifiers. - /// - /// - /// - public virtual string WrapValue(string value) - { - if (value == "*") return value; - - var opening = this.OpeningIdentifier; - var closing = this.ClosingIdentifier; - - if (string.IsNullOrWhiteSpace(opening) && string.IsNullOrWhiteSpace(closing)) return value; - - return opening + value.Replace(closing, closing + closing) + closing; - } - - /// - /// Resolve a parameter - /// - /// - /// - /// - public virtual object Resolve(SqlResult ctx, object parameter) - { - // if we face a literal value we have to return it directly - if (parameter is UnsafeLiteral literal) - { - return literal.Value; - } - - // if we face a variable we have to lookup the variable from the predefined variables - if (parameter is Variable variable) - { - var value = ctx.Query.FindVariable(variable.Name); - return value; - } - - return parameter; - - } - - /// - /// Resolve a parameter and add it to the binding list - /// - /// - /// - /// - public virtual string Parameter(SqlResult ctx, object parameter) - { - // if we face a literal value we have to return it directly - if (parameter is UnsafeLiteral literal) - { - return literal.Value; - } - - // if we face a variable we have to lookup the variable from the predefined variables - if (parameter is Variable variable) - { - var value = ctx.Query.FindVariable(variable.Name); - ctx.Bindings.Add(value); - return parameterPlaceholder; - } - - ctx.Bindings.Add(parameter); - return parameterPlaceholder; - } - - /// - /// Create query parameter place-holders for an array. - /// - /// - /// - /// - public virtual string Parameterize(SqlResult ctx, IEnumerable values) - { - return string.Join(", ", values.Select(x => Parameter(ctx, x))); - } - - /// - /// Wrap an array of values. - /// - /// - /// - public virtual List WrapArray(List values) - { - return values.Select(x => Wrap(x)).ToList(); - } - - public virtual string WrapIdentifiers(string input) - { - return input - - // deprecated - .ReplaceIdentifierUnlessEscaped(this.EscapeCharacter, "{", this.OpeningIdentifier) - .ReplaceIdentifierUnlessEscaped(this.EscapeCharacter, "}", this.ClosingIdentifier) - - .ReplaceIdentifierUnlessEscaped(this.EscapeCharacter, "[", this.OpeningIdentifier) - .ReplaceIdentifierUnlessEscaped(this.EscapeCharacter, "]", this.ClosingIdentifier); - } - } -} +namespace SqlKata.Compilers; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +public partial class Compiler { + private readonly ConditionsCompilerProvider _compileConditionMethodsProvider; + protected virtual string parameterPlaceholder { get; set; } = "?"; + protected virtual string parameterPrefix { get; set; } = "@p"; + protected virtual string OpeningIdentifier { get; set; } = "\""; + protected virtual string ClosingIdentifier { get; set; } = "\""; + protected virtual string ColumnAsKeyword { get; set; } = "AS "; + protected virtual string TableAsKeyword { get; set; } = "AS "; + protected virtual string LastId { get; set; } = ""; + protected virtual string EscapeCharacter { get; set; } = "\\"; + + + protected virtual string SingleInsertStartClause { get; set; } = "INSERT INTO"; + protected virtual string MultiInsertStartClause { get; set; } = "INSERT INTO"; + + + protected Compiler() { + _compileConditionMethodsProvider = new ConditionsCompilerProvider(this); + } + + public virtual string EngineCode { get; } + + /// + /// Whether the compiler supports the `SELECT ... FILTER` syntax + /// + /// + public virtual bool SupportsFilterClause { get; set; } = false; + + /// + /// If true the compiler will remove the SELECT clause for the query used inside WHERE EXISTS + /// + /// + public virtual bool OmitSelectInsideExists { get; set; } = true; + + protected virtual string SingleRowDummyTableName { get => null; } + + /// + /// A list of white-listed operators + /// + /// + protected readonly HashSet operators = new HashSet + { + "=", "<", ">", "<=", ">=", "<>", "!=", "<=>", + "like", "not like", + "ilike", "not ilike", + "like binary", "not like binary", + "rlike", "not rlike", + "regexp", "not regexp", + "similar to", "not similar to" + }; + + protected HashSet userOperators = new HashSet { + + }; + + protected Dictionary generateNamedBindings(object[] bindings) { + return Helper.Flatten(bindings).Select((v, i) => new { i, v }) + .ToDictionary(x => parameterPrefix + x.i, x => x.v); + } + + protected SqlResult PrepareResult(SqlResult ctx) { + ctx.NamedBindings = generateNamedBindings(ctx.Bindings.ToArray()); + ctx.Sql = Helper.ReplaceAll(ctx.RawSql, parameterPlaceholder, EscapeCharacter, i => parameterPrefix + i); + return ctx; + } + + + private Query TransformAggregateQuery(Query query) { + var clause = query.GetOneComponent("aggregate", EngineCode); + + if (clause.Columns.Count == 1 && !query.IsDistinct) return query; + + if (query.IsDistinct) { + query.ClearComponent("aggregate", EngineCode); + query.ClearComponent("select", EngineCode); + query.Select(clause.Columns.ToArray()); + } + else { + foreach (var column in clause.Columns) { + query.WhereNotNull(column); + } + } + + var outerClause = new AggregateClause() { + Columns = new List { "*" }, + Type = clause.Type + }; + + return new Query() + .AddComponent("aggregate", outerClause) + .From(query, $"{clause.Type}Query"); + } + + protected virtual SqlResult CompileRaw(Query query) { + SqlResult ctx; + + if (query.Method == "insert") { + ctx = CompileInsertQuery(query); + } + else if (query.Method == "update") { + ctx = CompileUpdateQuery(query); + } + else if (query.Method == "delete") { + ctx = CompileDeleteQuery(query); + } + else { + if (query.Method == "aggregate") { + query.ClearComponent("limit") + .ClearComponent("order") + .ClearComponent("group"); + + query = TransformAggregateQuery(query); + } + + ctx = CompileSelectQuery(query); + } + + // handle CTEs + if (query.HasComponent("cte", EngineCode)) { + ctx = CompileCteQuery(ctx, query); + } + + ctx.RawSql = Helper.ExpandParameters(ctx.RawSql, parameterPlaceholder, EscapeCharacter, ctx.Bindings.ToArray()); + + return ctx; + } + + /// + /// Add the passed operator(s) to the white list so they can be used with + /// the Where/Having methods, this prevent passing arbitrary operators + /// that opens the door for SQL injections. + /// + /// + /// + public Compiler Whitelist(params string[] operators) { + foreach (var op in operators) { + this.userOperators.Add(op); + } + + return this; + } + + public virtual SqlResult Compile(Query query) { + var ctx = CompileRaw(query); + + ctx = PrepareResult(ctx); + + return ctx; + } + + public virtual SqlResult Compile(IEnumerable queries) { + var compiled = queries.Select(CompileRaw).ToArray(); + var bindings = compiled.Select(r => r.Bindings).ToArray(); + var totalBindingsCount = bindings.Select(b => b.Count).Aggregate((a, b) => a + b); + + var combinedBindings = new List(totalBindingsCount); + foreach (var cb in bindings) { + combinedBindings.AddRange(cb); + } + + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) { + RawSql = compiled.Select(r => r.RawSql).Aggregate((a, b) => a + ";\n" + b), + Bindings = combinedBindings, + }; + + ctx = PrepareResult(ctx); + + return ctx; + } + + protected virtual SqlResult CompileSelectQuery(Query query) { + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) { + Query = query.Clone(), + }; + + var results = new[] { + this.CompileColumns(ctx), + this.CompileFrom(ctx), + this.CompileJoins(ctx), + this.CompileWheres(ctx), + this.CompileGroups(ctx), + this.CompileHaving(ctx), + this.CompileOrders(ctx), + this.CompileLimit(ctx), + this.CompileUnion(ctx), + } + .Where(x => x != null) + .Where(x => !string.IsNullOrEmpty(x)) + .ToList(); + + string sql = string.Join(" ", results); + + ctx.RawSql = sql; + + return ctx; + } + + protected virtual SqlResult CompileAdHocQuery(AdHocTableFromClause adHoc) { + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter); + + var row = "SELECT " + string.Join(", ", adHoc.Columns.Select(col => $"{parameterPlaceholder} AS {Wrap(col)}")); + + var fromTable = SingleRowDummyTableName; + + if (fromTable != null) { + row += $" FROM {fromTable}"; + } + + var rows = string.Join(" UNION ALL ", Enumerable.Repeat(row, adHoc.Values.Count / adHoc.Columns.Count)); + + ctx.RawSql = rows; + ctx.Bindings = adHoc.Values; + + return ctx; + } + + protected virtual SqlResult CompileDeleteQuery(Query query) { + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) { + Query = query + }; + + if (!ctx.Query.HasComponent("from", EngineCode)) { + throw new InvalidOperationException("No table set to delete"); + } + + var fromClause = ctx.Query.GetOneComponent("from", EngineCode); + + string table = null; + + if (fromClause is FromClause fromClauseCast) { + table = Wrap(fromClauseCast.Table); + } + + if (fromClause is RawFromClause rawFromClause) { + table = WrapIdentifiers(rawFromClause.Expression); + ctx.Bindings.AddRange(rawFromClause.Bindings); + } + + if (table is null) { + throw new InvalidOperationException("Invalid table expression"); + } + + var joins = CompileJoins(ctx); + + var where = CompileWheres(ctx); + + if (!string.IsNullOrEmpty(where)) { + where = " " + where; + } + + if (string.IsNullOrEmpty(joins)) { + ctx.RawSql = $"DELETE FROM {table}{where}"; + } + else { + // check if we have alias + if (fromClause is FromClause && !string.IsNullOrEmpty(fromClause.Alias)) { + ctx.RawSql = $"DELETE {Wrap(fromClause.Alias)} FROM {table} {joins}{where}"; + } + else { + ctx.RawSql = $"DELETE {table} FROM {table} {joins}{where}"; + } + + } + + return ctx; + } + + protected virtual SqlResult CompileUpdateQuery(Query query) { + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) { + Query = query + }; + + if (!ctx.Query.HasComponent("from", EngineCode)) { + throw new InvalidOperationException("No table set to update"); + } + + var fromClause = ctx.Query.GetOneComponent("from", EngineCode); + + string table = null; + + if (fromClause is FromClause fromClauseCast) { + table = Wrap(fromClauseCast.Table); + } + + if (fromClause is RawFromClause rawFromClause) { + table = WrapIdentifiers(rawFromClause.Expression); + ctx.Bindings.AddRange(rawFromClause.Bindings); + } + + if (table is null) { + throw new InvalidOperationException("Invalid table expression"); + } + + // check for increment statements + var clause = ctx.Query.GetOneComponent("update", EngineCode); + + string wheres; + + if (clause != null && clause is IncrementClause increment) { + var column = Wrap(increment.Column); + var value = Parameter(ctx, Math.Abs(increment.Value)); + var sign = increment.Value >= 0 ? "+" : "-"; + + wheres = CompileWheres(ctx); + + if (!string.IsNullOrEmpty(wheres)) { + wheres = " " + wheres; + } + + ctx.RawSql = $"UPDATE {table} SET {column} = {column} {sign} {value}{wheres}"; + + return ctx; + } + + + var toUpdate = ctx.Query.GetOneComponent("update", EngineCode); + var parts = new List(); + + for (var i = 0; i < toUpdate.Columns.Count; i++) { + parts.Add(Wrap(toUpdate.Columns[i]) + " = " + Parameter(ctx, toUpdate.Values[i])); + } + + var sets = string.Join(", ", parts); + + wheres = CompileWheres(ctx); + + if (!string.IsNullOrEmpty(wheres)) { + wheres = " " + wheres; + } + + ctx.RawSql = $"UPDATE {table} SET {sets}{wheres}"; + + return ctx; + } + + protected virtual SqlResult CompileInsertQuery(Query query) { + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) { + Query = query + }; + + if (!ctx.Query.HasComponent("from", EngineCode)) + throw new InvalidOperationException("No table set to insert"); + + var fromClause = ctx.Query.GetOneComponent("from", EngineCode); + if (fromClause is null) + throw new InvalidOperationException("Invalid table expression"); + + string table = null; + if (fromClause is FromClause fromClauseCast) + table = Wrap(fromClauseCast.Table); + if (fromClause is RawFromClause rawFromClause) { + table = WrapIdentifiers(rawFromClause.Expression); + ctx.Bindings.AddRange(rawFromClause.Bindings); + } + + if (table is null) + throw new InvalidOperationException("Invalid table expression"); + + var inserts = ctx.Query.GetComponents("insert", EngineCode); + if (inserts[0] is InsertQueryClause insertQueryClause) + return CompileInsertQueryClause(ctx, table, insertQueryClause); + else + return CompileValueInsertClauses(ctx, table, inserts.Cast()); + } + + protected virtual SqlResult CompileInsertQueryClause( + SqlResult ctx, string table, InsertQueryClause clause) { + string columns = GetInsertColumnsList(clause.Columns); + + var subCtx = CompileSelectQuery(clause.Query); + ctx.Bindings.AddRange(subCtx.Bindings); + + ctx.RawSql = $"{SingleInsertStartClause} {table}{columns} {subCtx.RawSql}"; + + return ctx; + } + + protected virtual SqlResult CompileValueInsertClauses( + SqlResult ctx, string table, IEnumerable insertClauses) { + bool isMultiValueInsert = insertClauses.Skip(1).Any(); + + var insertInto = (isMultiValueInsert) ? MultiInsertStartClause : SingleInsertStartClause; + + var firstInsert = insertClauses.First(); + string columns = GetInsertColumnsList(firstInsert.Columns); + var values = string.Join(", ", Parameterize(ctx, firstInsert.Values)); + + ctx.RawSql = $"{insertInto} {table}{columns} VALUES ({values})"; + + if (isMultiValueInsert) + return CompileRemainingInsertClauses(ctx, table, insertClauses); + + if (firstInsert.ReturnId && !string.IsNullOrEmpty(LastId)) + ctx.RawSql += ";" + LastId; + + return ctx; + } + + protected virtual SqlResult CompileRemainingInsertClauses(SqlResult ctx, string table, IEnumerable inserts) { + foreach (var insert in inserts.Skip(1)) { + string values = string.Join(", ", Parameterize(ctx, insert.Values)); + ctx.RawSql += $", ({values})"; + } + return ctx; + } + + protected string GetInsertColumnsList(List columnList) { + var columns = ""; + if (columnList.Any()) + columns = $" ({string.Join(", ", WrapArray(columnList))})"; + + return columns; + } + + protected virtual SqlResult CompileCteQuery(SqlResult ctx, Query query) { + var cteFinder = new CteFinder(query, EngineCode); + var cteSearchResult = cteFinder.Find(); + + var rawSql = new StringBuilder("WITH "); + var cteBindings = new List(); + + foreach (var cte in cteSearchResult) { + var cteCtx = CompileCte(cte); + + cteBindings.AddRange(cteCtx.Bindings); + rawSql.Append(cteCtx.RawSql.Trim()); + rawSql.Append(",\n"); + } + + rawSql.Length -= 2; // remove last comma + rawSql.Append('\n'); + rawSql.Append(ctx.RawSql); + + ctx.Bindings.InsertRange(0, cteBindings); + ctx.RawSql = rawSql.ToString(); + + return ctx; + } + + /// + /// Compile a single column clause + /// + /// + /// + /// + public virtual string CompileColumn(SqlResult ctx, AbstractColumn column) { + if (column is RawColumn raw) { + ctx.Bindings.AddRange(raw.Bindings); + return WrapIdentifiers(raw.Expression); + } + + if (column is QueryColumn queryColumn) { + var alias = ""; + + if (!string.IsNullOrWhiteSpace(queryColumn.Query.QueryAlias)) { + alias = $" {ColumnAsKeyword}{WrapValue(queryColumn.Query.QueryAlias)}"; + } + + var subCtx = CompileSelectQuery(queryColumn.Query); + + ctx.Bindings.AddRange(subCtx.Bindings); + + return "(" + subCtx.RawSql + $"){alias}"; + } + + if (column is AggregatedColumn aggregatedColumn) { + string agg = aggregatedColumn.Aggregate.ToUpperInvariant(); + + var (col, alias) = SplitAlias(CompileColumn(ctx, aggregatedColumn.Column)); + + alias = string.IsNullOrEmpty(alias) ? "" : $" {ColumnAsKeyword}{alias}"; + + string filterCondition = CompileFilterConditions(ctx, aggregatedColumn); + + if (string.IsNullOrEmpty(filterCondition)) { + return $"{agg}({col}){alias}"; + } + + if (SupportsFilterClause) { + return $"{agg}({col}) FILTER (WHERE {filterCondition}){alias}"; + } + + return $"{agg}(CASE WHEN {filterCondition} THEN {col} END){alias}"; + } + + return Wrap((column as Column).Name); + + } + + protected virtual string CompileFilterConditions(SqlResult ctx, AggregatedColumn aggregatedColumn) { + if (aggregatedColumn.Filter == null) { + return null; + } + + var wheres = aggregatedColumn.Filter.GetComponents("where"); + + return CompileConditions(ctx, wheres); + } + + public virtual SqlResult CompileCte(AbstractFrom cte) { + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter); + + if (null == cte) { + return ctx; + } + + if (cte is RawFromClause raw) { + ctx.Bindings.AddRange(raw.Bindings); + ctx.RawSql = $"{WrapValue(raw.Alias)} AS ({WrapIdentifiers(raw.Expression)})"; + } + else if (cte is QueryFromClause queryFromClause) { + var subCtx = CompileSelectQuery(queryFromClause.Query); + ctx.Bindings.AddRange(subCtx.Bindings); + + ctx.RawSql = $"{WrapValue(queryFromClause.Alias)} AS ({subCtx.RawSql})"; + } + else if (cte is AdHocTableFromClause adHoc) { + var subCtx = CompileAdHocQuery(adHoc); + ctx.Bindings.AddRange(subCtx.Bindings); + + ctx.RawSql = $"{WrapValue(adHoc.Alias)} AS ({subCtx.RawSql})"; + } + + return ctx; + } + + protected virtual SqlResult OnBeforeSelect(SqlResult ctx) { + return ctx; + } + + protected virtual string CompileColumns(SqlResult ctx) { + if (ctx.Query.HasComponent("aggregate", EngineCode)) { + var aggregate = ctx.Query.GetOneComponent("aggregate", EngineCode); + + var aggregateColumns = aggregate.Columns + .Select(x => CompileColumn(ctx, new Column { Name = x })) + .ToList(); + + string sql = string.Empty; + + if (aggregateColumns.Count == 1) { + sql = string.Join(", ", aggregateColumns); + + if (ctx.Query.IsDistinct) { + sql = "DISTINCT " + sql; + } + + return "SELECT " + aggregate.Type.ToUpperInvariant() + "(" + sql + $") {ColumnAsKeyword}" + WrapValue(aggregate.Type); + } + + return "SELECT 1"; + } + + var columns = ctx.Query + .GetComponents("select", EngineCode) + .Select(x => CompileColumn(ctx, x)) + .ToList(); + + var distinct = ctx.Query.IsDistinct ? "DISTINCT " : ""; + + var select = columns.Any() ? string.Join(", ", columns) : "*"; + + return $"SELECT {distinct}{select}"; + + } + + public virtual string CompileUnion(SqlResult ctx) { + + // Handle UNION, EXCEPT and INTERSECT + if (!ctx.Query.GetComponents("combine", EngineCode).Any()) { + return null; + } + + var combinedQueries = new List(); + + var clauses = ctx.Query.GetComponents("combine", EngineCode); + + foreach (var clause in clauses) { + if (clause is Combine combineClause) { + var combineOperator = combineClause.Operation.ToUpperInvariant() + " " + (combineClause.All ? "ALL " : ""); + + var subCtx = CompileSelectQuery(combineClause.Query); + + ctx.Bindings.AddRange(subCtx.Bindings); + + combinedQueries.Add($"{combineOperator}{subCtx.RawSql}"); + } + else { + var combineRawClause = clause as RawCombine; + + ctx.Bindings.AddRange(combineRawClause.Bindings); + + combinedQueries.Add(WrapIdentifiers(combineRawClause.Expression)); + + } + } + + return string.Join(" ", combinedQueries); + + } + + public virtual string CompileTableExpression(SqlResult ctx, AbstractFrom from) { + if (from is RawFromClause raw) { + ctx.Bindings.AddRange(raw.Bindings); + return WrapIdentifiers(raw.Expression); + } + + if (from is QueryFromClause queryFromClause) { + var fromQuery = queryFromClause.Query; + + var alias = string.IsNullOrEmpty(fromQuery.QueryAlias) ? "" : $" {TableAsKeyword}" + WrapValue(fromQuery.QueryAlias); + + var subCtx = CompileSelectQuery(fromQuery); + + ctx.Bindings.AddRange(subCtx.Bindings); + + return "(" + subCtx.RawSql + ")" + alias; + } + + if (from is FromClause fromClause) { + return Wrap(fromClause.Table); + } + + throw InvalidClauseException("TableExpression", from); + } + + public virtual string CompileFrom(SqlResult ctx) { + if (ctx.Query.HasComponent("from", EngineCode)) { + var from = ctx.Query.GetOneComponent("from", EngineCode); + + return "FROM " + CompileTableExpression(ctx, from); + } + + return string.Empty; + } + + public virtual string CompileJoins(SqlResult ctx) { + if (!ctx.Query.HasComponent("join", EngineCode)) { + return null; + } + + var joins = ctx.Query + .GetComponents("join", EngineCode) + .Select(x => CompileJoin(ctx, x.Join)); + + return "\n" + string.Join("\n", joins); + } + + public virtual string CompileJoin(SqlResult ctx, Join join, bool isNested = false) { + + var from = join.GetOneComponent("from", EngineCode); + var conditions = join.GetComponents("where", EngineCode); + + var joinTable = CompileTableExpression(ctx, from); + var constraints = CompileConditions(ctx, conditions); + + var onClause = conditions.Any() ? $" ON {constraints}" : ""; + + return $"{join.Type} {joinTable}{onClause}"; + } + + public virtual string CompileWheres(SqlResult ctx) { + if (!ctx.Query.HasComponent("where", EngineCode)) { + return null; + } + + var conditions = ctx.Query.GetComponents("where", EngineCode); + var sql = CompileConditions(ctx, conditions).Trim(); + + return string.IsNullOrEmpty(sql) ? null : $"WHERE {sql}"; + } + + public virtual string CompileGroups(SqlResult ctx) { + if (!ctx.Query.HasComponent("group", EngineCode)) { + return null; + } + + var columns = ctx.Query + .GetComponents("group", EngineCode) + .Select(x => CompileColumn(ctx, x)); + + return "GROUP BY " + string.Join(", ", columns); + } + + public virtual string CompileOrders(SqlResult ctx) { + if (!ctx.Query.HasComponent("order", EngineCode)) { + return null; + } + + var columns = ctx.Query + .GetComponents("order", EngineCode) + .Select(x => { + + if (x is RawOrderBy raw) { + ctx.Bindings.AddRange(raw.Bindings); + return WrapIdentifiers(raw.Expression); + } + + var direction = (x as OrderBy).Ascending ? "" : " DESC"; + + return Wrap((x as OrderBy).Column) + direction; + }); + + return "ORDER BY " + string.Join(", ", columns); + } + + public virtual string CompileHaving(SqlResult ctx) { + if (!ctx.Query.HasComponent("having", EngineCode)) { + return null; + } + + var sql = new List(); + string boolOperator; + + var having = ctx.Query.GetComponents("having", EngineCode) + .Cast() + .ToList(); + + for (var i = 0; i < having.Count; i++) { + var compiled = CompileCondition(ctx, having[i]); + + if (!string.IsNullOrEmpty(compiled)) { + boolOperator = i > 0 ? having[i].IsOr ? "OR " : "AND " : ""; + + sql.Add(boolOperator + compiled); + } + } + + return $"HAVING {string.Join(" ", sql)}"; + } + + public virtual string CompileLimit(SqlResult ctx) { + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit == 0 && offset == 0) { + return null; + } + + if (offset == 0) { + ctx.Bindings.Add(limit); + return $"LIMIT {parameterPlaceholder}"; + } + + if (limit == 0) { + ctx.Bindings.Add(offset); + return $"OFFSET {parameterPlaceholder}"; + } + + ctx.Bindings.Add(limit); + ctx.Bindings.Add(offset); + + return $"LIMIT {parameterPlaceholder} OFFSET {parameterPlaceholder}"; + } + + /// + /// Compile the random statement into SQL. + /// + /// + /// + public virtual string CompileRandom(string seed) { + return "RANDOM()"; + } + + public virtual string CompileLower(string value) { + return $"LOWER({value})"; + } + + public virtual string CompileUpper(string value) { + return $"UPPER({value})"; + } + + public virtual string CompileTrue() { + return "true"; + } + + public virtual string CompileFalse() { + return "false"; + } + + private InvalidCastException InvalidClauseException(string section, AbstractClause clause) { + return new InvalidCastException($"Invalid type \"{clause.GetType().Name}\" provided for the \"{section}\" clause."); + } + + protected string checkOperator(string op) { + op = op.ToLowerInvariant(); + + var valid = operators.Contains(op) || userOperators.Contains(op); + + if (!valid) { + throw new InvalidOperationException($"The operator '{op}' cannot be used. Please consider white listing it before using it."); + } + + return op; + } + + /// + /// Wrap a single string in a column identifier. + /// + /// + /// + public virtual string Wrap(string value) { + + if (value.ToLowerInvariant().Contains(" as ")) { + var (before, after) = SplitAlias(value); + + return Wrap(before) + $" {ColumnAsKeyword}" + WrapValue(after); + } + + if (value.Contains(".")) { + return string.Join(".", value.Split('.').Select((x, index) => { + return WrapValue(x); + })); + } + + // If we reach here then the value does not contain an "AS" alias + // nor dot "." expression, so wrap it as regular value. + return WrapValue(value); + } + + public virtual (string, string) SplitAlias(string value) { + var index = value.LastIndexOf(" as ", StringComparison.OrdinalIgnoreCase); + + if (index > 0) { + var before = value.Substring(0, index); + var after = value.Substring(index + 4); + return (before, after); + } + + return (value, null); + } + + /// + /// Wrap a single string in keyword identifiers. + /// + /// + /// + public virtual string WrapValue(string value) { + if (value == "*") return value; + + var opening = this.OpeningIdentifier; + var closing = this.ClosingIdentifier; + + if (string.IsNullOrWhiteSpace(opening) && string.IsNullOrWhiteSpace(closing)) return value; + + return opening + value.Replace(closing, closing + closing) + closing; + } + + /// + /// Resolve a parameter + /// + /// + /// + /// + public virtual object Resolve(SqlResult ctx, object parameter) { + // if we face a literal value we have to return it directly + if (parameter is UnsafeLiteral literal) { + return literal.Value; + } + + // if we face a variable we have to lookup the variable from the predefined variables + if (parameter is Variable variable) { + var value = ctx.Query.FindVariable(variable.Name); + return value; + } + + return parameter; + + } + + /// + /// Resolve a parameter and add it to the binding list + /// + /// + /// + /// + public virtual string Parameter(SqlResult ctx, object parameter) { + // if we face a literal value we have to return it directly + if (parameter is UnsafeLiteral literal) { + return literal.Value; + } + + // if we face a variable we have to lookup the variable from the predefined variables + if (parameter is Variable variable) { + var value = ctx.Query.FindVariable(variable.Name); + ctx.Bindings.Add(value); + return parameterPlaceholder; + } + + ctx.Bindings.Add(parameter); + return parameterPlaceholder; + } + + /// + /// Create query parameter place-holders for an array. + /// + /// + /// + /// + public virtual string Parameterize(SqlResult ctx, IEnumerable values) { + return string.Join(", ", values.Select(x => Parameter(ctx, x))); + } + + /// + /// Wrap an array of values. + /// + /// + /// + public virtual List WrapArray(List values) { + return values.Select(x => Wrap(x)).ToList(); + } + + public virtual string WrapIdentifiers(string input) { + return input + + // deprecated + .ReplaceIdentifierUnlessEscaped(this.EscapeCharacter, "{", this.OpeningIdentifier) + .ReplaceIdentifierUnlessEscaped(this.EscapeCharacter, "}", this.ClosingIdentifier) + + .ReplaceIdentifierUnlessEscaped(this.EscapeCharacter, "[", this.OpeningIdentifier) + .ReplaceIdentifierUnlessEscaped(this.EscapeCharacter, "]", this.ClosingIdentifier); + } +} diff --git a/QueryBuilder/Compilers/ConditionsCompilerProvider.cs b/QueryBuilder/Compilers/ConditionsCompilerProvider.cs index 6439da94..bef95fdc 100644 --- a/QueryBuilder/Compilers/ConditionsCompilerProvider.cs +++ b/QueryBuilder/Compilers/ConditionsCompilerProvider.cs @@ -1,54 +1,45 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace SqlKata.Compilers -{ - internal class ConditionsCompilerProvider - { - private readonly Type compilerType; - private readonly Dictionary methodsCache = new Dictionary(); - private readonly object syncRoot = new object(); - - public ConditionsCompilerProvider(Compiler compiler) - { - this.compilerType = compiler.GetType(); - } - - public MethodInfo GetMethodInfo(Type clauseType, string methodName) - { - // The cache key should take the type and the method name into consideration - var cacheKey = methodName + "::" + clauseType.FullName; - - lock (syncRoot) - { - if (methodsCache.ContainsKey(cacheKey)) - { - return methodsCache[cacheKey]; - } - - return methodsCache[cacheKey] = FindMethodInfo(clauseType, methodName); - } - } - - private MethodInfo FindMethodInfo(Type clauseType, string methodName) - { - MethodInfo methodInfo = compilerType - .GetRuntimeMethods() - .FirstOrDefault(x => x.Name == methodName); - - if (methodInfo == null) - { - throw new Exception($"Failed to locate a compiler for '{methodName}'."); - } - - if (clauseType.IsConstructedGenericType && methodInfo.GetGenericArguments().Any()) - { - methodInfo = methodInfo.MakeGenericMethod(clauseType.GenericTypeArguments); - } - - return methodInfo; - } - } -} +namespace SqlKata.Compilers; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +internal class ConditionsCompilerProvider { + private readonly Type compilerType; + private readonly Dictionary methodsCache = new Dictionary(); + private readonly object syncRoot = new object(); + + public ConditionsCompilerProvider(Compiler compiler) { + this.compilerType = compiler.GetType(); + } + + public MethodInfo GetMethodInfo(Type clauseType, string methodName) { + // The cache key should take the type and the method name into consideration + var cacheKey = methodName + "::" + clauseType.FullName; + + lock (syncRoot) { + if (methodsCache.ContainsKey(cacheKey)) { + return methodsCache[cacheKey]; + } + + return methodsCache[cacheKey] = FindMethodInfo(clauseType, methodName); + } + } + + private MethodInfo FindMethodInfo(Type clauseType, string methodName) { + MethodInfo methodInfo = compilerType + .GetRuntimeMethods() + .FirstOrDefault(x => x.Name == methodName); + + if (methodInfo == null) { + throw new Exception($"Failed to locate a compiler for '{methodName}'."); + } + + if (clauseType.IsConstructedGenericType && methodInfo.GetGenericArguments().Any()) { + methodInfo = methodInfo.MakeGenericMethod(clauseType.GenericTypeArguments); + } + + return methodInfo; + } +} diff --git a/QueryBuilder/Compilers/CteFinder.cs b/QueryBuilder/Compilers/CteFinder.cs index fbb0800a..41d580f0 100644 --- a/QueryBuilder/Compilers/CteFinder.cs +++ b/QueryBuilder/Compilers/CteFinder.cs @@ -1,56 +1,49 @@ -using System.Collections.Generic; - -namespace SqlKata.Compilers -{ - public class CteFinder - { - private readonly Query query; - private readonly string engineCode; - private HashSet namesOfPreviousCtes; - private List orderedCteList; - - public CteFinder(Query query, string engineCode) - { - this.query = query; - this.engineCode = engineCode; - } - - public List Find() - { - if (null != orderedCteList) - return orderedCteList; - - namesOfPreviousCtes = new HashSet(); - - orderedCteList = findInternal(query); - - namesOfPreviousCtes.Clear(); - namesOfPreviousCtes = null; - - return orderedCteList; - } - - private List findInternal(Query queryToSearch) - { - var cteList = queryToSearch.GetComponents("cte", engineCode); - - var resultList = new List(); - - foreach (var cte in cteList) - { - if (namesOfPreviousCtes.Contains(cte.Alias)) - continue; - - namesOfPreviousCtes.Add(cte.Alias); - resultList.Add(cte); - - if (cte is QueryFromClause queryFromClause) - { - resultList.InsertRange(0, findInternal(queryFromClause.Query)); - } - } - - return resultList; - } - } -} +namespace SqlKata.Compilers; + +using System.Collections.Generic; + +public class CteFinder { + private readonly Query query; + private readonly string engineCode; + private HashSet namesOfPreviousCtes; + private List orderedCteList; + + public CteFinder(Query query, string engineCode) { + this.query = query; + this.engineCode = engineCode; + } + + public List Find() { + if (null != orderedCteList) + return orderedCteList; + + namesOfPreviousCtes = new HashSet(); + + orderedCteList = findInternal(query); + + namesOfPreviousCtes.Clear(); + namesOfPreviousCtes = null; + + return orderedCteList; + } + + private List findInternal(Query queryToSearch) { + var cteList = queryToSearch.GetComponents("cte", engineCode); + + var resultList = new List(); + + foreach (var cte in cteList) { + if (namesOfPreviousCtes.Contains(cte.Alias)) + continue; + + namesOfPreviousCtes.Add(cte.Alias); + resultList.Add(cte); + + if (cte is QueryFromClause queryFromClause) { + resultList.InsertRange(0, findInternal(queryFromClause.Query)); + } + } + + return resultList; + } +} diff --git a/QueryBuilder/Compilers/EngineCodes.cs b/QueryBuilder/Compilers/EngineCodes.cs index 9405f7f5..da2ae81b 100644 --- a/QueryBuilder/Compilers/EngineCodes.cs +++ b/QueryBuilder/Compilers/EngineCodes.cs @@ -1,13 +1,10 @@ -namespace SqlKata.Compilers -{ - public static class EngineCodes - { - public const string Firebird = "firebird"; - public const string Generic = "generic"; - public const string MySql = "mysql"; - public const string Oracle = "oracle"; - public const string PostgreSql = "postgres"; - public const string Sqlite = "sqlite"; - public const string SqlServer = "sqlsrv"; - } -} +namespace SqlKata.Compilers; +public static class EngineCodes { + public const string Firebird = "firebird"; + public const string Generic = "generic"; + public const string MySql = "mysql"; + public const string Oracle = "oracle"; + public const string PostgreSql = "postgres"; + public const string Sqlite = "sqlite"; + public const string SqlServer = "sqlsrv"; +} diff --git a/QueryBuilder/Compilers/FirebirdCompiler.cs b/QueryBuilder/Compilers/FirebirdCompiler.cs index 61ab9547..1858e55b 100644 --- a/QueryBuilder/Compilers/FirebirdCompiler.cs +++ b/QueryBuilder/Compilers/FirebirdCompiler.cs @@ -1,120 +1,102 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace SqlKata.Compilers -{ - public class FirebirdCompiler : Compiler - { - public FirebirdCompiler() - { - } - - public override string EngineCode { get; } = EngineCodes.Firebird; - protected override string SingleRowDummyTableName => "RDB$DATABASE"; - - protected override SqlResult CompileInsertQuery(Query query) - { - var ctx = base.CompileInsertQuery(query); - - var inserts = ctx.Query.GetComponents("insert", EngineCode); - - if (inserts.Count > 1) - { - ctx.RawSql = Regex.Replace(ctx.RawSql, @"\)\s+VALUES\s+\(", ") SELECT "); - ctx.RawSql = Regex.Replace(ctx.RawSql, @"\),\s*\(", " FROM RDB$DATABASE UNION ALL SELECT "); - ctx.RawSql = Regex.Replace(ctx.RawSql, @"\)$", " FROM RDB$DATABASE"); - } - - return ctx; - } - - public override string CompileLimit(SqlResult ctx) - { - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - if (limit > 0 && offset > 0) - { - ctx.Bindings.Add(offset + 1); - ctx.Bindings.Add(limit + offset); - - return $"ROWS {parameterPlaceholder} TO {parameterPlaceholder}"; - } - - return null; - } - - - protected override string CompileColumns(SqlResult ctx) - { - var compiled = base.CompileColumns(ctx); - - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - if (limit > 0 && offset == 0) - { - ctx.Bindings.Insert(0, limit); - - ctx.Query.ClearComponent("limit"); - - return $"SELECT FIRST {parameterPlaceholder}" + compiled.Substring(6); - } - else if (limit == 0 && offset > 0) - { - ctx.Bindings.Insert(0, offset); - - ctx.Query.ClearComponent("offset"); - - return $"SELECT SKIP {parameterPlaceholder}" + compiled.Substring(6); - } - - return compiled; - } - - protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) - { - var column = Wrap(condition.Column); - - string left; - - if (condition.Part == "time") - { - left = $"CAST({column} as TIME)"; - } - else if (condition.Part == "date") - { - left = $"CAST({column} as DATE)"; - } - else - { - left = $"EXTRACT({condition.Part.ToUpperInvariant()} FROM {column})"; - } - - var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; - - if (condition.IsNot) - { - return $"NOT ({sql})"; - } - - return sql; - } - - public override string WrapValue(string value) - { - return base.WrapValue(value).ToUpperInvariant(); - } - - public override string CompileTrue() - { - return "1"; - } - - public override string CompileFalse() - { - return "0"; - } - } -} +namespace SqlKata.Compilers; + +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +public class FirebirdCompiler : Compiler { + public FirebirdCompiler() { + } + + public override string EngineCode { get; } = EngineCodes.Firebird; + protected override string SingleRowDummyTableName => "RDB$DATABASE"; + + protected override SqlResult CompileInsertQuery(Query query) { + var ctx = base.CompileInsertQuery(query); + + var inserts = ctx.Query.GetComponents("insert", EngineCode); + + if (inserts.Count > 1) { + ctx.RawSql = Regex.Replace(ctx.RawSql, @"\)\s+VALUES\s+\(", ") SELECT "); + ctx.RawSql = Regex.Replace(ctx.RawSql, @"\),\s*\(", " FROM RDB$DATABASE UNION ALL SELECT "); + ctx.RawSql = Regex.Replace(ctx.RawSql, @"\)$", " FROM RDB$DATABASE"); + } + + return ctx; + } + + public override string CompileLimit(SqlResult ctx) { + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit > 0 && offset > 0) { + ctx.Bindings.Add(offset + 1); + ctx.Bindings.Add(limit + offset); + + return $"ROWS {parameterPlaceholder} TO {parameterPlaceholder}"; + } + + return null; + } + + + protected override string CompileColumns(SqlResult ctx) { + var compiled = base.CompileColumns(ctx); + + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit > 0 && offset == 0) { + ctx.Bindings.Insert(0, limit); + + ctx.Query.ClearComponent("limit"); + + return $"SELECT FIRST {parameterPlaceholder}" + compiled.Substring(6); + } + else if (limit == 0 && offset > 0) { + ctx.Bindings.Insert(0, offset); + + ctx.Query.ClearComponent("offset"); + + return $"SELECT SKIP {parameterPlaceholder}" + compiled.Substring(6); + } + + return compiled; + } + + protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) { + var column = Wrap(condition.Column); + + string left; + + if (condition.Part == "time") { + left = $"CAST({column} as TIME)"; + } + else if (condition.Part == "date") { + left = $"CAST({column} as DATE)"; + } + else { + left = $"EXTRACT({condition.Part.ToUpperInvariant()} FROM {column})"; + } + + var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; + + if (condition.IsNot) { + return $"NOT ({sql})"; + } + + return sql; + } + + public override string WrapValue(string value) { + return base.WrapValue(value).ToUpperInvariant(); + } + + public override string CompileTrue() { + return "1"; + } + + public override string CompileFalse() { + return "0"; + } +} diff --git a/QueryBuilder/Compilers/MySqlCompiler.cs b/QueryBuilder/Compilers/MySqlCompiler.cs index 4729125b..ce171f9a 100644 --- a/QueryBuilder/Compilers/MySqlCompiler.cs +++ b/QueryBuilder/Compilers/MySqlCompiler.cs @@ -1,49 +1,41 @@ -namespace SqlKata.Compilers -{ - public class MySqlCompiler : Compiler - { - public MySqlCompiler() - { - OpeningIdentifier = ClosingIdentifier = "`"; - LastId = "SELECT last_insert_id() as Id"; - } - - public override string EngineCode { get; } = EngineCodes.MySql; - - public override string CompileLimit(SqlResult ctx) - { - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - - if (offset == 0 && limit == 0) - { - return null; - } - - if (offset == 0) - { - ctx.Bindings.Add(limit); - return $"LIMIT {parameterPlaceholder}"; - } - - if (limit == 0) - { - - // MySql will not accept offset without limit, so we will put a large number - // to avoid this error. - - ctx.Bindings.Add(offset); - return $"LIMIT 18446744073709551615 OFFSET {parameterPlaceholder}"; - } - - // We have both values - - ctx.Bindings.Add(limit); - ctx.Bindings.Add(offset); - - return $"LIMIT {parameterPlaceholder} OFFSET {parameterPlaceholder}"; - - } - } -} +namespace SqlKata.Compilers; +public class MySqlCompiler : Compiler { + public MySqlCompiler() { + OpeningIdentifier = ClosingIdentifier = "`"; + LastId = "SELECT last_insert_id() as Id"; + } + + public override string EngineCode { get; } = EngineCodes.MySql; + + public override string CompileLimit(SqlResult ctx) { + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + + if (offset == 0 && limit == 0) { + return null; + } + + if (offset == 0) { + ctx.Bindings.Add(limit); + return $"LIMIT {parameterPlaceholder}"; + } + + if (limit == 0) { + + // MySql will not accept offset without limit, so we will put a large number + // to avoid this error. + + ctx.Bindings.Add(offset); + return $"LIMIT 18446744073709551615 OFFSET {parameterPlaceholder}"; + } + + // We have both values + + ctx.Bindings.Add(limit); + ctx.Bindings.Add(offset); + + return $"LIMIT {parameterPlaceholder} OFFSET {parameterPlaceholder}"; + + } +} diff --git a/QueryBuilder/Compilers/OracleCompiler.cs b/QueryBuilder/Compilers/OracleCompiler.cs index 610ec20d..51b3369b 100644 --- a/QueryBuilder/Compilers/OracleCompiler.cs +++ b/QueryBuilder/Compilers/OracleCompiler.cs @@ -1,176 +1,155 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text.RegularExpressions; - -namespace SqlKata.Compilers -{ - public class OracleCompiler : Compiler - { - public OracleCompiler() - { - ColumnAsKeyword = ""; - TableAsKeyword = ""; - parameterPrefix = ":p"; - MultiInsertStartClause = "INSERT ALL INTO"; - } - - public override string EngineCode { get; } = EngineCodes.Oracle; - public bool UseLegacyPagination { get; set; } = false; - protected override string SingleRowDummyTableName => "DUAL"; - - protected override SqlResult CompileSelectQuery(Query query) - { - if (!UseLegacyPagination) - { - return base.CompileSelectQuery(query); - } - - var result = base.CompileSelectQuery(query); - - ApplyLegacyLimit(result); - - return result; - } - - public override string CompileLimit(SqlResult ctx) - { - if (UseLegacyPagination) - { - // in pre-12c versions of Oracle, limit is handled by ROWNUM techniques - return null; - } - - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - if (limit == 0 && offset == 0) - { - return null; - } - - var safeOrder = ""; - - if (!ctx.Query.HasComponent("order")) - { - safeOrder = "ORDER BY (SELECT 0 FROM DUAL) "; - } - - if (limit == 0) - { - ctx.Bindings.Add(offset); - return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS"; - } - - ctx.Bindings.Add(offset); - ctx.Bindings.Add(limit); - - return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS FETCH NEXT {parameterPlaceholder} ROWS ONLY"; - } - - internal void ApplyLegacyLimit(SqlResult ctx) - { - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - if (limit == 0 && offset == 0) - { - return; - } - - string newSql; - if (limit == 0) - { - newSql = $"SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM ({ctx.RawSql}) \"results_wrapper\") WHERE \"row_num\" > {parameterPlaceholder}"; - ctx.Bindings.Add(offset); - } - else if (offset == 0) - { - newSql = $"SELECT * FROM ({ctx.RawSql}) WHERE ROWNUM <= {parameterPlaceholder}"; - ctx.Bindings.Add(limit); - } - else - { - newSql = $"SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM ({ctx.RawSql}) \"results_wrapper\" WHERE ROWNUM <= {parameterPlaceholder}) WHERE \"row_num\" > {parameterPlaceholder}"; - ctx.Bindings.Add(limit + offset); - ctx.Bindings.Add(offset); - } - - ctx.RawSql = newSql; - } - - protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) - { - - var column = Wrap(condition.Column); - var value = Parameter(ctx, condition.Value); - - var sql = ""; - var valueFormat = ""; - - var isDateTime = (condition.Value is DateTime dt); - - switch (condition.Part) - { - case "date": // assume YY-MM-DD format - if (isDateTime) - valueFormat = $"{value}"; - else - valueFormat = $"TO_DATE({value}, 'YY-MM-DD')"; - sql = $"TO_CHAR({column}, 'YY-MM-DD') {condition.Operator} TO_CHAR({valueFormat}, 'YY-MM-DD')"; - break; - case "time": - if (isDateTime) - valueFormat = $"{value}"; - else - { - // assume HH:MM format - if (condition.Value.ToString().Split(':').Count() == 2) - valueFormat = $"TO_DATE({value}, 'HH24:MI')"; - else // assume HH:MM:SS format - valueFormat = $"TO_DATE({value}, 'HH24:MI:SS')"; - } - sql = $"TO_CHAR({column}, 'HH24:MI:SS') {condition.Operator} TO_CHAR({valueFormat}, 'HH24:MI:SS')"; - break; - case "year": - case "month": - case "day": - case "hour": - case "minute": - case "second": - sql = $"EXTRACT({condition.Part.ToUpperInvariant()} FROM {column}) {condition.Operator} {value}"; - break; - default: - sql = $"{column} {condition.Operator} {value}"; - break; - } - - if (condition.IsNot) - { - return $"NOT ({sql})"; - } - - return sql; - - } - - protected override SqlResult CompileRemainingInsertClauses( - SqlResult ctx, string table, IEnumerable inserts) - { - foreach (var insert in inserts.Skip(1)) - { - string columns = GetInsertColumnsList(insert.Columns); - string values = string.Join(", ", Parameterize(ctx, insert.Values)); - - string intoFormat = " INTO {0}{1} VALUES ({2})"; - var nextInsert = string.Format(intoFormat, table, columns, values); - - ctx.RawSql += nextInsert; - } - - ctx.RawSql += " SELECT 1 FROM DUAL"; - return ctx; - } - } -} +namespace SqlKata.Compilers; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; + +public class OracleCompiler : Compiler { + public OracleCompiler() { + ColumnAsKeyword = ""; + TableAsKeyword = ""; + parameterPrefix = ":p"; + MultiInsertStartClause = "INSERT ALL INTO"; + } + + public override string EngineCode { get; } = EngineCodes.Oracle; + public bool UseLegacyPagination { get; set; } = false; + protected override string SingleRowDummyTableName => "DUAL"; + + protected override SqlResult CompileSelectQuery(Query query) { + if (!UseLegacyPagination) { + return base.CompileSelectQuery(query); + } + + var result = base.CompileSelectQuery(query); + + ApplyLegacyLimit(result); + + return result; + } + + public override string CompileLimit(SqlResult ctx) { + if (UseLegacyPagination) { + // in pre-12c versions of Oracle, limit is handled by ROWNUM techniques + return null; + } + + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit == 0 && offset == 0) { + return null; + } + + var safeOrder = ""; + + if (!ctx.Query.HasComponent("order")) { + safeOrder = "ORDER BY (SELECT 0 FROM DUAL) "; + } + + if (limit == 0) { + ctx.Bindings.Add(offset); + return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS"; + } + + ctx.Bindings.Add(offset); + ctx.Bindings.Add(limit); + + return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS FETCH NEXT {parameterPlaceholder} ROWS ONLY"; + } + + internal void ApplyLegacyLimit(SqlResult ctx) { + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit == 0 && offset == 0) { + return; + } + + string newSql; + if (limit == 0) { + newSql = $"SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM ({ctx.RawSql}) \"results_wrapper\") WHERE \"row_num\" > {parameterPlaceholder}"; + ctx.Bindings.Add(offset); + } + else if (offset == 0) { + newSql = $"SELECT * FROM ({ctx.RawSql}) WHERE ROWNUM <= {parameterPlaceholder}"; + ctx.Bindings.Add(limit); + } + else { + newSql = $"SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM ({ctx.RawSql}) \"results_wrapper\" WHERE ROWNUM <= {parameterPlaceholder}) WHERE \"row_num\" > {parameterPlaceholder}"; + ctx.Bindings.Add(limit + offset); + ctx.Bindings.Add(offset); + } + + ctx.RawSql = newSql; + } + + protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) { + + var column = Wrap(condition.Column); + var value = Parameter(ctx, condition.Value); + + var sql = ""; + var valueFormat = ""; + + var isDateTime = (condition.Value is DateTime dt); + + switch (condition.Part) { + case "date": // assume YY-MM-DD format + if (isDateTime) + valueFormat = $"{value}"; + else + valueFormat = $"TO_DATE({value}, 'YY-MM-DD')"; + sql = $"TO_CHAR({column}, 'YY-MM-DD') {condition.Operator} TO_CHAR({valueFormat}, 'YY-MM-DD')"; + break; + case "time": + if (isDateTime) + valueFormat = $"{value}"; + else { + // assume HH:MM format + if (condition.Value.ToString().Split(':').Count() == 2) + valueFormat = $"TO_DATE({value}, 'HH24:MI')"; + else // assume HH:MM:SS format + valueFormat = $"TO_DATE({value}, 'HH24:MI:SS')"; + } + sql = $"TO_CHAR({column}, 'HH24:MI:SS') {condition.Operator} TO_CHAR({valueFormat}, 'HH24:MI:SS')"; + break; + case "year": + case "month": + case "day": + case "hour": + case "minute": + case "second": + sql = $"EXTRACT({condition.Part.ToUpperInvariant()} FROM {column}) {condition.Operator} {value}"; + break; + default: + sql = $"{column} {condition.Operator} {value}"; + break; + } + + if (condition.IsNot) { + return $"NOT ({sql})"; + } + + return sql; + + } + + protected override SqlResult CompileRemainingInsertClauses( + SqlResult ctx, string table, IEnumerable inserts) { + foreach (var insert in inserts.Skip(1)) { + string columns = GetInsertColumnsList(insert.Columns); + string values = string.Join(", ", Parameterize(ctx, insert.Values)); + + string intoFormat = " INTO {0}{1} VALUES ({2})"; + var nextInsert = string.Format(intoFormat, table, columns, values); + + ctx.RawSql += nextInsert; + } + + ctx.RawSql += " SELECT 1 FROM DUAL"; + return ctx; + } +} diff --git a/QueryBuilder/Compilers/PostgresCompiler.cs b/QueryBuilder/Compilers/PostgresCompiler.cs index 3b45d0e6..dc808659 100644 --- a/QueryBuilder/Compilers/PostgresCompiler.cs +++ b/QueryBuilder/Compilers/PostgresCompiler.cs @@ -1,99 +1,84 @@ -using System; -using System.Linq; - -namespace SqlKata.Compilers -{ - public class PostgresCompiler : Compiler - { - public PostgresCompiler() - { - LastId = "SELECT lastval() AS id"; - } - - public override string EngineCode { get; } = EngineCodes.PostgreSql; - public override bool SupportsFilterClause { get; set; } = true; - - - protected override string CompileBasicStringCondition(SqlResult ctx, BasicStringCondition x) - { - - var column = Wrap(x.Column); - - var value = Resolve(ctx, x.Value) as string; - - if (value == null) - { - throw new ArgumentException("Expecting a non nullable string"); - } - - var method = x.Operator; - - if (new[] { "starts", "ends", "contains", "like", "ilike" }.Contains(x.Operator)) - { - method = x.CaseSensitive ? "LIKE" : "ILIKE"; - - switch (x.Operator) - { - case "starts": - value = $"{value}%"; - break; - case "ends": - value = $"%{value}"; - break; - case "contains": - value = $"%{value}%"; - break; - } - } - - string sql; - - if (x.Value is UnsafeLiteral) - { - sql = $"{column} {checkOperator(method)} {value}"; - } - else - { - sql = $"{column} {checkOperator(method)} {Parameter(ctx, value)}"; - } - - if (!string.IsNullOrEmpty(x.EscapeCharacter)) - { - sql = $"{sql} ESCAPE '{x.EscapeCharacter}'"; - } - - return x.IsNot ? $"NOT ({sql})" : sql; - - } - - - protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) - { - var column = Wrap(condition.Column); - - string left; - - if (condition.Part == "time") - { - left = $"{column}::time"; - } - else if (condition.Part == "date") - { - left = $"{column}::date"; - } - else - { - left = $"DATE_PART('{condition.Part.ToUpperInvariant()}', {column})"; - } - - var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; - - if (condition.IsNot) - { - return $"NOT ({sql})"; - } - - return sql; - } - } -} +namespace SqlKata.Compilers; + +using System; +using System.Linq; + +public class PostgresCompiler : Compiler { + public PostgresCompiler() { + LastId = "SELECT lastval() AS id"; + } + + public override string EngineCode { get; } = EngineCodes.PostgreSql; + public override bool SupportsFilterClause { get; set; } = true; + + + protected override string CompileBasicStringCondition(SqlResult ctx, BasicStringCondition x) { + + var column = Wrap(x.Column); + + var value = Resolve(ctx, x.Value) as string; + + if (value == null) { + throw new ArgumentException("Expecting a non nullable string"); + } + + var method = x.Operator; + + if (new[] { "starts", "ends", "contains", "like", "ilike" }.Contains(x.Operator)) { + method = x.CaseSensitive ? "LIKE" : "ILIKE"; + + switch (x.Operator) { + case "starts": + value = $"{value}%"; + break; + case "ends": + value = $"%{value}"; + break; + case "contains": + value = $"%{value}%"; + break; + } + } + + string sql; + + if (x.Value is UnsafeLiteral) { + sql = $"{column} {checkOperator(method)} {value}"; + } + else { + sql = $"{column} {checkOperator(method)} {Parameter(ctx, value)}"; + } + + if (!string.IsNullOrEmpty(x.EscapeCharacter)) { + sql = $"{sql} ESCAPE '{x.EscapeCharacter}'"; + } + + return x.IsNot ? $"NOT ({sql})" : sql; + + } + + + protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) { + var column = Wrap(condition.Column); + + string left; + + if (condition.Part == "time") { + left = $"{column}::time"; + } + else if (condition.Part == "date") { + left = $"{column}::date"; + } + else { + left = $"DATE_PART('{condition.Part.ToUpperInvariant()}', {column})"; + } + + var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; + + if (condition.IsNot) { + return $"NOT ({sql})"; + } + + return sql; + } +} diff --git a/QueryBuilder/Compilers/SqlServerCompiler.cs b/QueryBuilder/Compilers/SqlServerCompiler.cs index 98b488bf..aaf7a356 100644 --- a/QueryBuilder/Compilers/SqlServerCompiler.cs +++ b/QueryBuilder/Compilers/SqlServerCompiler.cs @@ -1,190 +1,164 @@ -using System.Linq; - -namespace SqlKata.Compilers -{ - public class SqlServerCompiler : Compiler - { - public SqlServerCompiler() - { - OpeningIdentifier = "["; - ClosingIdentifier = "]"; - LastId = "SELECT scope_identity() as Id"; - } - - public override string EngineCode { get; } = EngineCodes.SqlServer; - public bool UseLegacyPagination { get; set; } = false; - - protected override SqlResult CompileSelectQuery(Query query) - { - if (!UseLegacyPagination || !query.HasOffset(EngineCode)) - { - return base.CompileSelectQuery(query); - } - - query = query.Clone(); - - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) - { - Query = query, - }; - - var limit = query.GetLimit(EngineCode); - var offset = query.GetOffset(EngineCode); - - - if (!query.HasComponent("select")) - { - query.Select("*"); - } - - var order = CompileOrders(ctx) ?? "ORDER BY (SELECT 0)"; - - query.SelectRaw($"ROW_NUMBER() OVER ({order}) AS [row_num]", ctx.Bindings.ToArray()); - - query.ClearComponent("order"); - - - var result = base.CompileSelectQuery(query); - - if (limit == 0) - { - result.RawSql = $"SELECT * FROM ({result.RawSql}) AS [results_wrapper] WHERE [row_num] >= {parameterPlaceholder}"; - result.Bindings.Add(offset + 1); - } - else - { - result.RawSql = $"SELECT * FROM ({result.RawSql}) AS [results_wrapper] WHERE [row_num] BETWEEN {parameterPlaceholder} AND {parameterPlaceholder}"; - result.Bindings.Add(offset + 1); - result.Bindings.Add(limit + offset); - } - - return result; - } - - protected override string CompileColumns(SqlResult ctx) - { - var compiled = base.CompileColumns(ctx); - - if (!UseLegacyPagination) - { - return compiled; - } - - // If there is a limit on the query, but not an offset, we will add the top - // clause to the query, which serves as a "limit" type clause within the - // SQL Server system similar to the limit keywords available in MySQL. - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - if (limit > 0 && offset == 0) - { - // top bindings should be inserted first - ctx.Bindings.Insert(0, limit); - - ctx.Query.ClearComponent("limit"); - - // handle distinct - if (compiled.IndexOf("SELECT DISTINCT") == 0) - { - return $"SELECT DISTINCT TOP ({parameterPlaceholder}){compiled.Substring(15)}"; - } - - return $"SELECT TOP ({parameterPlaceholder}){compiled.Substring(6)}"; - } - - return compiled; - } - - public override string CompileLimit(SqlResult ctx) - { - if (UseLegacyPagination) - { - // in legacy versions of Sql Server, limit is handled by TOP - // and ROW_NUMBER techniques - return null; - } - - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - if (limit == 0 && offset == 0) - { - return null; - } - - var safeOrder = ""; - if (!ctx.Query.HasComponent("order")) - { - safeOrder = "ORDER BY (SELECT 0) "; - } - - if (limit == 0) - { - ctx.Bindings.Add(offset); - return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS"; - } - - ctx.Bindings.Add(offset); - ctx.Bindings.Add(limit); - - return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS FETCH NEXT {parameterPlaceholder} ROWS ONLY"; - } - - public override string CompileRandom(string seed) - { - return "NEWID()"; - } - - public override string CompileTrue() - { - return "cast(1 as bit)"; - } - - public override string CompileFalse() - { - return "cast(0 as bit)"; - } - - protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) - { - var column = Wrap(condition.Column); - var part = condition.Part.ToUpperInvariant(); - - string left; - - if (part == "TIME" || part == "DATE") - { - left = $"CAST({column} AS {part.ToUpperInvariant()})"; - } - else - { - left = $"DATEPART({part.ToUpperInvariant()}, {column})"; - } - - var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; - - if (condition.IsNot) - { - return $"NOT ({sql})"; - } - - return sql; - } - - protected override SqlResult CompileAdHocQuery(AdHocTableFromClause adHoc) - { - var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter); - - var colNames = string.Join(", ", adHoc.Columns.Select(Wrap)); - - var valueRow = string.Join(", ", Enumerable.Repeat(parameterPlaceholder, adHoc.Columns.Count)); - var valueRows = string.Join(", ", Enumerable.Repeat($"({valueRow})", adHoc.Values.Count / adHoc.Columns.Count)); - var sql = $"SELECT {colNames} FROM (VALUES {valueRows}) AS tbl ({colNames})"; - - ctx.RawSql = sql; - ctx.Bindings = adHoc.Values; - - return ctx; - } - } -} +namespace SqlKata.Compilers; + +using System.Linq; + +public class SqlServerCompiler : Compiler { + public SqlServerCompiler() { + OpeningIdentifier = "["; + ClosingIdentifier = "]"; + LastId = "SELECT scope_identity() as Id"; + } + + public override string EngineCode { get; } = EngineCodes.SqlServer; + public bool UseLegacyPagination { get; set; } = false; + + protected override SqlResult CompileSelectQuery(Query query) { + if (!UseLegacyPagination || !query.HasOffset(EngineCode)) { + return base.CompileSelectQuery(query); + } + + query = query.Clone(); + + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) { + Query = query, + }; + + var limit = query.GetLimit(EngineCode); + var offset = query.GetOffset(EngineCode); + + + if (!query.HasComponent("select")) { + query.Select("*"); + } + + var order = CompileOrders(ctx) ?? "ORDER BY (SELECT 0)"; + + query.SelectRaw($"ROW_NUMBER() OVER ({order}) AS [row_num]", ctx.Bindings.ToArray()); + + query.ClearComponent("order"); + + + var result = base.CompileSelectQuery(query); + + if (limit == 0) { + result.RawSql = $"SELECT * FROM ({result.RawSql}) AS [results_wrapper] WHERE [row_num] >= {parameterPlaceholder}"; + result.Bindings.Add(offset + 1); + } + else { + result.RawSql = $"SELECT * FROM ({result.RawSql}) AS [results_wrapper] WHERE [row_num] BETWEEN {parameterPlaceholder} AND {parameterPlaceholder}"; + result.Bindings.Add(offset + 1); + result.Bindings.Add(limit + offset); + } + + return result; + } + + protected override string CompileColumns(SqlResult ctx) { + var compiled = base.CompileColumns(ctx); + + if (!UseLegacyPagination) { + return compiled; + } + + // If there is a limit on the query, but not an offset, we will add the top + // clause to the query, which serves as a "limit" type clause within the + // SQL Server system similar to the limit keywords available in MySQL. + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit > 0 && offset == 0) { + // top bindings should be inserted first + ctx.Bindings.Insert(0, limit); + + ctx.Query.ClearComponent("limit"); + + // handle distinct + if (compiled.IndexOf("SELECT DISTINCT") == 0) { + return $"SELECT DISTINCT TOP ({parameterPlaceholder}){compiled.Substring(15)}"; + } + + return $"SELECT TOP ({parameterPlaceholder}){compiled.Substring(6)}"; + } + + return compiled; + } + + public override string CompileLimit(SqlResult ctx) { + if (UseLegacyPagination) { + // in legacy versions of Sql Server, limit is handled by TOP + // and ROW_NUMBER techniques + return null; + } + + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit == 0 && offset == 0) { + return null; + } + + var safeOrder = ""; + if (!ctx.Query.HasComponent("order")) { + safeOrder = "ORDER BY (SELECT 0) "; + } + + if (limit == 0) { + ctx.Bindings.Add(offset); + return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS"; + } + + ctx.Bindings.Add(offset); + ctx.Bindings.Add(limit); + + return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS FETCH NEXT {parameterPlaceholder} ROWS ONLY"; + } + + public override string CompileRandom(string seed) { + return "NEWID()"; + } + + public override string CompileTrue() { + return "cast(1 as bit)"; + } + + public override string CompileFalse() { + return "cast(0 as bit)"; + } + + protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) { + var column = Wrap(condition.Column); + var part = condition.Part.ToUpperInvariant(); + + string left; + + if (part == "TIME" || part == "DATE") { + left = $"CAST({column} AS {part.ToUpperInvariant()})"; + } + else { + left = $"DATEPART({part.ToUpperInvariant()}, {column})"; + } + + var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; + + if (condition.IsNot) { + return $"NOT ({sql})"; + } + + return sql; + } + + protected override SqlResult CompileAdHocQuery(AdHocTableFromClause adHoc) { + var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter); + + var colNames = string.Join(", ", adHoc.Columns.Select(Wrap)); + + var valueRow = string.Join(", ", Enumerable.Repeat(parameterPlaceholder, adHoc.Columns.Count)); + var valueRows = string.Join(", ", Enumerable.Repeat($"({valueRow})", adHoc.Values.Count / adHoc.Columns.Count)); + var sql = $"SELECT {colNames} FROM (VALUES {valueRows}) AS tbl ({colNames})"; + + ctx.RawSql = sql; + ctx.Bindings = adHoc.Values; + + return ctx; + } +} diff --git a/QueryBuilder/Compilers/SqliteCompiler.cs b/QueryBuilder/Compilers/SqliteCompiler.cs index 1401dd35..0ca3f222 100644 --- a/QueryBuilder/Compilers/SqliteCompiler.cs +++ b/QueryBuilder/Compilers/SqliteCompiler.cs @@ -1,67 +1,58 @@ -using System.Collections.Generic; - -namespace SqlKata.Compilers -{ - public class SqliteCompiler : Compiler - { - public override string EngineCode { get; } = EngineCodes.Sqlite; - protected override string OpeningIdentifier { get; set; } = "\""; - protected override string ClosingIdentifier { get; set; } = "\""; - protected override string LastId { get; set; } = "select last_insert_rowid() as id"; - public override bool SupportsFilterClause { get; set; } = true; - - public override string CompileTrue() - { - return "1"; - } - - public override string CompileFalse() - { - return "0"; - } - - public override string CompileLimit(SqlResult ctx) - { - var limit = ctx.Query.GetLimit(EngineCode); - var offset = ctx.Query.GetOffset(EngineCode); - - if (limit == 0 && offset > 0) - { - ctx.Bindings.Add(offset); - return $"LIMIT -1 OFFSET {parameterPlaceholder}"; - } - - return base.CompileLimit(ctx); - } - - protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) - { - var column = Wrap(condition.Column); - var value = Parameter(ctx, condition.Value); - - var formatMap = new Dictionary { - { "date", "%Y-%m-%d" }, - { "time", "%H:%M:%S" }, - { "year", "%Y" }, - { "month", "%m" }, - { "day", "%d" }, - { "hour", "%H" }, - { "minute", "%M" }, - }; - - if (!formatMap.ContainsKey(condition.Part)) - { - return $"{column} {condition.Operator} {value}"; - } - - var sql = $"strftime('{formatMap[condition.Part]}', {column}) {condition.Operator} cast({value} as text)"; - - if (condition.IsNot) - { - return $"NOT ({sql})"; - } - - return sql; - } - } -} +namespace SqlKata.Compilers; + +using System.Collections.Generic; + +public class SqliteCompiler : Compiler { + public override string EngineCode { get; } = EngineCodes.Sqlite; + protected override string OpeningIdentifier { get; set; } = "\""; + protected override string ClosingIdentifier { get; set; } = "\""; + protected override string LastId { get; set; } = "select last_insert_rowid() as id"; + public override bool SupportsFilterClause { get; set; } = true; + + public override string CompileTrue() { + return "1"; + } + + public override string CompileFalse() { + return "0"; + } + + public override string CompileLimit(SqlResult ctx) { + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit == 0 && offset > 0) { + ctx.Bindings.Add(offset); + return $"LIMIT -1 OFFSET {parameterPlaceholder}"; + } + + return base.CompileLimit(ctx); + } + + protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) { + var column = Wrap(condition.Column); + var value = Parameter(ctx, condition.Value); + + var formatMap = new Dictionary { + { "date", "%Y-%m-%d" }, + { "time", "%H:%M:%S" }, + { "year", "%Y" }, + { "month", "%m" }, + { "day", "%d" }, + { "hour", "%H" }, + { "minute", "%M" }, + }; + + if (!formatMap.ContainsKey(condition.Part)) { + return $"{column} {condition.Operator} {value}"; + } + + var sql = $"strftime('{formatMap[condition.Part]}', {column}) {condition.Operator} cast({value} as text)"; + + if (condition.IsNot) { + return $"NOT ({sql})"; + } + + return sql; + } +} diff --git a/QueryBuilder/Expressions.cs b/QueryBuilder/Expressions.cs index 8a5fdae1..94cffa56 100644 --- a/QueryBuilder/Expressions.cs +++ b/QueryBuilder/Expressions.cs @@ -1,28 +1,23 @@ -namespace SqlKata -{ - public static class Expressions - { - /// - /// Instruct the compiler to resolve the value from the predefined variables - /// In the current query or parents queries. - /// - /// - /// - public static Variable Variable(string name) - { - return new Variable(name); - } - - /// - /// Instruct the compiler to treat this as a literal. - /// WARNING: don't pass user data directly to this method. - /// - /// - /// if true it will esacpe single quotes - /// - public static UnsafeLiteral UnsafeLiteral(string value, bool replaceQuotes = true) - { - return new UnsafeLiteral(value, replaceQuotes); - } - } -} +namespace SqlKata; +public static class Expressions { + /// + /// Instruct the compiler to resolve the value from the predefined variables + /// In the current query or parents queries. + /// + /// + /// + public static Variable Variable(string name) { + return new Variable(name); + } + + /// + /// Instruct the compiler to treat this as a literal. + /// WARNING: don't pass user data directly to this method. + /// + /// + /// if true it will esacpe single quotes + /// + public static UnsafeLiteral UnsafeLiteral(string value, bool replaceQuotes = true) { + return new UnsafeLiteral(value, replaceQuotes); + } +} diff --git a/QueryBuilder/Extensions/QueryForExtensions.cs b/QueryBuilder/Extensions/QueryForExtensions.cs index a785a5f0..8f5e6fba 100644 --- a/QueryBuilder/Extensions/QueryForExtensions.cs +++ b/QueryBuilder/Extensions/QueryForExtensions.cs @@ -1,39 +1,31 @@ -using System; -using SqlKata.Compilers; - -namespace SqlKata.Extensions -{ - public static class QueryForExtensions - { - public static Query ForFirebird(this Query src, Func fn) - { - return src.For(EngineCodes.Firebird, fn); - } - - public static Query ForMySql(this Query src, Func fn) - { - return src.For(EngineCodes.MySql, fn); - } - - public static Query ForOracle(this Query src, Func fn) - { - return src.For(EngineCodes.Oracle, fn); - } - - public static Query ForPostgreSql(this Query src, Func fn) - { - return src.For(EngineCodes.PostgreSql, fn); - } - - public static Query ForSqlite(this Query src, Func fn) - { - return src.For(EngineCodes.Sqlite, fn); - } - - public static Query ForSqlServer(this Query src, Func fn) - { - return src.For(EngineCodes.SqlServer, fn); - } - - } -} +namespace SqlKata.Extensions; + +using System; +using SqlKata.Compilers; + +public static class QueryForExtensions { + public static Query ForFirebird(this Query src, Func fn) { + return src.For(EngineCodes.Firebird, fn); + } + + public static Query ForMySql(this Query src, Func fn) { + return src.For(EngineCodes.MySql, fn); + } + + public static Query ForOracle(this Query src, Func fn) { + return src.For(EngineCodes.Oracle, fn); + } + + public static Query ForPostgreSql(this Query src, Func fn) { + return src.For(EngineCodes.PostgreSql, fn); + } + + public static Query ForSqlite(this Query src, Func fn) { + return src.For(EngineCodes.Sqlite, fn); + } + + public static Query ForSqlServer(this Query src, Func fn) { + return src.For(EngineCodes.SqlServer, fn); + } + +} diff --git a/QueryBuilder/Helper.cs b/QueryBuilder/Helper.cs index 9735dee3..997d381a 100644 --- a/QueryBuilder/Helper.cs +++ b/QueryBuilder/Helper.cs @@ -1,178 +1,149 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace SqlKata -{ - public static class Helper - { - public static bool IsArray(object value) - { - if (value is string) - { - return false; - } - - if (value is byte[]) - { - return false; - } - - return value is IEnumerable; - } - - /// - /// Flat IEnumerable one level down - /// - /// - /// - public static IEnumerable Flatten(IEnumerable array) - { - foreach (var item in array) - { - if (IsArray(item)) - { - foreach (var sub in (item as IEnumerable)) - { - yield return sub; - } - } - else - { - yield return item; - } - - } - } - - public static IEnumerable FlattenDeep(IEnumerable array) - { - return array.SelectMany(o => IsArray(o) ? FlattenDeep(o as IEnumerable) : new[] { o }); - } - - public static IEnumerable AllIndexesOf(string str, string value) - { - if (string.IsNullOrEmpty(value)) - { - yield break; - } - - var index = 0; - - do - { - index = str.IndexOf(value, index, StringComparison.Ordinal); - - if (index == -1) - { - yield break; - } - - yield return index; - - } while ((index += value.Length) < str.Length); - } - - public static string ReplaceAll(string subject, string match, string escapeCharacter, Func callback) - { - if (string.IsNullOrWhiteSpace(subject) || !subject.Contains(match)) - { - return subject; - } - - var splitted = Regex.Split(subject, $@"(? callback(index) + item) - .Aggregate(new StringBuilder(splitted.First()), (prev, right) => prev.Append(right)) - .ToString(); - } - - public static string RemoveEscapeCharacter(string subject, string match, string escapeCharacter) - { - var escapedRegex = new Regex($@"{Regex.Escape(escapeCharacter)}{Regex.Escape(match)}"); - return escapedRegex.Replace(subject, match); - } - - public static string JoinArray(string glue, IEnumerable array) - { - var result = new List(); - - foreach (var item in array) - { - result.Add(item.ToString()); - } - - return string.Join(glue, result); - } - - public static string ExpandParameters(string sql, string placeholder, string escapeCharacter, object[] bindings) - { - return ReplaceAll(sql, placeholder, escapeCharacter ,i => - { - var parameter = bindings[i]; - - if (IsArray(parameter)) - { - var count = EnumerableCount(parameter as IEnumerable); - return string.Join(",", placeholder.Repeat(count)); - } - - return placeholder.ToString(); - }); - } - - public static int EnumerableCount(IEnumerable obj) - { - int count = 0; - - foreach (var item in obj) - { - count++; - } - - return count; - } - - public static List ExpandExpression(string expression) - { - var regex = @"^(?:\w+\.){1,2}{([^}]*)}"; - var match = Regex.Match(expression, regex, RegexOptions.Multiline); - - if (!match.Success) - { - // we did not found a match return the string as is. - return new List { expression }; - } - - var table = expression.Substring(0, expression.IndexOf(".{")); - - var captures = match.Groups[1].Value; - - var cols = Regex.Split(captures, @"\s*,\s*", RegexOptions.Multiline) - .Select(x => $"{table}.{x.Trim()}") - .ToList(); - - return cols; - } - - public static IEnumerable Repeat(this string str, int count) - { - return Enumerable.Repeat(str, count); - } - - public static string ReplaceIdentifierUnlessEscaped(this string input, string escapeCharacter, string identifier, string newIdentifier) - { - //Replace standard, non-escaped identifiers first - var nonEscapedRegex = new Regex($@"(? + /// Flat IEnumerable one level down + /// + /// + /// + public static IEnumerable Flatten(IEnumerable array) { + foreach (var item in array) { + if (IsArray(item)) { + foreach (var sub in (item as IEnumerable)) { + yield return sub; + } + } + else { + yield return item; + } + + } + } + + public static IEnumerable FlattenDeep(IEnumerable array) { + return array.SelectMany(o => IsArray(o) ? FlattenDeep(o as IEnumerable) : new[] { o }); + } + + public static IEnumerable AllIndexesOf(string str, string value) { + if (string.IsNullOrEmpty(value)) { + yield break; + } + + var index = 0; + + do { + index = str.IndexOf(value, index, StringComparison.Ordinal); + + if (index == -1) { + yield break; + } + + yield return index; + + } while ((index += value.Length) < str.Length); + } + + public static string ReplaceAll(string subject, string match, string escapeCharacter, Func callback) { + if (string.IsNullOrWhiteSpace(subject) || !subject.Contains(match)) { + return subject; + } + + var splitted = Regex.Split(subject, $@"(? callback(index) + item) + .Aggregate(new StringBuilder(splitted.First()), (prev, right) => prev.Append(right)) + .ToString(); + } + + public static string RemoveEscapeCharacter(string subject, string match, string escapeCharacter) { + var escapedRegex = new Regex($@"{Regex.Escape(escapeCharacter)}{Regex.Escape(match)}"); + return escapedRegex.Replace(subject, match); + } + + public static string JoinArray(string glue, IEnumerable array) { + var result = new List(); + + foreach (var item in array) { + result.Add(item.ToString()); + } + + return string.Join(glue, result); + } + + public static string ExpandParameters(string sql, string placeholder, string escapeCharacter, object[] bindings) { + return ReplaceAll(sql, placeholder, escapeCharacter, i => { + var parameter = bindings[i]; + + if (IsArray(parameter)) { + var count = EnumerableCount(parameter as IEnumerable); + return string.Join(",", placeholder.Repeat(count)); + } + + return placeholder.ToString(); + }); + } + + public static int EnumerableCount(IEnumerable obj) { + int count = 0; + + foreach (var item in obj) { + count++; + } + + return count; + } + + public static List ExpandExpression(string expression) { + var regex = @"^(?:\w+\.){1,2}{([^}]*)}"; + var match = Regex.Match(expression, regex, RegexOptions.Multiline); + + if (!match.Success) { + // we did not found a match return the string as is. + return new List { expression }; + } + + var table = expression.Substring(0, expression.IndexOf(".{")); + + var captures = match.Groups[1].Value; + + var cols = Regex.Split(captures, @"\s*,\s*", RegexOptions.Multiline) + .Select(x => $"{table}.{x.Trim()}") + .ToList(); + + return cols; + } + + public static IEnumerable Repeat(this string str, int count) { + return Enumerable.Repeat(str, count); + } + + public static string ReplaceIdentifierUnlessEscaped(this string input, string escapeCharacter, string identifier, string newIdentifier) { + //Replace standard, non-escaped identifiers first + var nonEscapedRegex = new Regex($@"(? - /// This class is used as metadata to ignore a property on insert and update queries - /// - /// - /// - /// public class Person - /// { - /// public string Name {get ;set;} - /// - /// [Ignore] - /// public string PhoneNumber {get ;set;} - /// - /// } - /// - /// new Query("Table").Insert(new Person { Name = "User", PhoneNumber = "70123456" }) - /// - /// output: INSERT INTO [Table] ([Name]) VALUES('User') - /// - /// - public class IgnoreAttribute : Attribute - { - } -} +namespace SqlKata; + +using System; + +/// +/// This class is used as metadata to ignore a property on insert and update queries +/// +/// +/// +/// public class Person +/// { +/// public string Name {get ;set;} +/// +/// [Ignore] +/// public string PhoneNumber {get ;set;} +/// +/// } +/// +/// new Query("Table").Insert(new Person { Name = "User", PhoneNumber = "70123456" }) +/// +/// output: INSERT INTO [Table] ([Name]) VALUES('User') +/// +/// +public class IgnoreAttribute : Attribute { +} diff --git a/QueryBuilder/Include.cs b/QueryBuilder/Include.cs index e0cbfe02..99933ef7 100644 --- a/QueryBuilder/Include.cs +++ b/QueryBuilder/Include.cs @@ -1,11 +1,8 @@ -namespace SqlKata -{ - public class Include - { - public string Name { get; set; } - public Query Query { get; set; } - public string ForeignKey { get; set; } - public string LocalKey { get; set; } - public bool IsMany { get; set; } - } -} +namespace SqlKata; +public class Include { + public string Name { get; set; } + public Query Query { get; set; } + public string ForeignKey { get; set; } + public string LocalKey { get; set; } + public bool IsMany { get; set; } +} diff --git a/QueryBuilder/Join.cs b/QueryBuilder/Join.cs index ae0969a6..2875526b 100644 --- a/QueryBuilder/Join.cs +++ b/QueryBuilder/Join.cs @@ -1,77 +1,65 @@ -using System; - -namespace SqlKata -{ - public class Join : BaseQuery - { - protected string _type = "inner join"; - - public string Type - { - get - { - return _type; - } - set - { - _type = value.ToUpperInvariant(); - } - } - - public Join() : base() - { - } - - public override Join Clone() - { - var clone = base.Clone(); - clone._type = _type; - return clone; - } - - public Join AsType(string type) - { - Type = type; - return this; - } - - /// - /// Alias for "from" operator. - /// Since "from" does not sound well with join clauses - /// - /// - /// - public Join JoinWith(string table) => From(table); - public Join JoinWith(Query query) => From(query); - public Join JoinWith(Func callback) => From(callback); - - public Join AsInner() => AsType("inner join"); - public Join AsOuter() => AsType("outer join"); - public Join AsLeft() => AsType("left join"); - public Join AsRight() => AsType("right join"); - public Join AsCross() => AsType("cross join"); - - public Join On(string first, string second, string op = "=") - { - return AddComponent("where", new TwoColumnsCondition - { - First = first, - Second = second, - Operator = op, - IsOr = GetOr(), - IsNot = GetNot() - }); - - } - - public Join OrOn(string first, string second, string op = "=") - { - return Or().On(first, second, op); - } - - public override Join NewQuery() - { - return new Join(); - } - } -} +namespace SqlKata; + +using System; + +public class Join : BaseQuery { + protected string _type = "inner join"; + + public string Type { + get { + return _type; + } + set { + _type = value.ToUpperInvariant(); + } + } + + public Join() : base() { + } + + public override Join Clone() { + var clone = base.Clone(); + clone._type = _type; + return clone; + } + + public Join AsType(string type) { + Type = type; + return this; + } + + /// + /// Alias for "from" operator. + /// Since "from" does not sound well with join clauses + /// + /// + /// + public Join JoinWith(string table) => From(table); + public Join JoinWith(Query query) => From(query); + public Join JoinWith(Func callback) => From(callback); + + public Join AsInner() => AsType("inner join"); + public Join AsOuter() => AsType("outer join"); + public Join AsLeft() => AsType("left join"); + public Join AsRight() => AsType("right join"); + public Join AsCross() => AsType("cross join"); + + public Join On(string first, string second, string op = "=") { + return AddComponent("where", new TwoColumnsCondition { + First = first, + Second = second, + Operator = op, + IsOr = GetOr(), + IsNot = GetNot() + }); + + } + + public Join OrOn(string first, string second, string op = "=") { + return Or().On(first, second, op); + } + + public override Join NewQuery() { + return new Join(); + } +} diff --git a/QueryBuilder/Properties/AssemblyInfo.cs b/QueryBuilder/Properties/AssemblyInfo.cs index b8e480cf..148172f3 100644 --- a/QueryBuilder/Properties/AssemblyInfo.cs +++ b/QueryBuilder/Properties/AssemblyInfo.cs @@ -1,3 +1,3 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("QueryBuilder.Tests")] +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("QueryBuilder.Tests")] diff --git a/QueryBuilder/Query.Aggregate.cs b/QueryBuilder/Query.Aggregate.cs index d4fc5057..6785b9a6 100644 --- a/QueryBuilder/Query.Aggregate.cs +++ b/QueryBuilder/Query.Aggregate.cs @@ -1,59 +1,48 @@ -using System.Collections.Generic; -using System.Linq; - -namespace SqlKata -{ - public partial class Query - { - public Query AsAggregate(string type, string[] columns = null) - { - - Method = "aggregate"; - - this.ClearComponent("aggregate") - .AddComponent("aggregate", new AggregateClause - { - Type = type, - Columns = columns?.ToList() ?? new List(), - }); - - return this; - } - - public Query AsCount(string[] columns = null) - { - var cols = columns?.ToList() ?? new List { }; - - if (!cols.Any()) - { - cols.Add("*"); - } - - return AsAggregate("count", cols.ToArray()); - } - - public Query AsAvg(string column) - { - return AsAggregate("avg", new string[] { column }); - } - public Query AsAverage(string column) - { - return AsAvg(column); - } - - public Query AsSum(string column) - { - return AsAggregate("sum", new[] { column }); - } - - public Query AsMax(string column) - { - return AsAggregate("max", new[] { column }); - } - - public Query AsMin(string column) - { - return AsAggregate("min", new[] { column }); - } - } -} +namespace SqlKata; + +using System.Collections.Generic; +using System.Linq; + +public partial class Query { + public Query AsAggregate(string type, string[] columns = null) { + + Method = "aggregate"; + + this.ClearComponent("aggregate") + .AddComponent("aggregate", new AggregateClause { + Type = type, + Columns = columns?.ToList() ?? new List(), + }); + + return this; + } + + public Query AsCount(string[] columns = null) { + var cols = columns?.ToList() ?? new List { }; + + if (!cols.Any()) { + cols.Add("*"); + } + + return AsAggregate("count", cols.ToArray()); + } + + public Query AsAvg(string column) { + return AsAggregate("avg", new string[] { column }); + } + public Query AsAverage(string column) { + return AsAvg(column); + } + + public Query AsSum(string column) { + return AsAggregate("sum", new[] { column }); + } + + public Query AsMax(string column) { + return AsAggregate("max", new[] { column }); + } + + public Query AsMin(string column) { + return AsAggregate("min", new[] { column }); + } +} diff --git a/QueryBuilder/Query.Combine.cs b/QueryBuilder/Query.Combine.cs index b7238264..d5af0100 100644 --- a/QueryBuilder/Query.Combine.cs +++ b/QueryBuilder/Query.Combine.cs @@ -1,106 +1,86 @@ -using System; -using System.Linq; - -namespace SqlKata -{ - public partial class Query - { - - public Query Combine(string operation, bool all, Query query) - { - if (this.Method != "select" || query.Method != "select") - { - throw new InvalidOperationException("Only select queries can be combined."); - } - - return AddComponent("combine", new Combine - { - Query = query, - Operation = operation, - All = all, - }); - } - - public Query CombineRaw(string sql, params object[] bindings) - { - if (this.Method != "select") - { - throw new InvalidOperationException("Only select queries can be combined."); - } - - return AddComponent("combine", new RawCombine - { - Expression = sql, - Bindings = bindings, - }); - } - - public Query Union(Query query, bool all = false) - { - return Combine("union", all, query); - } - - public Query UnionAll(Query query) - { - return Union(query, true); - } - - public Query Union(Func callback, bool all = false) - { - var query = callback.Invoke(new Query()); - return Union(query, all); - } - - public Query UnionAll(Func callback) - { - return Union(callback, true); - } - - public Query UnionRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); - - public Query Except(Query query, bool all = false) - { - return Combine("except", all, query); - } - - public Query ExceptAll(Query query) - { - return Except(query, true); - } - - public Query Except(Func callback, bool all = false) - { - var query = callback.Invoke(new Query()); - return Except(query, all); - } - - public Query ExceptAll(Func callback) - { - return Except(callback, true); - } - public Query ExceptRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); - - public Query Intersect(Query query, bool all = false) - { - return Combine("intersect", all, query); - } - - public Query IntersectAll(Query query) - { - return Intersect(query, true); - } - - public Query Intersect(Func callback, bool all = false) - { - var query = callback.Invoke(new Query()); - return Intersect(query, all); - } - - public Query IntersectAll(Func callback) - { - return Intersect(callback, true); - } - public Query IntersectRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); - - } -} +namespace SqlKata; + +using System; +using System.Linq; + +public partial class Query { + + public Query Combine(string operation, bool all, Query query) { + if (this.Method != "select" || query.Method != "select") { + throw new InvalidOperationException("Only select queries can be combined."); + } + + return AddComponent("combine", new Combine { + Query = query, + Operation = operation, + All = all, + }); + } + + public Query CombineRaw(string sql, params object[] bindings) { + if (this.Method != "select") { + throw new InvalidOperationException("Only select queries can be combined."); + } + + return AddComponent("combine", new RawCombine { + Expression = sql, + Bindings = bindings, + }); + } + + public Query Union(Query query, bool all = false) { + return Combine("union", all, query); + } + + public Query UnionAll(Query query) { + return Union(query, true); + } + + public Query Union(Func callback, bool all = false) { + var query = callback.Invoke(new Query()); + return Union(query, all); + } + + public Query UnionAll(Func callback) { + return Union(callback, true); + } + + public Query UnionRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); + + public Query Except(Query query, bool all = false) { + return Combine("except", all, query); + } + + public Query ExceptAll(Query query) { + return Except(query, true); + } + + public Query Except(Func callback, bool all = false) { + var query = callback.Invoke(new Query()); + return Except(query, all); + } + + public Query ExceptAll(Func callback) { + return Except(callback, true); + } + public Query ExceptRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); + + public Query Intersect(Query query, bool all = false) { + return Combine("intersect", all, query); + } + + public Query IntersectAll(Query query) { + return Intersect(query, true); + } + + public Query Intersect(Func callback, bool all = false) { + var query = callback.Invoke(new Query()); + return Intersect(query, all); + } + + public Query IntersectAll(Func callback) { + return Intersect(callback, true); + } + public Query IntersectRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); + +} diff --git a/QueryBuilder/Query.Delete.cs b/QueryBuilder/Query.Delete.cs index bd00cad9..1b1b3b70 100644 --- a/QueryBuilder/Query.Delete.cs +++ b/QueryBuilder/Query.Delete.cs @@ -1,12 +1,8 @@ -namespace SqlKata -{ - public partial class Query - { - public Query AsDelete() - { - Method = "delete"; - return this; - } - - } -} +namespace SqlKata; +public partial class Query { + public Query AsDelete() { + Method = "delete"; + return this; + } + +} diff --git a/QueryBuilder/Query.Having.cs b/QueryBuilder/Query.Having.cs index ee367941..c4c2ede2 100644 --- a/QueryBuilder/Query.Having.cs +++ b/QueryBuilder/Query.Having.cs @@ -1,656 +1,535 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace SqlKata -{ - public partial class Query - { - public Query Having(string column, string op, object value) - { - - // If the value is "null", we will just assume the developer wants to add a - // Having null clause to the query. So, we will allow a short-cut here to - // that method for convenience so the developer doesn't have to check. - if (value == null) - { - return Not(op != "=").HavingNull(column); - } - - return AddComponent("having", new BasicCondition - { - Column = column, - Operator = op, - Value = value, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Query HavingNot(string column, string op, object value) - { - return Not().Having(column, op, value); - } - - public Query OrHaving(string column, string op, object value) - { - return Or().Having(column, op, value); - } - - public Query OrHavingNot(string column, string op, object value) - { - return this.Or().Not().Having(column, op, value); - } - - public Query Having(string column, object value) - { - return Having(column, "=", value); - } - public Query HavingNot(string column, object value) - { - return HavingNot(column, "=", value); - } - public Query OrHaving(string column, object value) - { - return OrHaving(column, "=", value); - } - public Query OrHavingNot(string column, object value) - { - return OrHavingNot(column, "=", value); - } - - /// - /// Perform a Having constraint - /// - /// - /// - public Query Having(object constraints) - { - var dictionary = new Dictionary(); - - foreach (var item in constraints.GetType().GetRuntimeProperties()) - { - dictionary.Add(item.Name, item.GetValue(constraints)); - } - - return Having(dictionary); - } - - public Query Having(IEnumerable> values) - { - var query = this; - var orFlag = GetOr(); - var notFlag = GetNot(); - - foreach (var tuple in values) - { - if (orFlag) - { - query = query.Or(); - } - else - { - query.And(); - } - - query = this.Not(notFlag).Having(tuple.Key, tuple.Value); - } - - return query; - } - - public Query HavingRaw(string sql, params object[] bindings) - { - return AddComponent("having", new RawCondition - { - Expression = sql, - Bindings = bindings, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Query OrHavingRaw(string sql, params object[] bindings) - { - return Or().HavingRaw(sql, bindings); - } - - /// - /// Apply a nested Having clause - /// - /// - /// - public Query Having(Func callback) - { - var query = callback.Invoke(NewChild()); - - return AddComponent("having", new NestedCondition - { - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); - } - - public Query HavingNot(Func callback) - { - return Not().Having(callback); - } - - public Query OrHaving(Func callback) - { - return Or().Having(callback); - } - - public Query OrHavingNot(Func callback) - { - return Not().Or().Having(callback); - } - - public Query HavingColumns(string first, string op, string second) - { - return AddComponent("having", new TwoColumnsCondition - { - First = first, - Second = second, - Operator = op, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Query OrHavingColumns(string first, string op, string second) - { - return Or().HavingColumns(first, op, second); - } - - public Query HavingNull(string column) - { - return AddComponent("having", new NullCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Query HavingNotNull(string column) - { - return Not().HavingNull(column); - } - - public Query OrHavingNull(string column) - { - return this.Or().HavingNull(column); - } - - public Query OrHavingNotNull(string column) - { - return Or().Not().HavingNull(column); - } - - public Query HavingTrue(string column) - { - return AddComponent("having", new BooleanCondition - { - Column = column, - Value = true, - }); - } - - public Query OrHavingTrue(string column) - { - return Or().HavingTrue(column); - } - - public Query HavingFalse(string column) - { - return AddComponent("having", new BooleanCondition - { - Column = column, - Value = false, - }); - } - - public Query OrHavingFalse(string column) - { - return Or().HavingFalse(column); - } - - public Query HavingLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return AddComponent("having", new BasicStringCondition - { - Operator = "like", - Column = column, - Value = value, - CaseSensitive = caseSensitive, - EscapeCharacter = escapeCharacter, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Query HavingNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Not().HavingLike(column, value, caseSensitive, escapeCharacter); - } - - public Query OrHavingLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().HavingLike(column, value, caseSensitive, escapeCharacter); - } - - public Query OrHavingNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().Not().HavingLike(column, value, caseSensitive, escapeCharacter); - } - public Query HavingStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return AddComponent("having", new BasicStringCondition - { - Operator = "starts", - Column = column, - Value = value, - CaseSensitive = caseSensitive, - EscapeCharacter = escapeCharacter, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Query HavingNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Not().HavingStarts(column, value, caseSensitive, escapeCharacter); - } - - public Query OrHavingStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().HavingStarts(column, value, caseSensitive, escapeCharacter); - } - - public Query OrHavingNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().Not().HavingStarts(column, value, caseSensitive, escapeCharacter); - } - - public Query HavingEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return AddComponent("having", new BasicStringCondition - { - Operator = "ends", - Column = column, - Value = value, - CaseSensitive = caseSensitive, - EscapeCharacter = escapeCharacter, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Query HavingNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Not().HavingEnds(column, value, caseSensitive, escapeCharacter); - } - - public Query OrHavingEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().HavingEnds(column, value, caseSensitive, escapeCharacter); - } - - public Query OrHavingNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().Not().HavingEnds(column, value, caseSensitive, escapeCharacter); - } - - public Query HavingContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return AddComponent("having", new BasicStringCondition - { - Operator = "contains", - Column = column, - Value = value, - CaseSensitive = caseSensitive, - EscapeCharacter = escapeCharacter, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - - public Query HavingNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Not().HavingContains(column, value, caseSensitive, escapeCharacter); - } - - public Query OrHavingContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().HavingContains(column, value, caseSensitive, escapeCharacter); - } - - public Query OrHavingNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) - { - return Or().Not().HavingContains(column, value, caseSensitive, escapeCharacter); - } - - public Query HavingBetween(string column, T lower, T higher) - { - return AddComponent("having", new BetweenCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Lower = lower, - Higher = higher - }); - } - - public Query OrHavingBetween(string column, T lower, T higher) - { - return Or().HavingBetween(column, lower, higher); - } - public Query HavingNotBetween(string column, T lower, T higher) - { - return Not().HavingBetween(column, lower, higher); - } - public Query OrHavingNotBetween(string column, T lower, T higher) - { - return Or().Not().HavingBetween(column, lower, higher); - } - - public Query HavingIn(string column, IEnumerable values) - { - // If the developer has passed a string they most likely want a List - // since string is considered as List - if (values is string) - { - string val = values as string; - - return AddComponent("having", new InCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Values = new List { val } - }); - } - - return AddComponent("having", new InCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Values = values.Distinct().ToList() - }); - - - } - - public Query OrHavingIn(string column, IEnumerable values) - { - return Or().HavingIn(column, values); - } - - public Query HavingNotIn(string column, IEnumerable values) - { - return Not().HavingIn(column, values); - } - - public Query OrHavingNotIn(string column, IEnumerable values) - { - return Or().Not().HavingIn(column, values); - } - - - public Query HavingIn(string column, Query query) - { - return AddComponent("having", new InQueryCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Query = query, - }); - } - public Query HavingIn(string column, Func callback) - { - var query = callback.Invoke(new Query()); - - return HavingIn(column, query); - } - - public Query OrHavingIn(string column, Query query) - { - return Or().HavingIn(column, query); - } - - public Query OrHavingIn(string column, Func callback) - { - return Or().HavingIn(column, callback); - } - public Query HavingNotIn(string column, Query query) - { - return Not().HavingIn(column, query); - } - - public Query HavingNotIn(string column, Func callback) - { - return Not().HavingIn(column, callback); - } - - public Query OrHavingNotIn(string column, Query query) - { - return Or().Not().HavingIn(column, query); - } - - public Query OrHavingNotIn(string column, Func callback) - { - return Or().Not().HavingIn(column, callback); - } - - - /// - /// Perform a sub query Having clause - /// - /// - /// - /// - /// - public Query Having(string column, string op, Func callback) - { - var query = callback.Invoke(NewChild()); - - return Having(column, op, query); - } - - public Query Having(string column, string op, Query query) - { - return AddComponent("having", new QueryCondition - { - Column = column, - Operator = op, - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); - } - - public Query OrHaving(string column, string op, Query query) - { - return Or().Having(column, op, query); - } - public Query OrHaving(string column, string op, Func callback) - { - return Or().Having(column, op, callback); - } - - public Query HavingExists(Query query) - { - if (!query.HasComponent("from")) - { - throw new ArgumentException($"{nameof(FromClause)} cannot be empty if used inside a {nameof(HavingExists)} condition"); - } - - // simplify the query as much as possible - query = query.Clone().ClearComponent("select") - .SelectRaw("1") - .Limit(1); - - return AddComponent("having", new ExistsCondition - { - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); - } - public Query HavingExists(Func callback) - { - var childQuery = new Query().SetParent(this); - return HavingExists(callback.Invoke(childQuery)); - } - - public Query HavingNotExists(Query query) - { - return Not().HavingExists(query); - } - - public Query HavingNotExists(Func callback) - { - return Not().HavingExists(callback); - } - - public Query OrHavingExists(Query query) - { - return Or().HavingExists(query); - } - public Query OrHavingExists(Func callback) - { - return Or().HavingExists(callback); - } - public Query OrHavingNotExists(Query query) - { - return Or().Not().HavingExists(query); - } - public Query OrHavingNotExists(Func callback) - { - return Or().Not().HavingExists(callback); - } - - #region date - public Query HavingDatePart(string part, string column, string op, object value) - { - return AddComponent("having", new BasicDateCondition - { - Operator = op, - Column = column, - Value = value, - Part = part, - IsOr = GetOr(), - IsNot = GetNot(), - }); - } - public Query HavingNotDatePart(string part, string column, string op, object value) - { - return Not().HavingDatePart(part, column, op, value); - } - - public Query OrHavingDatePart(string part, string column, string op, object value) - { - return Or().HavingDatePart(part, column, op, value); - } - - public Query OrHavingNotDatePart(string part, string column, string op, object value) - { - return Or().Not().HavingDatePart(part, column, op, value); - } - - public Query HavingDate(string column, string op, object value) - { - return HavingDatePart("date", column, op, value); - } - public Query HavingNotDate(string column, string op, object value) - { - return Not().HavingDate(column, op, value); - } - public Query OrHavingDate(string column, string op, object value) - { - return Or().HavingDate(column, op, value); - } - public Query OrHavingNotDate(string column, string op, object value) - { - return Or().Not().HavingDate(column, op, value); - } - - public Query HavingTime(string column, string op, object value) - { - return HavingDatePart("time", column, op, value); - } - public Query HavingNotTime(string column, string op, object value) - { - return Not().HavingTime(column, op, value); - } - public Query OrHavingTime(string column, string op, object value) - { - return Or().HavingTime(column, op, value); - } - public Query OrHavingNotTime(string column, string op, object value) - { - return Or().Not().HavingTime(column, op, value); - } - - public Query HavingDatePart(string part, string column, object value) - { - return HavingDatePart(part, column, "=", value); - } - public Query HavingNotDatePart(string part, string column, object value) - { - return HavingNotDatePart(part, column, "=", value); - } - - public Query OrHavingDatePart(string part, string column, object value) - { - return OrHavingDatePart(part, column, "=", value); - } - - public Query OrHavingNotDatePart(string part, string column, object value) - { - return OrHavingNotDatePart(part, column, "=", value); - } - - public Query HavingDate(string column, object value) - { - return HavingDate(column, "=", value); - } - public Query HavingNotDate(string column, object value) - { - return HavingNotDate(column, "=", value); - } - public Query OrHavingDate(string column, object value) - { - return OrHavingDate(column, "=", value); - } - public Query OrHavingNotDate(string column, object value) - { - return OrHavingNotDate(column, "=", value); - } - - public Query HavingTime(string column, object value) - { - return HavingTime(column, "=", value); - } - public Query HavingNotTime(string column, object value) - { - return HavingNotTime(column, "=", value); - } - public Query OrHavingTime(string column, object value) - { - return OrHavingTime(column, "=", value); - } - public Query OrHavingNotTime(string column, object value) - { - return OrHavingNotTime(column, "=", value); - } - - #endregion - } -} +namespace SqlKata; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +public partial class Query { + public Query Having(string column, string op, object value) { + + // If the value is "null", we will just assume the developer wants to add a + // Having null clause to the query. So, we will allow a short-cut here to + // that method for convenience so the developer doesn't have to check. + if (value == null) { + return Not(op != "=").HavingNull(column); + } + + return AddComponent("having", new BasicCondition { + Column = column, + Operator = op, + Value = value, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Query HavingNot(string column, string op, object value) { + return Not().Having(column, op, value); + } + + public Query OrHaving(string column, string op, object value) { + return Or().Having(column, op, value); + } + + public Query OrHavingNot(string column, string op, object value) { + return this.Or().Not().Having(column, op, value); + } + + public Query Having(string column, object value) { + return Having(column, "=", value); + } + public Query HavingNot(string column, object value) { + return HavingNot(column, "=", value); + } + public Query OrHaving(string column, object value) { + return OrHaving(column, "=", value); + } + public Query OrHavingNot(string column, object value) { + return OrHavingNot(column, "=", value); + } + + /// + /// Perform a Having constraint + /// + /// + /// + public Query Having(object constraints) { + var dictionary = new Dictionary(); + + foreach (var item in constraints.GetType().GetRuntimeProperties()) { + dictionary.Add(item.Name, item.GetValue(constraints)); + } + + return Having(dictionary); + } + + public Query Having(IEnumerable> values) { + var query = this; + var orFlag = GetOr(); + var notFlag = GetNot(); + + foreach (var tuple in values) { + if (orFlag) { + query = query.Or(); + } + else { + query.And(); + } + + query = this.Not(notFlag).Having(tuple.Key, tuple.Value); + } + + return query; + } + + public Query HavingRaw(string sql, params object[] bindings) { + return AddComponent("having", new RawCondition { + Expression = sql, + Bindings = bindings, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Query OrHavingRaw(string sql, params object[] bindings) { + return Or().HavingRaw(sql, bindings); + } + + /// + /// Apply a nested Having clause + /// + /// + /// + public Query Having(Func callback) { + var query = callback.Invoke(NewChild()); + + return AddComponent("having", new NestedCondition { + Query = query, + IsNot = GetNot(), + IsOr = GetOr(), + }); + } + + public Query HavingNot(Func callback) { + return Not().Having(callback); + } + + public Query OrHaving(Func callback) { + return Or().Having(callback); + } + + public Query OrHavingNot(Func callback) { + return Not().Or().Having(callback); + } + + public Query HavingColumns(string first, string op, string second) { + return AddComponent("having", new TwoColumnsCondition { + First = first, + Second = second, + Operator = op, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Query OrHavingColumns(string first, string op, string second) { + return Or().HavingColumns(first, op, second); + } + + public Query HavingNull(string column) { + return AddComponent("having", new NullCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Query HavingNotNull(string column) { + return Not().HavingNull(column); + } + + public Query OrHavingNull(string column) { + return this.Or().HavingNull(column); + } + + public Query OrHavingNotNull(string column) { + return Or().Not().HavingNull(column); + } + + public Query HavingTrue(string column) { + return AddComponent("having", new BooleanCondition { + Column = column, + Value = true, + }); + } + + public Query OrHavingTrue(string column) { + return Or().HavingTrue(column); + } + + public Query HavingFalse(string column) { + return AddComponent("having", new BooleanCondition { + Column = column, + Value = false, + }); + } + + public Query OrHavingFalse(string column) { + return Or().HavingFalse(column); + } + + public Query HavingLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return AddComponent("having", new BasicStringCondition { + Operator = "like", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Query HavingNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Not().HavingLike(column, value, caseSensitive, escapeCharacter); + } + + public Query OrHavingLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().HavingLike(column, value, caseSensitive, escapeCharacter); + } + + public Query OrHavingNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().Not().HavingLike(column, value, caseSensitive, escapeCharacter); + } + public Query HavingStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return AddComponent("having", new BasicStringCondition { + Operator = "starts", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Query HavingNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Not().HavingStarts(column, value, caseSensitive, escapeCharacter); + } + + public Query OrHavingStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().HavingStarts(column, value, caseSensitive, escapeCharacter); + } + + public Query OrHavingNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().Not().HavingStarts(column, value, caseSensitive, escapeCharacter); + } + + public Query HavingEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return AddComponent("having", new BasicStringCondition { + Operator = "ends", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Query HavingNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Not().HavingEnds(column, value, caseSensitive, escapeCharacter); + } + + public Query OrHavingEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().HavingEnds(column, value, caseSensitive, escapeCharacter); + } + + public Query OrHavingNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().Not().HavingEnds(column, value, caseSensitive, escapeCharacter); + } + + public Query HavingContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return AddComponent("having", new BasicStringCondition { + Operator = "contains", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + + public Query HavingNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Not().HavingContains(column, value, caseSensitive, escapeCharacter); + } + + public Query OrHavingContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().HavingContains(column, value, caseSensitive, escapeCharacter); + } + + public Query OrHavingNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { + return Or().Not().HavingContains(column, value, caseSensitive, escapeCharacter); + } + + public Query HavingBetween(string column, T lower, T higher) { + return AddComponent("having", new BetweenCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + Lower = lower, + Higher = higher + }); + } + + public Query OrHavingBetween(string column, T lower, T higher) { + return Or().HavingBetween(column, lower, higher); + } + public Query HavingNotBetween(string column, T lower, T higher) { + return Not().HavingBetween(column, lower, higher); + } + public Query OrHavingNotBetween(string column, T lower, T higher) { + return Or().Not().HavingBetween(column, lower, higher); + } + + public Query HavingIn(string column, IEnumerable values) { + // If the developer has passed a string they most likely want a List + // since string is considered as List + if (values is string) { + string val = values as string; + + return AddComponent("having", new InCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + Values = new List { val } + }); + } + + return AddComponent("having", new InCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + Values = values.Distinct().ToList() + }); + + + } + + public Query OrHavingIn(string column, IEnumerable values) { + return Or().HavingIn(column, values); + } + + public Query HavingNotIn(string column, IEnumerable values) { + return Not().HavingIn(column, values); + } + + public Query OrHavingNotIn(string column, IEnumerable values) { + return Or().Not().HavingIn(column, values); + } + + + public Query HavingIn(string column, Query query) { + return AddComponent("having", new InQueryCondition { + Column = column, + IsOr = GetOr(), + IsNot = GetNot(), + Query = query, + }); + } + public Query HavingIn(string column, Func callback) { + var query = callback.Invoke(new Query()); + + return HavingIn(column, query); + } + + public Query OrHavingIn(string column, Query query) { + return Or().HavingIn(column, query); + } + + public Query OrHavingIn(string column, Func callback) { + return Or().HavingIn(column, callback); + } + public Query HavingNotIn(string column, Query query) { + return Not().HavingIn(column, query); + } + + public Query HavingNotIn(string column, Func callback) { + return Not().HavingIn(column, callback); + } + + public Query OrHavingNotIn(string column, Query query) { + return Or().Not().HavingIn(column, query); + } + + public Query OrHavingNotIn(string column, Func callback) { + return Or().Not().HavingIn(column, callback); + } + + + /// + /// Perform a sub query Having clause + /// + /// + /// + /// + /// + public Query Having(string column, string op, Func callback) { + var query = callback.Invoke(NewChild()); + + return Having(column, op, query); + } + + public Query Having(string column, string op, Query query) { + return AddComponent("having", new QueryCondition { + Column = column, + Operator = op, + Query = query, + IsNot = GetNot(), + IsOr = GetOr(), + }); + } + + public Query OrHaving(string column, string op, Query query) { + return Or().Having(column, op, query); + } + public Query OrHaving(string column, string op, Func callback) { + return Or().Having(column, op, callback); + } + + public Query HavingExists(Query query) { + if (!query.HasComponent("from")) { + throw new ArgumentException($"{nameof(FromClause)} cannot be empty if used inside a {nameof(HavingExists)} condition"); + } + + // simplify the query as much as possible + query = query.Clone().ClearComponent("select") + .SelectRaw("1") + .Limit(1); + + return AddComponent("having", new ExistsCondition { + Query = query, + IsNot = GetNot(), + IsOr = GetOr(), + }); + } + public Query HavingExists(Func callback) { + var childQuery = new Query().SetParent(this); + return HavingExists(callback.Invoke(childQuery)); + } + + public Query HavingNotExists(Query query) { + return Not().HavingExists(query); + } + + public Query HavingNotExists(Func callback) { + return Not().HavingExists(callback); + } + + public Query OrHavingExists(Query query) { + return Or().HavingExists(query); + } + public Query OrHavingExists(Func callback) { + return Or().HavingExists(callback); + } + public Query OrHavingNotExists(Query query) { + return Or().Not().HavingExists(query); + } + public Query OrHavingNotExists(Func callback) { + return Or().Not().HavingExists(callback); + } + + #region date + public Query HavingDatePart(string part, string column, string op, object value) { + return AddComponent("having", new BasicDateCondition { + Operator = op, + Column = column, + Value = value, + Part = part, + IsOr = GetOr(), + IsNot = GetNot(), + }); + } + public Query HavingNotDatePart(string part, string column, string op, object value) { + return Not().HavingDatePart(part, column, op, value); + } + + public Query OrHavingDatePart(string part, string column, string op, object value) { + return Or().HavingDatePart(part, column, op, value); + } + + public Query OrHavingNotDatePart(string part, string column, string op, object value) { + return Or().Not().HavingDatePart(part, column, op, value); + } + + public Query HavingDate(string column, string op, object value) { + return HavingDatePart("date", column, op, value); + } + public Query HavingNotDate(string column, string op, object value) { + return Not().HavingDate(column, op, value); + } + public Query OrHavingDate(string column, string op, object value) { + return Or().HavingDate(column, op, value); + } + public Query OrHavingNotDate(string column, string op, object value) { + return Or().Not().HavingDate(column, op, value); + } + + public Query HavingTime(string column, string op, object value) { + return HavingDatePart("time", column, op, value); + } + public Query HavingNotTime(string column, string op, object value) { + return Not().HavingTime(column, op, value); + } + public Query OrHavingTime(string column, string op, object value) { + return Or().HavingTime(column, op, value); + } + public Query OrHavingNotTime(string column, string op, object value) { + return Or().Not().HavingTime(column, op, value); + } + + public Query HavingDatePart(string part, string column, object value) { + return HavingDatePart(part, column, "=", value); + } + public Query HavingNotDatePart(string part, string column, object value) { + return HavingNotDatePart(part, column, "=", value); + } + + public Query OrHavingDatePart(string part, string column, object value) { + return OrHavingDatePart(part, column, "=", value); + } + + public Query OrHavingNotDatePart(string part, string column, object value) { + return OrHavingNotDatePart(part, column, "=", value); + } + + public Query HavingDate(string column, object value) { + return HavingDate(column, "=", value); + } + public Query HavingNotDate(string column, object value) { + return HavingNotDate(column, "=", value); + } + public Query OrHavingDate(string column, object value) { + return OrHavingDate(column, "=", value); + } + public Query OrHavingNotDate(string column, object value) { + return OrHavingNotDate(column, "=", value); + } + + public Query HavingTime(string column, object value) { + return HavingTime(column, "=", value); + } + public Query HavingNotTime(string column, object value) { + return HavingNotTime(column, "=", value); + } + public Query OrHavingTime(string column, object value) { + return OrHavingTime(column, "=", value); + } + public Query OrHavingNotTime(string column, object value) { + return OrHavingNotTime(column, "=", value); + } + + #endregion +} diff --git a/QueryBuilder/Query.Insert.cs b/QueryBuilder/Query.Insert.cs index dbec60af..168091d4 100644 --- a/QueryBuilder/Query.Insert.cs +++ b/QueryBuilder/Query.Insert.cs @@ -1,119 +1,102 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace SqlKata -{ - public partial class Query - { - public Query AsInsert(object data, bool returnId = false) - { - var propertiesKeyValues = BuildKeyValuePairsFromObject(data); - - return AsInsert(propertiesKeyValues, returnId); - } - - public Query AsInsert(IEnumerable columns, IEnumerable values) - { - var columnsList = columns?.ToList(); - var valuesList = values?.ToList(); - - if ((columnsList?.Count ?? 0) == 0 || (valuesList?.Count ?? 0) == 0) - { - throw new InvalidOperationException($"{nameof(columns)} and {nameof(values)} cannot be null or empty"); - } - - if (columnsList.Count != valuesList.Count) - { - throw new InvalidOperationException($"{nameof(columns)} and {nameof(values)} cannot be null or empty"); - } - - Method = "insert"; - - ClearComponent("insert").AddComponent("insert", new InsertClause - { - Columns = columnsList, - Values = valuesList - }); - - return this; - } - - public Query AsInsert(IEnumerable> values, bool returnId = false) - { - if (values == null || values.Any() == false) - { - throw new InvalidOperationException($"{values} argument cannot be null or empty"); - } - - Method = "insert"; - - ClearComponent("insert").AddComponent("insert", new InsertClause - { - Columns = values.Select(x => x.Key).ToList(), - Values = values.Select(x => x.Value).ToList(), - ReturnId = returnId, - }); - - return this; - } - - /// - /// Produces insert multi records - /// - /// - /// - /// - public Query AsInsert(IEnumerable columns, IEnumerable> rowsValues) - { - var columnsList = columns?.ToList(); - var valuesCollectionList = rowsValues?.ToList(); - - if ((columnsList?.Count ?? 0) == 0 || (valuesCollectionList?.Count ?? 0) == 0) - { - throw new InvalidOperationException($"{nameof(columns)} and {nameof(rowsValues)} cannot be null or empty"); - } - - Method = "insert"; - - ClearComponent("insert"); - - foreach (var values in valuesCollectionList) - { - var valuesList = values.ToList(); - if (columnsList.Count != valuesList.Count) - { - throw new InvalidOperationException($"{nameof(columns)} count should be equal to each {nameof(rowsValues)} entry count"); - } - - AddComponent("insert", new InsertClause - { - Columns = columnsList, - Values = valuesList - }); - } - - return this; - } - - /// - /// Produces insert from subquery - /// - /// - /// - /// - public Query AsInsert(IEnumerable columns, Query query) - { - Method = "insert"; - - ClearComponent("insert").AddComponent("insert", new InsertQueryClause - { - Columns = columns.ToList(), - Query = query.Clone(), - }); - - return this; - } - } -} +namespace SqlKata; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +public partial class Query { + public Query AsInsert(object data, bool returnId = false) { + var propertiesKeyValues = BuildKeyValuePairsFromObject(data); + + return AsInsert(propertiesKeyValues, returnId); + } + + public Query AsInsert(IEnumerable columns, IEnumerable values) { + var columnsList = columns?.ToList(); + var valuesList = values?.ToList(); + + if ((columnsList?.Count ?? 0) == 0 || (valuesList?.Count ?? 0) == 0) { + throw new InvalidOperationException($"{nameof(columns)} and {nameof(values)} cannot be null or empty"); + } + + if (columnsList.Count != valuesList.Count) { + throw new InvalidOperationException($"{nameof(columns)} and {nameof(values)} cannot be null or empty"); + } + + Method = "insert"; + + ClearComponent("insert").AddComponent("insert", new InsertClause { + Columns = columnsList, + Values = valuesList + }); + + return this; + } + + public Query AsInsert(IEnumerable> values, bool returnId = false) { + if (values == null || values.Any() == false) { + throw new InvalidOperationException($"{values} argument cannot be null or empty"); + } + + Method = "insert"; + + ClearComponent("insert").AddComponent("insert", new InsertClause { + Columns = values.Select(x => x.Key).ToList(), + Values = values.Select(x => x.Value).ToList(), + ReturnId = returnId, + }); + + return this; + } + + /// + /// Produces insert multi records + /// + /// + /// + /// + public Query AsInsert(IEnumerable columns, IEnumerable> rowsValues) { + var columnsList = columns?.ToList(); + var valuesCollectionList = rowsValues?.ToList(); + + if ((columnsList?.Count ?? 0) == 0 || (valuesCollectionList?.Count ?? 0) == 0) { + throw new InvalidOperationException($"{nameof(columns)} and {nameof(rowsValues)} cannot be null or empty"); + } + + Method = "insert"; + + ClearComponent("insert"); + + foreach (var values in valuesCollectionList) { + var valuesList = values.ToList(); + if (columnsList.Count != valuesList.Count) { + throw new InvalidOperationException($"{nameof(columns)} count should be equal to each {nameof(rowsValues)} entry count"); + } + + AddComponent("insert", new InsertClause { + Columns = columnsList, + Values = valuesList + }); + } + + return this; + } + + /// + /// Produces insert from subquery + /// + /// + /// + /// + public Query AsInsert(IEnumerable columns, Query query) { + Method = "insert"; + + ClearComponent("insert").AddComponent("insert", new InsertQueryClause { + Columns = columns.ToList(), + Query = query.Clone(), + }); + + return this; + } +} diff --git a/QueryBuilder/Query.Join.cs b/QueryBuilder/Query.Join.cs index 3fe3bbb1..b33a516f 100644 --- a/QueryBuilder/Query.Join.cs +++ b/QueryBuilder/Query.Join.cs @@ -1,75 +1,61 @@ -using System; - -namespace SqlKata -{ - public partial class Query - { - - private Query Join(Func callback) - { - var join = callback.Invoke(new Join().AsInner()); - - return AddComponent("join", new BaseJoin - { - Join = join - }); - } - - public Query Join( - string table, - string first, - string second, - string op = "=", - string type = "inner join" - ) - { - return Join(j => j.JoinWith(table).WhereColumns(first, op, second).AsType(type)); - } - - public Query Join(string table, Func callback, string type = "inner join") - { - return Join(j => j.JoinWith(table).Where(callback).AsType(type)); - } - - public Query Join(Query query, Func onCallback, string type = "inner join") - { - return Join(j => j.JoinWith(query).Where(onCallback).AsType(type)); - } - - public Query LeftJoin(string table, string first, string second, string op = "=") - { - return Join(table, first, second, op, "left join"); - } - - public Query LeftJoin(string table, Func callback) - { - return Join(table, callback, "left join"); - } - - public Query LeftJoin(Query query, Func onCallback) - { - return Join(query, onCallback, "left join"); - } - - public Query RightJoin(string table, string first, string second, string op = "=") - { - return Join(table, first, second, op, "right join"); - } - - public Query RightJoin(string table, Func callback) - { - return Join(table, callback, "right join"); - } - - public Query RightJoin(Query query, Func onCallback) - { - return Join(query, onCallback, "right join"); - } - - public Query CrossJoin(string table) - { - return Join(j => j.JoinWith(table).AsCross()); - } - - } -} +namespace SqlKata; + +using System; + +public partial class Query { + + private Query Join(Func callback) { + var join = callback.Invoke(new Join().AsInner()); + + return AddComponent("join", new BaseJoin { + Join = join + }); + } + + public Query Join( + string table, + string first, + string second, + string op = "=", + string type = "inner join" + ) { + return Join(j => j.JoinWith(table).WhereColumns(first, op, second).AsType(type)); + } + + public Query Join(string table, Func callback, string type = "inner join") { + return Join(j => j.JoinWith(table).Where(callback).AsType(type)); + } + + public Query Join(Query query, Func onCallback, string type = "inner join") { + return Join(j => j.JoinWith(query).Where(onCallback).AsType(type)); + } + + public Query LeftJoin(string table, string first, string second, string op = "=") { + return Join(table, first, second, op, "left join"); + } + + public Query LeftJoin(string table, Func callback) { + return Join(table, callback, "left join"); + } + + public Query LeftJoin(Query query, Func onCallback) { + return Join(query, onCallback, "left join"); + } + + public Query RightJoin(string table, string first, string second, string op = "=") { + return Join(table, first, second, op, "right join"); + } + + public Query RightJoin(string table, Func callback) { + return Join(table, callback, "right join"); + } + + public Query RightJoin(Query query, Func onCallback) { + return Join(query, onCallback, "right join"); + } + + public Query CrossJoin(string table) { + return Join(j => j.JoinWith(table).AsCross()); + } + +} diff --git a/QueryBuilder/Query.Select.cs b/QueryBuilder/Query.Select.cs index 9502c024..508a16f7 100644 --- a/QueryBuilder/Query.Select.cs +++ b/QueryBuilder/Query.Select.cs @@ -1,121 +1,101 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace SqlKata -{ - public partial class Query - { - - public Query Select(params string[] columns) - { - return Select(columns.AsEnumerable()); - } - - public Query Select(IEnumerable columns) - { - Method = "select"; - - columns = columns - .Select(x => Helper.ExpandExpression(x)) - .SelectMany(x => x) - .ToArray(); - - - foreach (var column in columns) - { - AddComponent("select", new Column - { - Name = column - }); - } - - return this; - } - - /// - /// Add a new "raw" select expression to the query. - /// - /// - public Query SelectRaw(string sql, params object[] bindings) - { - Method = "select"; - - AddComponent("select", new RawColumn - { - Expression = sql, - Bindings = bindings, - }); - - return this; - } - - public Query Select(Query query, string alias) - { - Method = "select"; - - query = query.Clone(); - - AddComponent("select", new QueryColumn - { - Query = query.As(alias), - }); - - return this; - } - - public Query Select(Func callback, string alias) - { - return Select(callback.Invoke(NewChild()), alias); - } - - public Query SelectAggregate(string aggregate, string column, Query filter = null) - { - Method = "select"; - - AddComponent("select", new AggregatedColumn - { - Column = new Column { Name = column }, - Aggregate = aggregate, - Filter = filter, - }); - - return this; - } - - public Query SelectAggregate(string aggregate, string column, Func filter) - { - if (filter == null) - { - return SelectAggregate(aggregate, column); - } - - return SelectAggregate(aggregate, column, filter.Invoke(NewChild())); - } - - public Query SelectSum(string column, Func filter = null) - { - return SelectAggregate("sum", column, filter); - } - - public Query SelectCount(string column, Func filter = null) - { - return SelectAggregate("count", column, filter); - } - - public Query SelectAvg(string column, Func filter = null) - { - return SelectAggregate("avg", column, filter); - } - - public Query SelectMin(string column, Func filter = null) - { - return SelectAggregate("min", column, filter); - } - - public Query SelectMax(string column, Func filter = null) - { - return SelectAggregate("max", column, filter); - } - } -} +namespace SqlKata; + +using System; +using System.Collections.Generic; +using System.Linq; + +public partial class Query { + + public Query Select(params string[] columns) { + return Select(columns.AsEnumerable()); + } + + public Query Select(IEnumerable columns) { + Method = "select"; + + columns = columns + .Select(x => Helper.ExpandExpression(x)) + .SelectMany(x => x) + .ToArray(); + + + foreach (var column in columns) { + AddComponent("select", new Column { + Name = column + }); + } + + return this; + } + + /// + /// Add a new "raw" select expression to the query. + /// + /// + public Query SelectRaw(string sql, params object[] bindings) { + Method = "select"; + + AddComponent("select", new RawColumn { + Expression = sql, + Bindings = bindings, + }); + + return this; + } + + public Query Select(Query query, string alias) { + Method = "select"; + + query = query.Clone(); + + AddComponent("select", new QueryColumn { + Query = query.As(alias), + }); + + return this; + } + + public Query Select(Func callback, string alias) { + return Select(callback.Invoke(NewChild()), alias); + } + + public Query SelectAggregate(string aggregate, string column, Query filter = null) { + Method = "select"; + + AddComponent("select", new AggregatedColumn { + Column = new Column { Name = column }, + Aggregate = aggregate, + Filter = filter, + }); + + return this; + } + + public Query SelectAggregate(string aggregate, string column, Func filter) { + if (filter == null) { + return SelectAggregate(aggregate, column); + } + + return SelectAggregate(aggregate, column, filter.Invoke(NewChild())); + } + + public Query SelectSum(string column, Func filter = null) { + return SelectAggregate("sum", column, filter); + } + + public Query SelectCount(string column, Func filter = null) { + return SelectAggregate("count", column, filter); + } + + public Query SelectAvg(string column, Func filter = null) { + return SelectAggregate("avg", column, filter); + } + + public Query SelectMin(string column, Func filter = null) { + return SelectAggregate("min", column, filter); + } + + public Query SelectMax(string column, Func filter = null) { + return SelectAggregate("max", column, filter); + } +} diff --git a/QueryBuilder/Query.Update.cs b/QueryBuilder/Query.Update.cs index d88aeb00..5c8580ff 100644 --- a/QueryBuilder/Query.Update.cs +++ b/QueryBuilder/Query.Update.cs @@ -1,75 +1,62 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace SqlKata -{ - public partial class Query - { - public Query AsUpdate(object data) - { - var dictionary = BuildKeyValuePairsFromObject(data, considerKeys: true); - - return AsUpdate(dictionary); - } - - public Query AsUpdate(IEnumerable columns, IEnumerable values) - { - if ((columns?.Any() ?? false) == false || (values?.Any() ?? false) == false) - { - throw new InvalidOperationException($"{columns} and {values} cannot be null or empty"); - } - - if (columns.Count() != values.Count()) - { - throw new InvalidOperationException($"{columns} count should be equal to {values} count"); - } - - Method = "update"; - - ClearComponent("update").AddComponent("update", new InsertClause - { - Columns = columns.ToList(), - Values = values.ToList() - }); - - return this; - } - - public Query AsUpdate(IEnumerable> values) - { - if (values == null || values.Any() == false) - { - throw new InvalidOperationException($"{values} cannot be null or empty"); - } - - Method = "update"; - - ClearComponent("update").AddComponent("update", new InsertClause - { - Columns = values.Select(x => x.Key).ToList(), - Values = values.Select(x => x.Value).ToList(), - }); - - return this; - } - - public Query AsIncrement(string column, int value = 1) - { - Method = "update"; - AddOrReplaceComponent("update", new IncrementClause - { - Column = column, - Value = value - }); - - return this; - } - - public Query AsDecrement(string column, int value = 1) - { - return AsIncrement(column, -value); - } - } -} +namespace SqlKata; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +public partial class Query { + public Query AsUpdate(object data) { + var dictionary = BuildKeyValuePairsFromObject(data, considerKeys: true); + + return AsUpdate(dictionary); + } + + public Query AsUpdate(IEnumerable columns, IEnumerable values) { + if ((columns?.Any() ?? false) == false || (values?.Any() ?? false) == false) { + throw new InvalidOperationException($"{columns} and {values} cannot be null or empty"); + } + + if (columns.Count() != values.Count()) { + throw new InvalidOperationException($"{columns} count should be equal to {values} count"); + } + + Method = "update"; + + ClearComponent("update").AddComponent("update", new InsertClause { + Columns = columns.ToList(), + Values = values.ToList() + }); + + return this; + } + + public Query AsUpdate(IEnumerable> values) { + if (values == null || values.Any() == false) { + throw new InvalidOperationException($"{values} cannot be null or empty"); + } + + Method = "update"; + + ClearComponent("update").AddComponent("update", new InsertClause { + Columns = values.Select(x => x.Key).ToList(), + Values = values.Select(x => x.Value).ToList(), + }); + + return this; + } + + public Query AsIncrement(string column, int value = 1) { + Method = "update"; + AddOrReplaceComponent("update", new IncrementClause { + Column = column, + Value = value + }); + + return this; + } + + public Query AsDecrement(string column, int value = 1) { + return AsIncrement(column, -value); + } +} diff --git a/QueryBuilder/Query.cs b/QueryBuilder/Query.cs index 8435eca6..a7012d10 100755 --- a/QueryBuilder/Query.cs +++ b/QueryBuilder/Query.cs @@ -1,437 +1,373 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace SqlKata -{ - public partial class Query : BaseQuery - { - private string comment; - - public bool IsDistinct { get; set; } = false; - public string QueryAlias { get; set; } - public string Method { get; set; } = "select"; - public List Includes = new List(); - public Dictionary Variables = new Dictionary(); - - public Query() : base() - { - } - - public Query(string table, string comment = null) : base() - { - From(table); - Comment(comment); - } - - public string GetComment() => comment ?? ""; - - public bool HasOffset(string engineCode = null) => GetOffset(engineCode) > 0; - - public bool HasLimit(string engineCode = null) => GetLimit(engineCode) > 0; - - internal long GetOffset(string engineCode = null) - { - engineCode = engineCode ?? EngineScope; - var offset = this.GetOneComponent("offset", engineCode); - - return offset?.Offset ?? 0; - } - - internal int GetLimit(string engineCode = null) - { - engineCode = engineCode ?? EngineScope; - var limit = this.GetOneComponent("limit", engineCode); - - return limit?.Limit ?? 0; - } - - public override Query Clone() - { - var clone = base.Clone(); - clone.Parent = Parent; - clone.QueryAlias = QueryAlias; - clone.IsDistinct = IsDistinct; - clone.Method = Method; - clone.Includes = Includes; - clone.Variables = Variables; - return clone; - } - - public Query As(string alias) - { - QueryAlias = alias; - return this; - } - - /// - /// Sets a comment for the query. - /// - /// The comment. - /// - public Query Comment(string comment) - { - this.comment = comment; - return this; - } - - public Query For(string engine, Func fn) - { - EngineScope = engine; - - var result = fn.Invoke(this); - - // reset the engine - EngineScope = null; - - return result; - } - - public Query With(Query query) - { - // Clear query alias and add it to the containing clause - if (string.IsNullOrWhiteSpace(query.QueryAlias)) - { - throw new InvalidOperationException("No Alias found for the CTE query"); - } - - query = query.Clone(); - - var alias = query.QueryAlias.Trim(); - - // clear the query alias - query.QueryAlias = null; - - return AddComponent("cte", new QueryFromClause - { - Query = query, - Alias = alias, - }); - } - - public Query With(Func fn) - { - return With(fn.Invoke(new Query())); - } - - public Query With(string alias, Query query) - { - return With(query.As(alias)); - } - - public Query With(string alias, Func fn) - { - return With(alias, fn.Invoke(new Query())); - } - - /// - /// Constructs an ad-hoc table of the given data as a CTE. - /// - public Query With(string alias, IEnumerable columns, IEnumerable> valuesCollection) - { - var columnsList = columns?.ToList(); - var valuesCollectionList = valuesCollection?.ToList(); - - if ((columnsList?.Count ?? 0) == 0 || (valuesCollectionList?.Count ?? 0) == 0) - { - throw new InvalidOperationException("Columns and valuesCollection cannot be null or empty"); - } - - var clause = new AdHocTableFromClause() - { - Alias = alias, - Columns = columnsList, - Values = new List(), - }; - - foreach (var values in valuesCollectionList) - { - var valuesList = values.ToList(); - if (columnsList.Count != valuesList.Count) - { - throw new InvalidOperationException("Columns count should be equal to each Values count"); - } - - clause.Values.AddRange(valuesList); - } - - return AddComponent("cte", clause); - } - - public Query WithRaw(string alias, string sql, params object[] bindings) - { - return AddComponent("cte", new RawFromClause - { - Alias = alias, - Expression = sql, - Bindings = bindings, - }); - } - - public Query Limit(int value) - { - var newClause = new LimitClause - { - Limit = value - }; - - return AddOrReplaceComponent("limit", newClause); - } - - public Query Offset(long value) - { - var newClause = new OffsetClause - { - Offset = value - }; - - return AddOrReplaceComponent("offset", newClause); - } - - public Query Offset(int value) - { - return Offset((long)value); - } - - /// - /// Alias for Limit - /// - /// - /// - public Query Take(int limit) - { - return Limit(limit); - } - - /// - /// Alias for Offset - /// - /// - /// - public Query Skip(int offset) - { - return Offset(offset); - } - - /// - /// Set the limit and offset for a given page. - /// - /// - /// - /// - public Query ForPage(int page, int perPage = 15) - { - return Skip((page - 1) * perPage).Take(perPage); - } - - public Query Distinct() - { - IsDistinct = true; - return this; - } - - /// - /// Apply the callback's query changes if the given "condition" is true. - /// - /// - /// Invoked when the condition is true - /// Optional, invoked when the condition is false - /// - public Query When(bool condition, Func whenTrue, Func whenFalse = null) - { - if (condition && whenTrue != null) - { - return whenTrue.Invoke(this); - } - - if (!condition && whenFalse != null) - { - return whenFalse.Invoke(this); - } - - return this; - } - - /// - /// Apply the callback's query changes if the given "condition" is false. - /// - /// - /// - /// - public Query WhenNot(bool condition, Func callback) - { - if (!condition) - { - return callback.Invoke(this); - } - - return this; - } - - public Query OrderBy(params string[] columns) - { - foreach (var column in columns) - { - AddComponent("order", new OrderBy - { - Column = column, - Ascending = true - }); - } - - return this; - } - - public Query OrderByDesc(params string[] columns) - { - foreach (var column in columns) - { - AddComponent("order", new OrderBy - { - Column = column, - Ascending = false - }); - } - - return this; - } - - public Query OrderByRaw(string expression, params object[] bindings) - { - return AddComponent("order", new RawOrderBy - { - Expression = expression, - Bindings = Helper.Flatten(bindings).ToArray() - }); - } - - public Query OrderByRandom(string seed) - { - return AddComponent("order", new OrderByRandom { }); - } - - public Query GroupBy(params string[] columns) - { - foreach (var column in columns) - { - AddComponent("group", new Column - { - Name = column - }); - } - - return this; - } - - public Query GroupByRaw(string expression, params object[] bindings) - { - AddComponent("group", new RawColumn - { - Expression = expression, - Bindings = bindings, - }); - - return this; - } - - public override Query NewQuery() - { - return new Query(); - } - - public Query Include(string relationName, Query query, string foreignKey = null, string localKey = "Id", bool isMany = false) - { - - Includes.Add(new Include - { - Name = relationName, - LocalKey = localKey, - ForeignKey = foreignKey, - Query = query, - IsMany = isMany, - }); - - return this; - } - - public Query IncludeMany(string relationName, Query query, string foreignKey = null, string localKey = "Id") - { - return Include(relationName, query, foreignKey, localKey, isMany: true); - } - - private static readonly ConcurrentDictionary CacheDictionaryProperties = new ConcurrentDictionary(); - - /// - /// Define a variable to be used within the query - /// - /// - /// - /// - public Query Define(string variable, object value) - { - Variables.Add(variable, value); - - return this; - } - - public object FindVariable(string variable) - { - var found = Variables.ContainsKey(variable); - - if (found) - { - return Variables[variable]; - } - - if (Parent != null) - { - return (Parent as Query).FindVariable(variable); - } - - throw new Exception($"Variable '{variable}' not found"); - } - - /// - /// Gather a list of key-values representing the properties of the object and their values. - /// - /// The plain C# object - /// - /// When true it will search for properties with the [Key] attribute - /// and will add it automatically to the Where clause - /// - /// - private IEnumerable> BuildKeyValuePairsFromObject(object data, bool considerKeys = false) - { - var dictionary = new Dictionary(); - var props = CacheDictionaryProperties.GetOrAdd(data.GetType(), type => type.GetRuntimeProperties().ToArray()); - - foreach (var property in props) - { - if (property.GetCustomAttribute(typeof(IgnoreAttribute)) != null) - { - continue; - } - - var value = property.GetValue(data); - - var colAttr = property.GetCustomAttribute(typeof(ColumnAttribute)) as ColumnAttribute; - - var name = colAttr?.Name ?? property.Name; - - dictionary.Add(name, value); - - if (considerKeys && colAttr != null) - { - if ((colAttr as KeyAttribute) != null) - { - this.Where(name, value); - } - } - } - - return dictionary; - } - } -} +namespace SqlKata; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +public partial class Query : BaseQuery { + private string comment; + + public bool IsDistinct { get; set; } = false; + public string QueryAlias { get; set; } + public string Method { get; set; } = "select"; + public List Includes = new List(); + public Dictionary Variables = new Dictionary(); + + public Query() : base() { + } + + public Query(string table, string comment = null) : base() { + From(table); + Comment(comment); + } + + public string GetComment() => comment ?? ""; + + public bool HasOffset(string engineCode = null) => GetOffset(engineCode) > 0; + + public bool HasLimit(string engineCode = null) => GetLimit(engineCode) > 0; + + internal long GetOffset(string engineCode = null) { + engineCode = engineCode ?? EngineScope; + var offset = this.GetOneComponent("offset", engineCode); + + return offset?.Offset ?? 0; + } + + internal int GetLimit(string engineCode = null) { + engineCode = engineCode ?? EngineScope; + var limit = this.GetOneComponent("limit", engineCode); + + return limit?.Limit ?? 0; + } + + public override Query Clone() { + var clone = base.Clone(); + clone.Parent = Parent; + clone.QueryAlias = QueryAlias; + clone.IsDistinct = IsDistinct; + clone.Method = Method; + clone.Includes = Includes; + clone.Variables = Variables; + return clone; + } + + public Query As(string alias) { + QueryAlias = alias; + return this; + } + + /// + /// Sets a comment for the query. + /// + /// The comment. + /// + public Query Comment(string comment) { + this.comment = comment; + return this; + } + + public Query For(string engine, Func fn) { + EngineScope = engine; + + var result = fn.Invoke(this); + + // reset the engine + EngineScope = null; + + return result; + } + + public Query With(Query query) { + // Clear query alias and add it to the containing clause + if (string.IsNullOrWhiteSpace(query.QueryAlias)) { + throw new InvalidOperationException("No Alias found for the CTE query"); + } + + query = query.Clone(); + + var alias = query.QueryAlias.Trim(); + + // clear the query alias + query.QueryAlias = null; + + return AddComponent("cte", new QueryFromClause { + Query = query, + Alias = alias, + }); + } + + public Query With(Func fn) { + return With(fn.Invoke(new Query())); + } + + public Query With(string alias, Query query) { + return With(query.As(alias)); + } + + public Query With(string alias, Func fn) { + return With(alias, fn.Invoke(new Query())); + } + + /// + /// Constructs an ad-hoc table of the given data as a CTE. + /// + public Query With(string alias, IEnumerable columns, IEnumerable> valuesCollection) { + var columnsList = columns?.ToList(); + var valuesCollectionList = valuesCollection?.ToList(); + + if ((columnsList?.Count ?? 0) == 0 || (valuesCollectionList?.Count ?? 0) == 0) { + throw new InvalidOperationException("Columns and valuesCollection cannot be null or empty"); + } + + var clause = new AdHocTableFromClause() { + Alias = alias, + Columns = columnsList, + Values = new List(), + }; + + foreach (var values in valuesCollectionList) { + var valuesList = values.ToList(); + if (columnsList.Count != valuesList.Count) { + throw new InvalidOperationException("Columns count should be equal to each Values count"); + } + + clause.Values.AddRange(valuesList); + } + + return AddComponent("cte", clause); + } + + public Query WithRaw(string alias, string sql, params object[] bindings) { + return AddComponent("cte", new RawFromClause { + Alias = alias, + Expression = sql, + Bindings = bindings, + }); + } + + public Query Limit(int value) { + var newClause = new LimitClause { + Limit = value + }; + + return AddOrReplaceComponent("limit", newClause); + } + + public Query Offset(long value) { + var newClause = new OffsetClause { + Offset = value + }; + + return AddOrReplaceComponent("offset", newClause); + } + + public Query Offset(int value) { + return Offset((long)value); + } + + /// + /// Alias for Limit + /// + /// + /// + public Query Take(int limit) { + return Limit(limit); + } + + /// + /// Alias for Offset + /// + /// + /// + public Query Skip(int offset) { + return Offset(offset); + } + + /// + /// Set the limit and offset for a given page. + /// + /// + /// + /// + public Query ForPage(int page, int perPage = 15) { + return Skip((page - 1) * perPage).Take(perPage); + } + + public Query Distinct() { + IsDistinct = true; + return this; + } + + /// + /// Apply the callback's query changes if the given "condition" is true. + /// + /// + /// Invoked when the condition is true + /// Optional, invoked when the condition is false + /// + public Query When(bool condition, Func whenTrue, Func whenFalse = null) { + if (condition && whenTrue != null) { + return whenTrue.Invoke(this); + } + + if (!condition && whenFalse != null) { + return whenFalse.Invoke(this); + } + + return this; + } + + /// + /// Apply the callback's query changes if the given "condition" is false. + /// + /// + /// + /// + public Query WhenNot(bool condition, Func callback) { + if (!condition) { + return callback.Invoke(this); + } + + return this; + } + + public Query OrderBy(params string[] columns) { + foreach (var column in columns) { + AddComponent("order", new OrderBy { + Column = column, + Ascending = true + }); + } + + return this; + } + + public Query OrderByDesc(params string[] columns) { + foreach (var column in columns) { + AddComponent("order", new OrderBy { + Column = column, + Ascending = false + }); + } + + return this; + } + + public Query OrderByRaw(string expression, params object[] bindings) { + return AddComponent("order", new RawOrderBy { + Expression = expression, + Bindings = Helper.Flatten(bindings).ToArray() + }); + } + + public Query OrderByRandom(string seed) { + return AddComponent("order", new OrderByRandom { }); + } + + public Query GroupBy(params string[] columns) { + foreach (var column in columns) { + AddComponent("group", new Column { + Name = column + }); + } + + return this; + } + + public Query GroupByRaw(string expression, params object[] bindings) { + AddComponent("group", new RawColumn { + Expression = expression, + Bindings = bindings, + }); + + return this; + } + + public override Query NewQuery() { + return new Query(); + } + + public Query Include(string relationName, Query query, string foreignKey = null, string localKey = "Id", bool isMany = false) { + + Includes.Add(new Include { + Name = relationName, + LocalKey = localKey, + ForeignKey = foreignKey, + Query = query, + IsMany = isMany, + }); + + return this; + } + + public Query IncludeMany(string relationName, Query query, string foreignKey = null, string localKey = "Id") { + return Include(relationName, query, foreignKey, localKey, isMany: true); + } + + private static readonly ConcurrentDictionary CacheDictionaryProperties = new ConcurrentDictionary(); + + /// + /// Define a variable to be used within the query + /// + /// + /// + /// + public Query Define(string variable, object value) { + Variables.Add(variable, value); + + return this; + } + + public object FindVariable(string variable) { + var found = Variables.ContainsKey(variable); + + if (found) { + return Variables[variable]; + } + + if (Parent != null) { + return (Parent as Query).FindVariable(variable); + } + + throw new Exception($"Variable '{variable}' not found"); + } + + /// + /// Gather a list of key-values representing the properties of the object and their values. + /// + /// The plain C# object + /// + /// When true it will search for properties with the [Key] attribute + /// and will add it automatically to the Where clause + /// + /// + private IEnumerable> BuildKeyValuePairsFromObject(object data, bool considerKeys = false) { + var dictionary = new Dictionary(); + var props = CacheDictionaryProperties.GetOrAdd(data.GetType(), type => type.GetRuntimeProperties().ToArray()); + + foreach (var property in props) { + if (property.GetCustomAttribute(typeof(IgnoreAttribute)) != null) { + continue; + } + + var value = property.GetValue(data); + + var colAttr = property.GetCustomAttribute(typeof(ColumnAttribute)) as ColumnAttribute; + + var name = colAttr?.Name ?? property.Name; + + dictionary.Add(name, value); + + if (considerKeys && colAttr != null) { + if ((colAttr as KeyAttribute) != null) { + this.Where(name, value); + } + } + } + + return dictionary; + } +} diff --git a/QueryBuilder/SqlResult.cs b/QueryBuilder/SqlResult.cs index b84baf8a..5b78cbca 100644 --- a/QueryBuilder/SqlResult.cs +++ b/QueryBuilder/SqlResult.cs @@ -1,96 +1,82 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace SqlKata -{ - public class SqlResult - { - private string ParameterPlaceholder { get; set; } - private string EscapeCharacter { get; set; } - public SqlResult(string parameterPlaceholder, string escapeCharacter) - { - ParameterPlaceholder = parameterPlaceholder; - EscapeCharacter = escapeCharacter; - } - public Query Query { get; set; } - public string RawSql { get; set; } = ""; - public List Bindings { get; set; } = new List(); - public string Sql { get; set; } = ""; - public Dictionary NamedBindings = new Dictionary(); - - private static readonly Type[] NumberTypes = - { - typeof(int), - typeof(long), - typeof(decimal), - typeof(double), - typeof(float), - typeof(short), - typeof(ushort), - typeof(ulong), - }; - - public override string ToString() - { - var deepParameters = Helper.Flatten(Bindings).ToList(); - - var subject = Helper.ReplaceAll(RawSql, ParameterPlaceholder, EscapeCharacter, i => - { - if (i >= deepParameters.Count) - { - throw new Exception( - $"Failed to retrieve a binding at index {i}, the total bindings count is {Bindings.Count}"); - } - - var value = deepParameters[i]; - return ChangeToSqlValue(value); - }); - - return Helper.RemoveEscapeCharacter(subject, ParameterPlaceholder, EscapeCharacter); - } - - private string ChangeToSqlValue(object value) - { - if (value == null) - { - return "NULL"; - } - - if (Helper.IsArray(value)) - { - return Helper.JoinArray(",", value as IEnumerable); - } - - if (NumberTypes.Contains(value.GetType())) - { - return Convert.ToString(value, CultureInfo.InvariantCulture); - } - - if (value is DateTime date) - { - if (date.Date == date) - { - return "'" + date.ToString("yyyy-MM-dd") + "'"; - } - - return "'" + date.ToString("yyyy-MM-dd HH:mm:ss") + "'"; - } - - if (value is bool vBool) - { - return vBool ? "true" : "false"; - } - - if (value is Enum vEnum) - { - return Convert.ToInt32(vEnum) + $" /* {vEnum} */"; - } - - // fallback to string - return "'" + value.ToString().Replace("'","''") + "'"; - } - } -} +namespace SqlKata; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +public class SqlResult { + private string ParameterPlaceholder { get; set; } + private string EscapeCharacter { get; set; } + public SqlResult(string parameterPlaceholder, string escapeCharacter) { + ParameterPlaceholder = parameterPlaceholder; + EscapeCharacter = escapeCharacter; + } + public Query Query { get; set; } + public string RawSql { get; set; } = ""; + public List Bindings { get; set; } = new List(); + public string Sql { get; set; } = ""; + public Dictionary NamedBindings = new Dictionary(); + + private static readonly Type[] NumberTypes = + { + typeof(int), + typeof(long), + typeof(decimal), + typeof(double), + typeof(float), + typeof(short), + typeof(ushort), + typeof(ulong), + }; + + public override string ToString() { + var deepParameters = Helper.Flatten(Bindings).ToList(); + + var subject = Helper.ReplaceAll(RawSql, ParameterPlaceholder, EscapeCharacter, i => { + if (i >= deepParameters.Count) { + throw new Exception( + $"Failed to retrieve a binding at index {i}, the total bindings count is {Bindings.Count}"); + } + + var value = deepParameters[i]; + return ChangeToSqlValue(value); + }); + + return Helper.RemoveEscapeCharacter(subject, ParameterPlaceholder, EscapeCharacter); + } + + private string ChangeToSqlValue(object value) { + if (value == null) { + return "NULL"; + } + + if (Helper.IsArray(value)) { + return Helper.JoinArray(",", value as IEnumerable); + } + + if (NumberTypes.Contains(value.GetType())) { + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + + if (value is DateTime date) { + if (date.Date == date) { + return "'" + date.ToString("yyyy-MM-dd") + "'"; + } + + return "'" + date.ToString("yyyy-MM-dd HH:mm:ss") + "'"; + } + + if (value is bool vBool) { + return vBool ? "true" : "false"; + } + + if (value is Enum vEnum) { + return Convert.ToInt32(vEnum) + $" /* {vEnum} */"; + } + + // fallback to string + return "'" + value.ToString().Replace("'", "''") + "'"; + } +} diff --git a/QueryBuilder/UnsafeLiteral.cs b/QueryBuilder/UnsafeLiteral.cs index 23d81e2d..9253b754 100644 --- a/QueryBuilder/UnsafeLiteral.cs +++ b/QueryBuilder/UnsafeLiteral.cs @@ -1,23 +1,17 @@ -namespace SqlKata -{ - public class UnsafeLiteral - { - public string Value { get; set; } - - public UnsafeLiteral(string value, bool replaceQuotes = true) - { - if (value == null) - { - value = ""; - } - - if (replaceQuotes) - { - value = value.Replace("'", "''"); - } - - this.Value = value; - } - - } -} +namespace SqlKata; +public class UnsafeLiteral { + public string Value { get; set; } + + public UnsafeLiteral(string value, bool replaceQuotes = true) { + if (value == null) { + value = ""; + } + + if (replaceQuotes) { + value = value.Replace("'", "''"); + } + + this.Value = value; + } + +} diff --git a/QueryBuilder/Variable.cs b/QueryBuilder/Variable.cs index 63b936ea..1730e32b 100644 --- a/QueryBuilder/Variable.cs +++ b/QueryBuilder/Variable.cs @@ -1,13 +1,9 @@ -namespace SqlKata -{ - public class Variable - { - public string Name { get; set; } - - public Variable(string name) - { - this.Name = name; - } - - } -} +namespace SqlKata; +public class Variable { + public string Name { get; set; } + + public Variable(string name) { + this.Name = name; + } + +} diff --git a/SqlKata.Execution/InsertGetId.cs b/SqlKata.Execution/InsertGetId.cs index a25d6a3f..33b701a2 100644 --- a/SqlKata.Execution/InsertGetId.cs +++ b/SqlKata.Execution/InsertGetId.cs @@ -1,7 +1,4 @@ -namespace SqlKata.Execution -{ - public class InsertGetIdRow - { - public T Id { get; set; } - } -} +namespace SqlKata.Execution; +public class InsertGetIdRow { + public T Id { get; set; } +} diff --git a/SqlKata.Execution/PaginationIterator.cs b/SqlKata.Execution/PaginationIterator.cs index 9af8d036..ca3d2ab1 100644 --- a/SqlKata.Execution/PaginationIterator.cs +++ b/SqlKata.Execution/PaginationIterator.cs @@ -1,30 +1,25 @@ -using System.Collections; -using System.Collections.Generic; - -namespace SqlKata.Execution -{ - public class PaginationIterator : IEnumerable> - { - public PaginationResult FirstPage { get; set; } - public PaginationResult CurrentPage { get; set; } - - public IEnumerator> GetEnumerator() - { - CurrentPage = FirstPage; - - yield return CurrentPage; - - while (CurrentPage.HasNext) - { - CurrentPage = CurrentPage.Next(); - yield return CurrentPage; - } - - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +namespace SqlKata.Execution; + +using System.Collections; +using System.Collections.Generic; + +public class PaginationIterator : IEnumerable> { + public PaginationResult FirstPage { get; set; } + public PaginationResult CurrentPage { get; set; } + + public IEnumerator> GetEnumerator() { + CurrentPage = FirstPage; + + yield return CurrentPage; + + while (CurrentPage.HasNext) { + CurrentPage = CurrentPage.Next(); + yield return CurrentPage; + } + + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } +} diff --git a/SqlKata.Execution/PaginationResult.cs b/SqlKata.Execution/PaginationResult.cs index 85277503..36b4491c 100644 --- a/SqlKata.Execution/PaginationResult.cs +++ b/SqlKata.Execution/PaginationResult.cs @@ -1,107 +1,85 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Threading; -using System.Threading.Tasks; - -namespace SqlKata.Execution -{ - public class PaginationResult - { - public Query Query { get; set; } - public long Count { get; set; } - public IEnumerable List { get; set; } - public int Page { get; set; } - public int PerPage { get; set; } - public int TotalPages - { - get - { - - if (PerPage < 1) - { - return 0; - } - - var div = (float)Count / PerPage; - - return (int)Math.Ceiling(div); - - } - } - - public bool IsFirst - { - get - { - return Page == 1; - } - } - - public bool IsLast - { - get - { - return Page == TotalPages; - } - } - - public bool HasNext - { - get - { - return Page < TotalPages; - } - } - - public bool HasPrevious - { - get - { - return Page > 1; - } - } - - public Query NextQuery() - { - return this.Query.ForPage(Page + 1, PerPage); - } - - public PaginationResult Next(IDbTransaction transaction = null, int? timeout = null) - { - return this.Query.Paginate(Page + 1, PerPage, transaction, timeout); - } - - public async Task> NextAsync(IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await this.Query.PaginateAsync(Page + 1, PerPage, transaction, timeout, cancellationToken); - } - - public Query PreviousQuery() - { - return this.Query.ForPage(Page - 1, PerPage); - } - - public PaginationResult Previous(IDbTransaction transaction = null, int? timeout = null) - { - return this.Query.Paginate(Page - 1, PerPage, transaction, timeout); - } - - public async Task> PreviousAsync(IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await this.Query.PaginateAsync(Page - 1, PerPage, transaction, timeout, cancellationToken); - } - - public PaginationIterator Each - { - get - { - return new PaginationIterator - { - FirstPage = this - }; - } - } - - } -} +namespace SqlKata.Execution; + +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading; +using System.Threading.Tasks; + +public class PaginationResult { + public Query Query { get; set; } + public long Count { get; set; } + public IEnumerable List { get; set; } + public int Page { get; set; } + public int PerPage { get; set; } + public int TotalPages { + get { + + if (PerPage < 1) { + return 0; + } + + var div = (float)Count / PerPage; + + return (int)Math.Ceiling(div); + + } + } + + public bool IsFirst { + get { + return Page == 1; + } + } + + public bool IsLast { + get { + return Page == TotalPages; + } + } + + public bool HasNext { + get { + return Page < TotalPages; + } + } + + public bool HasPrevious { + get { + return Page > 1; + } + } + + public Query NextQuery() { + return this.Query.ForPage(Page + 1, PerPage); + } + + public PaginationResult Next(IDbTransaction transaction = null, int? timeout = null) { + return this.Query.Paginate(Page + 1, PerPage, transaction, timeout); + } + + public async Task> NextAsync(IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await this.Query.PaginateAsync(Page + 1, PerPage, transaction, timeout, cancellationToken); + } + + public Query PreviousQuery() { + return this.Query.ForPage(Page - 1, PerPage); + } + + public PaginationResult Previous(IDbTransaction transaction = null, int? timeout = null) { + return this.Query.Paginate(Page - 1, PerPage, transaction, timeout); + } + + public async Task> PreviousAsync(IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await this.Query.PaginateAsync(Page - 1, PerPage, transaction, timeout, cancellationToken); + } + + public PaginationIterator Each { + get { + return new PaginationIterator { + FirstPage = this + }; + } + } + +} diff --git a/SqlKata.Execution/Properties/AssemblyInfo.cs b/SqlKata.Execution/Properties/AssemblyInfo.cs index 5e161ab1..148172f3 100644 --- a/SqlKata.Execution/Properties/AssemblyInfo.cs +++ b/SqlKata.Execution/Properties/AssemblyInfo.cs @@ -1,3 +1,3 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("QueryBuilder.Tests")] \ No newline at end of file +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("QueryBuilder.Tests")] diff --git a/SqlKata.Execution/Query.Extensions.cs b/SqlKata.Execution/Query.Extensions.cs index 4a19d608..fb3bfcc8 100644 --- a/SqlKata.Execution/Query.Extensions.cs +++ b/SqlKata.Execution/Query.Extensions.cs @@ -1,390 +1,319 @@ -using System.Collections.Generic; -using System; -using System.Threading.Tasks; -using System.Data; -using System.Threading; - -namespace SqlKata.Execution -{ - public static class QueryExtensions - { - public static bool Exists(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Exists(query, transaction, timeout); - } - - public async static Task ExistsAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExistsAsync(query, transaction, timeout, cancellationToken); - } - - public static bool NotExist(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return !CreateQueryFactory(query).Exists(query, transaction, timeout); - } - - public async static Task NotExistAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return !(await CreateQueryFactory(query).ExistsAsync(query, transaction, timeout, cancellationToken)); - } - - public static IEnumerable Get(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Get(query, transaction, timeout); - } - - public static async Task> GetAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).GetAsync(query, transaction, timeout, cancellationToken); - } - - public static IEnumerable Get(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return query.Get(transaction, timeout); - } - - public static async Task> GetAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await GetAsync(query, transaction, timeout, cancellationToken); - } - - public static T FirstOrDefault(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).FirstOrDefault(query, transaction, timeout); - } - - public static async Task FirstOrDefaultAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).FirstOrDefaultAsync(query, transaction, timeout, cancellationToken); - } - - public static dynamic FirstOrDefault(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return FirstOrDefault(query, transaction, timeout); - } - - public static async Task FirstOrDefaultAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await FirstOrDefaultAsync(query, transaction, timeout, cancellationToken); - } - - public static T First(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).First(query, transaction, timeout); - } - - public static async Task FirstAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).FirstAsync(query, transaction, timeout, cancellationToken); - } - - public static dynamic First(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return First(query, transaction, timeout); - } - - public static async Task FirstAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await FirstAsync(query, transaction, timeout, cancellationToken); - } - - public static PaginationResult Paginate(this Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null) - { - var db = CreateQueryFactory(query); - - return db.Paginate(query, page, perPage, transaction, timeout); - } - - public static async Task> PaginateAsync(this Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var db = CreateQueryFactory(query); - - return await db.PaginateAsync(query, page, perPage, transaction, timeout, cancellationToken); - } - - public static PaginationResult Paginate(this Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null) - { - return query.Paginate(page, perPage, transaction, timeout); - } - - public static async Task> PaginateAsync(this Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await PaginateAsync(query, page, perPage, transaction, timeout, cancellationToken); - } - - public static void Chunk(this Query query, int chunkSize, Func, int, bool> func, IDbTransaction transaction = null, int? timeout = null) - { - var db = CreateQueryFactory(query); - - db.Chunk(query, chunkSize, func, transaction, timeout); - } - public static async Task ChunkAsync(this Query query, int chunkSize, Func, int, bool> func, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - await CreateQueryFactory(query).ChunkAsync(query, chunkSize, func, transaction, timeout, cancellationToken); - } - - public static void Chunk(this Query query, int chunkSize, Func, int, bool> func, IDbTransaction transaction = null, int? timeout = null) - { - query.Chunk(chunkSize, func, transaction, timeout); - } - public static async Task ChunkAsync(this Query query, int chunkSize, Func, int, bool> func, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - await ChunkAsync(query, chunkSize, func, transaction, timeout, cancellationToken); - } - - public static void Chunk(this Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null) - { - var db = CreateQueryFactory(query); - - db.Chunk(query, chunkSize, action, transaction, timeout); - } - - public static async Task ChunkAsync(this Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - await CreateQueryFactory(query).ChunkAsync(query, chunkSize, action, transaction, timeout, cancellationToken); - } - - public static void Chunk(this Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null) - { - query.Chunk(chunkSize, action, transaction, timeout); - } - - public static async Task ChunkAsync(this Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - await ChunkAsync(query, chunkSize, action, transaction, timeout, cancellationToken); - } - - public static int Insert(this Query query, IEnumerable> values, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsInsert(values), transaction, timeout); - } - - public static async Task InsertAsync(this Query query, IEnumerable> values, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsInsert(values), transaction, timeout, cancellationToken); - } - - public static int Insert(this Query query, IEnumerable columns, IEnumerable> valuesCollection, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsInsert(columns, valuesCollection), transaction, timeout); - } - - public static async Task InsertAsync(this Query query, IEnumerable columns, IEnumerable> valuesCollection, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsInsert(columns, valuesCollection), transaction, timeout, cancellationToken); - } - - public static int Insert(this Query query, IEnumerable columns, Query fromQuery, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsInsert(columns, fromQuery), transaction, timeout); - } - - public static async Task InsertAsync(this Query query, IEnumerable columns, Query fromQuery, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsInsert(columns, fromQuery), transaction, timeout, cancellationToken); - } - - public static int Insert(this Query query, object data, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsInsert(data), transaction, timeout); - } - - public static async Task InsertAsync(this Query query, object data, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsInsert(data), transaction, timeout, cancellationToken); - } - - public static T InsertGetId(this Query query, object data, IDbTransaction transaction = null, int? timeout = null) - { - var db = CreateQueryFactory(query); - - var row = db.First>(query.AsInsert(data, true), transaction, timeout); - - return row.Id; - } - - public static async Task InsertGetIdAsync(this Query query, object data, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var row = await CreateQueryFactory(query) - .FirstAsync>(query.AsInsert(data, true), transaction, timeout, cancellationToken); - - return row.Id; - } - - public static T InsertGetId(this Query query, IEnumerable> data, IDbTransaction transaction = null, int? timeout = null) - { - var row = CreateQueryFactory(query).First>(query.AsInsert(data, true), transaction, timeout); - - return row.Id; - } - - public static async Task InsertGetIdAsync(this Query query, IEnumerable> data, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var row = await CreateQueryFactory(query).FirstAsync>(query.AsInsert(data, true), transaction, timeout, cancellationToken); - - return row.Id; - } - - public static int Update(this Query query, IEnumerable> values, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsUpdate(values), transaction, timeout); - } - - public static async Task UpdateAsync(this Query query, IEnumerable> values, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsUpdate(values), transaction, timeout, cancellationToken); - } - - public static int Update(this Query query, object data, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsUpdate(data), transaction, timeout); - } - - public static async Task UpdateAsync(this Query query, object data, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsUpdate(data), transaction, timeout, cancellationToken); - } - - public static int Increment(this Query query, string column, int value = 1, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsIncrement(column, value), transaction, timeout); - } - - public static async Task IncrementAsync(this Query query, string column, int value = 1, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsIncrement(column, value), transaction, timeout, cancellationToken); - } - - public static int Decrement(this Query query, string column, int value = 1, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsDecrement(column, value), transaction, timeout); - } - - public static async Task DecrementAsync(this Query query, string column, int value = 1, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsDecrement(column, value), transaction, timeout, cancellationToken); - } - - public static int Delete(this Query query, IDbTransaction transaction = null, int? timeout = null) - { - return CreateQueryFactory(query).Execute(query.AsDelete(), transaction, timeout); - } - - public static async Task DeleteAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await CreateQueryFactory(query).ExecuteAsync(query.AsDelete(), transaction, timeout, cancellationToken); - } - - public static T Aggregate(this Query query, string aggregateOperation, string[] columns, IDbTransaction transaction = null, int? timeout = null) - { - var db = CreateQueryFactory(query); - - return db.ExecuteScalar(query.AsAggregate(aggregateOperation, columns), transaction, timeout); - } - - public static async Task AggregateAsync(this Query query, string aggregateOperation, string[] columns, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var db = CreateQueryFactory(query); - return await db.ExecuteScalarAsync(query.AsAggregate(aggregateOperation, columns), transaction, timeout, cancellationToken); - } - - public static T Count(this Query query, string[] columns = null, IDbTransaction transaction = null, int? timeout = null) - { - var db = CreateQueryFactory(query); - - return db.ExecuteScalar(query.AsCount(columns), transaction, timeout); - } - - public static async Task CountAsync(this Query query, string[] columns = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var db = CreateQueryFactory(query); - - return await db.ExecuteScalarAsync(query.AsCount(columns), transaction, timeout, cancellationToken); - } - - public static T Average(this Query query, string column, IDbTransaction transaction = null, int? timeout = null) - { - return query.Aggregate("avg", new[] { column }, transaction, timeout); - } - - public static async Task AverageAsync(this Query query, string column, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await query.AggregateAsync("avg", new[] { column }, transaction, timeout, cancellationToken); - } - - public static T Sum(this Query query, string column, IDbTransaction transaction = null, int? timeout = null) - { - return query.Aggregate("sum", new[] { column }, transaction, timeout); - } - - public static async Task SumAsync(this Query query, string column, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await query.AggregateAsync("sum", new[] { column }, transaction, timeout, cancellationToken); - } - - public static T Min(this Query query, string column, IDbTransaction transaction = null, int? timeout = null) - { - return query.Aggregate("min", new[] { column }, transaction, timeout); - } - - public static async Task MinAsync(this Query query, string column, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await query.AggregateAsync("min", new[] { column }, transaction, timeout, cancellationToken); - } - - public static T Max(this Query query, string column, IDbTransaction transaction = null, int? timeout = null) - { - return query.Aggregate("max", new[] { column }, transaction, timeout); - } - - public static async Task MaxAsync(this Query query, string column, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await query.AggregateAsync("max", new[] { column }, transaction, timeout, cancellationToken); - } - - internal static XQuery CastToXQuery(Query query, string method = null) - { - var xQuery = query as XQuery; - - if (xQuery is null) - { - if (method == null) - { - throw new InvalidOperationException( - $"Execution methods can only be used with `{nameof(XQuery)}` instances, " + - $"consider using the `{nameof(QueryFactory)}.{nameof(QueryFactory.Query)}()` to create executable queries, " + - $"check https://sqlkata.com/docs/execution/setup#xquery-class for more info"); - } - - throw new InvalidOperationException($"The method '{method}()' can only be used with `{nameof(XQuery)}` instances, " + - $"consider using the `{nameof(QueryFactory)}.{nameof(QueryFactory.Query)}()` to create executable queries, " + - $"check https://sqlkata.com/docs/execution/setup#xquery-class for more info"); - } - - return xQuery; - } - - internal static QueryFactory CreateQueryFactory(XQuery xQuery) - { - QueryFactory factory; - - if (xQuery.QueryFactory != null) - { - factory = new QueryFactory(xQuery.Connection, xQuery.Compiler, xQuery.QueryFactory.QueryTimeout); - } - else - { - factory = new QueryFactory(xQuery.Connection, xQuery.Compiler); - } - - factory.Logger = xQuery.Logger; - - return factory; - } - - internal static QueryFactory CreateQueryFactory(Query query) - { - return CreateQueryFactory(CastToXQuery(query)); - } - } -} +namespace SqlKata.Execution; + +using System.Collections.Generic; +using System; +using System.Threading.Tasks; +using System.Data; +using System.Threading; + +public static class QueryExtensions { + public static bool Exists(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Exists(query, transaction, timeout); + } + + public async static Task ExistsAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExistsAsync(query, transaction, timeout, cancellationToken); + } + + public static bool NotExist(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return !CreateQueryFactory(query).Exists(query, transaction, timeout); + } + + public async static Task NotExistAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return !(await CreateQueryFactory(query).ExistsAsync(query, transaction, timeout, cancellationToken)); + } + + public static IEnumerable Get(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Get(query, transaction, timeout); + } + + public static async Task> GetAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).GetAsync(query, transaction, timeout, cancellationToken); + } + + public static IEnumerable Get(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return query.Get(transaction, timeout); + } + + public static async Task> GetAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await GetAsync(query, transaction, timeout, cancellationToken); + } + + public static T FirstOrDefault(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).FirstOrDefault(query, transaction, timeout); + } + + public static async Task FirstOrDefaultAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).FirstOrDefaultAsync(query, transaction, timeout, cancellationToken); + } + + public static dynamic FirstOrDefault(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return FirstOrDefault(query, transaction, timeout); + } + + public static async Task FirstOrDefaultAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await FirstOrDefaultAsync(query, transaction, timeout, cancellationToken); + } + + public static T First(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).First(query, transaction, timeout); + } + + public static async Task FirstAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).FirstAsync(query, transaction, timeout, cancellationToken); + } + + public static dynamic First(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return First(query, transaction, timeout); + } + + public static async Task FirstAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await FirstAsync(query, transaction, timeout, cancellationToken); + } + + public static PaginationResult Paginate(this Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null) { + var db = CreateQueryFactory(query); + + return db.Paginate(query, page, perPage, transaction, timeout); + } + + public static async Task> PaginateAsync(this Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var db = CreateQueryFactory(query); + + return await db.PaginateAsync(query, page, perPage, transaction, timeout, cancellationToken); + } + + public static PaginationResult Paginate(this Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null) { + return query.Paginate(page, perPage, transaction, timeout); + } + + public static async Task> PaginateAsync(this Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await PaginateAsync(query, page, perPage, transaction, timeout, cancellationToken); + } + + public static void Chunk(this Query query, int chunkSize, Func, int, bool> func, IDbTransaction transaction = null, int? timeout = null) { + var db = CreateQueryFactory(query); + + db.Chunk(query, chunkSize, func, transaction, timeout); + } + public static async Task ChunkAsync(this Query query, int chunkSize, Func, int, bool> func, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + await CreateQueryFactory(query).ChunkAsync(query, chunkSize, func, transaction, timeout, cancellationToken); + } + + public static void Chunk(this Query query, int chunkSize, Func, int, bool> func, IDbTransaction transaction = null, int? timeout = null) { + query.Chunk(chunkSize, func, transaction, timeout); + } + public static async Task ChunkAsync(this Query query, int chunkSize, Func, int, bool> func, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + await ChunkAsync(query, chunkSize, func, transaction, timeout, cancellationToken); + } + + public static void Chunk(this Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null) { + var db = CreateQueryFactory(query); + + db.Chunk(query, chunkSize, action, transaction, timeout); + } + + public static async Task ChunkAsync(this Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + await CreateQueryFactory(query).ChunkAsync(query, chunkSize, action, transaction, timeout, cancellationToken); + } + + public static void Chunk(this Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null) { + query.Chunk(chunkSize, action, transaction, timeout); + } + + public static async Task ChunkAsync(this Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + await ChunkAsync(query, chunkSize, action, transaction, timeout, cancellationToken); + } + + public static int Insert(this Query query, IEnumerable> values, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsInsert(values), transaction, timeout); + } + + public static async Task InsertAsync(this Query query, IEnumerable> values, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsInsert(values), transaction, timeout, cancellationToken); + } + + public static int Insert(this Query query, IEnumerable columns, IEnumerable> valuesCollection, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsInsert(columns, valuesCollection), transaction, timeout); + } + + public static async Task InsertAsync(this Query query, IEnumerable columns, IEnumerable> valuesCollection, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsInsert(columns, valuesCollection), transaction, timeout, cancellationToken); + } + + public static int Insert(this Query query, IEnumerable columns, Query fromQuery, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsInsert(columns, fromQuery), transaction, timeout); + } + + public static async Task InsertAsync(this Query query, IEnumerable columns, Query fromQuery, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsInsert(columns, fromQuery), transaction, timeout, cancellationToken); + } + + public static int Insert(this Query query, object data, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsInsert(data), transaction, timeout); + } + + public static async Task InsertAsync(this Query query, object data, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsInsert(data), transaction, timeout, cancellationToken); + } + + public static T InsertGetId(this Query query, object data, IDbTransaction transaction = null, int? timeout = null) { + var db = CreateQueryFactory(query); + + var row = db.First>(query.AsInsert(data, true), transaction, timeout); + + return row.Id; + } + + public static async Task InsertGetIdAsync(this Query query, object data, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var row = await CreateQueryFactory(query) + .FirstAsync>(query.AsInsert(data, true), transaction, timeout, cancellationToken); + + return row.Id; + } + + public static T InsertGetId(this Query query, IEnumerable> data, IDbTransaction transaction = null, int? timeout = null) { + var row = CreateQueryFactory(query).First>(query.AsInsert(data, true), transaction, timeout); + + return row.Id; + } + + public static async Task InsertGetIdAsync(this Query query, IEnumerable> data, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var row = await CreateQueryFactory(query).FirstAsync>(query.AsInsert(data, true), transaction, timeout, cancellationToken); + + return row.Id; + } + + public static int Update(this Query query, IEnumerable> values, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsUpdate(values), transaction, timeout); + } + + public static async Task UpdateAsync(this Query query, IEnumerable> values, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsUpdate(values), transaction, timeout, cancellationToken); + } + + public static int Update(this Query query, object data, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsUpdate(data), transaction, timeout); + } + + public static async Task UpdateAsync(this Query query, object data, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsUpdate(data), transaction, timeout, cancellationToken); + } + + public static int Increment(this Query query, string column, int value = 1, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsIncrement(column, value), transaction, timeout); + } + + public static async Task IncrementAsync(this Query query, string column, int value = 1, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsIncrement(column, value), transaction, timeout, cancellationToken); + } + + public static int Decrement(this Query query, string column, int value = 1, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsDecrement(column, value), transaction, timeout); + } + + public static async Task DecrementAsync(this Query query, string column, int value = 1, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsDecrement(column, value), transaction, timeout, cancellationToken); + } + + public static int Delete(this Query query, IDbTransaction transaction = null, int? timeout = null) { + return CreateQueryFactory(query).Execute(query.AsDelete(), transaction, timeout); + } + + public static async Task DeleteAsync(this Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await CreateQueryFactory(query).ExecuteAsync(query.AsDelete(), transaction, timeout, cancellationToken); + } + + public static T Aggregate(this Query query, string aggregateOperation, string[] columns, IDbTransaction transaction = null, int? timeout = null) { + var db = CreateQueryFactory(query); + + return db.ExecuteScalar(query.AsAggregate(aggregateOperation, columns), transaction, timeout); + } + + public static async Task AggregateAsync(this Query query, string aggregateOperation, string[] columns, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var db = CreateQueryFactory(query); + return await db.ExecuteScalarAsync(query.AsAggregate(aggregateOperation, columns), transaction, timeout, cancellationToken); + } + + public static T Count(this Query query, string[] columns = null, IDbTransaction transaction = null, int? timeout = null) { + var db = CreateQueryFactory(query); + + return db.ExecuteScalar(query.AsCount(columns), transaction, timeout); + } + + public static async Task CountAsync(this Query query, string[] columns = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var db = CreateQueryFactory(query); + + return await db.ExecuteScalarAsync(query.AsCount(columns), transaction, timeout, cancellationToken); + } + + public static T Average(this Query query, string column, IDbTransaction transaction = null, int? timeout = null) { + return query.Aggregate("avg", new[] { column }, transaction, timeout); + } + + public static async Task AverageAsync(this Query query, string column, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await query.AggregateAsync("avg", new[] { column }, transaction, timeout, cancellationToken); + } + + public static T Sum(this Query query, string column, IDbTransaction transaction = null, int? timeout = null) { + return query.Aggregate("sum", new[] { column }, transaction, timeout); + } + + public static async Task SumAsync(this Query query, string column, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await query.AggregateAsync("sum", new[] { column }, transaction, timeout, cancellationToken); + } + + public static T Min(this Query query, string column, IDbTransaction transaction = null, int? timeout = null) { + return query.Aggregate("min", new[] { column }, transaction, timeout); + } + + public static async Task MinAsync(this Query query, string column, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await query.AggregateAsync("min", new[] { column }, transaction, timeout, cancellationToken); + } + + public static T Max(this Query query, string column, IDbTransaction transaction = null, int? timeout = null) { + return query.Aggregate("max", new[] { column }, transaction, timeout); + } + + public static async Task MaxAsync(this Query query, string column, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await query.AggregateAsync("max", new[] { column }, transaction, timeout, cancellationToken); + } + + internal static XQuery CastToXQuery(Query query, string method = null) { + var xQuery = query as XQuery; + + if (xQuery is null) { + if (method == null) { + throw new InvalidOperationException( + $"Execution methods can only be used with `{nameof(XQuery)}` instances, " + + $"consider using the `{nameof(QueryFactory)}.{nameof(QueryFactory.Query)}()` to create executable queries, " + + $"check https://sqlkata.com/docs/execution/setup#xquery-class for more info"); + } + + throw new InvalidOperationException($"The method '{method}()' can only be used with `{nameof(XQuery)}` instances, " + + $"consider using the `{nameof(QueryFactory)}.{nameof(QueryFactory.Query)}()` to create executable queries, " + + $"check https://sqlkata.com/docs/execution/setup#xquery-class for more info"); + } + + return xQuery; + } + + internal static QueryFactory CreateQueryFactory(XQuery xQuery) { + QueryFactory factory; + + if (xQuery.QueryFactory != null) { + factory = new QueryFactory(xQuery.Connection, xQuery.Compiler, xQuery.QueryFactory.QueryTimeout); + } + else { + factory = new QueryFactory(xQuery.Connection, xQuery.Compiler); + } + + factory.Logger = xQuery.Logger; + + return factory; + } + + internal static QueryFactory CreateQueryFactory(Query query) { + return CreateQueryFactory(CastToXQuery(query)); + } +} diff --git a/SqlKata.Execution/QueryFactory.cs b/SqlKata.Execution/QueryFactory.cs index 5ff399aa..a007b776 100644 --- a/SqlKata.Execution/QueryFactory.cs +++ b/SqlKata.Execution/QueryFactory.cs @@ -1,899 +1,792 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Dynamic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Dapper; -using Humanizer; -using SqlKata.Compilers; - -namespace SqlKata.Execution -{ - public class QueryFactory : IDisposable - { - public IDbConnection Connection { get; set; } - public Compiler Compiler { get; set; } - public Action Logger = result => { }; - private bool disposedValue; - - public int QueryTimeout { get; set; } = 30; - - public QueryFactory() { } - - public QueryFactory(IDbConnection connection, Compiler compiler, int timeout = 30) - { - Connection = connection; - Compiler = compiler; - QueryTimeout = timeout; - } - - public Query Query() - { - var query = new XQuery(this.Connection, this.Compiler); - - query.QueryFactory = this; - - query.Logger = Logger; - - return query; - } - - public Query Query(string table) - { - return Query().From(table); - } - - /// - /// Create an XQuery instance from a regular Query - /// - /// - /// - public Query FromQuery(Query query) - { - var xQuery = new XQuery(this.Connection, this.Compiler); - - xQuery.QueryFactory = this; - - xQuery.Clauses = query.Clauses.Select(x => x.Clone()).ToList(); - - xQuery.SetParent(query.Parent); - xQuery.QueryAlias = query.QueryAlias; - xQuery.IsDistinct = query.IsDistinct; - xQuery.Method = query.Method; - xQuery.Includes = query.Includes; - xQuery.Variables = query.Variables; - - xQuery.SetEngineScope(query.EngineScope); - - xQuery.Logger = Logger; - - return xQuery; - } - - public IEnumerable Get(Query query, IDbTransaction transaction = null, int? timeout = null) - { - var compiled = CompileAndLog(query); - - var result = this.Connection.Query( - compiled.Sql, - compiled.NamedBindings, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout - ).ToList(); - - result = handleIncludes(query, result).ToList(); - - return result; - } - - public async Task> GetAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var compiled = CompileAndLog(query); - var commandDefinition = new CommandDefinition( - commandText: compiled.Sql, - parameters: compiled.NamedBindings, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout, - cancellationToken: cancellationToken); - - var result = (await this.Connection.QueryAsync(commandDefinition)).ToList(); - - result = (await handleIncludesAsync(query, result, cancellationToken)).ToList(); - - return result; - } - - public IEnumerable> GetDictionary(Query query, IDbTransaction transaction = null, int? timeout = null) - { - var compiled = CompileAndLog(query); - - var result = this.Connection.Query( - compiled.Sql, - compiled.NamedBindings, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout - ); - - return result.Cast>(); - } - - public async Task>> GetDictionaryAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var compiled = CompileAndLog(query); - var commandDefinition = new CommandDefinition( - commandText: compiled.Sql, - parameters: compiled.NamedBindings, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout, - cancellationToken: cancellationToken); - - var result = await this.Connection.QueryAsync(commandDefinition); - - return result.Cast>(); - } - - public IEnumerable Get(Query query, IDbTransaction transaction = null, int? timeout = null) - { - return Get(query, transaction, timeout); - } - - public async Task> GetAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await GetAsync(query, transaction, timeout, cancellationToken); - } - - public T FirstOrDefault(Query query, IDbTransaction transaction = null, int? timeout = null) - { - var list = Get(query.Limit(1), transaction, timeout); - - return list.ElementAtOrDefault(0); - } - - public async Task FirstOrDefaultAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var list = await GetAsync(query.Limit(1), transaction, timeout, cancellationToken); - - return list.ElementAtOrDefault(0); - } - - public dynamic FirstOrDefault(Query query, IDbTransaction transaction = null, int? timeout = null) - { - return FirstOrDefault(query, transaction, timeout); - } - - public async Task FirstOrDefaultAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await FirstOrDefaultAsync(query, transaction, timeout, cancellationToken); - } - - public T First(Query query, IDbTransaction transaction = null, int? timeout = null) - { - var item = FirstOrDefault(query, transaction, timeout); - - if (item == null) - { - throw new InvalidOperationException("The sequence contains no elements"); - } - - return item; - } - - public async Task FirstAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var item = await FirstOrDefaultAsync(query, transaction, timeout, cancellationToken); - - if (item == null) - { - throw new InvalidOperationException("The sequence contains no elements"); - } - - return item; - } - - public dynamic First(Query query, IDbTransaction transaction = null, int? timeout = null) - { - return First(query, transaction, timeout); - } - - public async Task FirstAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await FirstAsync(query, transaction, timeout, cancellationToken); - } - - public int Execute( - Query query, - IDbTransaction transaction = null, - int? timeout = null - ) - { - var compiled = CompileAndLog(query); - - return this.Connection.Execute( - compiled.Sql, - compiled.NamedBindings, - transaction, - timeout ?? this.QueryTimeout - ); - } - - public async Task ExecuteAsync( - Query query, - IDbTransaction transaction = null, - int? timeout = null, - CancellationToken cancellationToken = default - ) - { - var compiled = CompileAndLog(query); - var commandDefinition = new CommandDefinition( - commandText: compiled.Sql, - parameters: compiled.NamedBindings, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout, - cancellationToken: cancellationToken); - - return await this.Connection.ExecuteAsync(commandDefinition); - } - - public T ExecuteScalar(Query query, IDbTransaction transaction = null, int? timeout = null) - { - var compiled = CompileAndLog(query.Limit(1)); - - return this.Connection.ExecuteScalar( - compiled.Sql, - compiled.NamedBindings, - transaction, - timeout ?? this.QueryTimeout - ); - } - - public async Task ExecuteScalarAsync( - Query query, - IDbTransaction transaction = null, - int? timeout = null, - CancellationToken cancellationToken = default - ) - { - var compiled = CompileAndLog(query.Limit(1)); - var commandDefinition = new CommandDefinition( - commandText: compiled.Sql, - parameters: compiled.NamedBindings, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout, - cancellationToken: cancellationToken); - - return await this.Connection.ExecuteScalarAsync(commandDefinition); - } - - public SqlMapper.GridReader GetMultiple( - Query[] queries, - IDbTransaction transaction = null, - int? timeout = null - ) - { - var compiled = this.Compiler.Compile(queries); - - return this.Connection.QueryMultiple( - compiled.Sql, - compiled.NamedBindings, - transaction, - timeout ?? this.QueryTimeout - ); - } - - public async Task GetMultipleAsync( - Query[] queries, - IDbTransaction transaction = null, - int? timeout = null, - CancellationToken cancellationToken = default) - { - var compiled = this.Compiler.Compile(queries); - var commandDefinition = new CommandDefinition( - commandText: compiled.Sql, - parameters: compiled.NamedBindings, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout, - cancellationToken: cancellationToken); - - return await this.Connection.QueryMultipleAsync(commandDefinition); - } - - public IEnumerable> Get( - Query[] queries, - IDbTransaction transaction = null, - int? timeout = null - ) - { - - var multi = this.GetMultiple( - queries, - transaction, - timeout - ); - - using (multi) - { - for (var i = 0; i < queries.Length; i++) - { - yield return multi.Read(); - } - } - } - - public async Task>> GetAsync( - Query[] queries, - IDbTransaction transaction = null, - int? timeout = null, - CancellationToken cancellationToken = default - ) - { - var multi = await this.GetMultipleAsync( - queries, - transaction, - timeout, - cancellationToken - ); - - var list = new List>(); - - using (multi) - { - for (var i = 0; i < queries.Length; i++) - { - list.Add(multi.Read()); - } - } - - return list; - } - - public bool Exists(Query query, IDbTransaction transaction = null, int? timeout = null) - { - var clone = query.Clone() - .ClearComponent("select") - .SelectRaw("1 as [Exists]") - .Limit(1); - - var rows = Get(clone, transaction, timeout); - - return rows.Any(); - } - - public async Task ExistsAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var clone = query.Clone() - .ClearComponent("select") - .SelectRaw("1 as [Exists]") - .Limit(1); - - var rows = await GetAsync(clone, transaction, timeout, cancellationToken); - - return rows.Any(); - } - - public T Aggregate( - Query query, - string aggregateOperation, - string[] columns = null, - IDbTransaction transaction = null, - int? timeout = null - ) - { - return this.ExecuteScalar(query.AsAggregate(aggregateOperation, columns), transaction, timeout ?? this.QueryTimeout); - } - - public async Task AggregateAsync( - Query query, - string aggregateOperation, - string[] columns = null, - IDbTransaction transaction = null, - int? timeout = null, - CancellationToken cancellationToken = default - ) - { - return await this.ExecuteScalarAsync( - query.AsAggregate(aggregateOperation, columns), - transaction, - timeout, - cancellationToken - ); - } - - public T Count(Query query, string[] columns = null, IDbTransaction transaction = null, int? timeout = null) - { - return this.ExecuteScalar( - query.AsCount(columns), - transaction, - timeout - ); - } - - public async Task CountAsync(Query query, string[] columns = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await this.ExecuteScalarAsync(query.AsCount(columns), transaction, timeout, cancellationToken); - } - - public T Average(Query query, string column, IDbTransaction transaction = null, int? timeout = null) - { - return this.Aggregate(query, "avg", new[] { column }); - } - - public async Task AverageAsync(Query query, string column, CancellationToken cancellationToken = default) - { - return await this.AggregateAsync(query, "avg", new[] { column }, cancellationToken: cancellationToken); - } - - public T Sum(Query query, string column) - { - return this.Aggregate(query, "sum", new[] { column }); - } - - public async Task SumAsync(Query query, string column, CancellationToken cancellationToken = default) - { - return await this.AggregateAsync(query, "sum", new[] { column }, cancellationToken: cancellationToken); - } - - public T Min(Query query, string column) - { - return this.Aggregate(query, "min", new[] { column }); - } - - public async Task MinAsync(Query query, string column, CancellationToken cancellationToken = default) - { - return await this.AggregateAsync(query, "min", new[] { column }, cancellationToken: cancellationToken); - } - - public T Max(Query query, string column) - { - return this.Aggregate(query, "max", new[] { column }); - } - - public async Task MaxAsync(Query query, string column, CancellationToken cancellationToken = default) - { - return await this.AggregateAsync(query, "max", new[] { column }, cancellationToken: cancellationToken); - } - - public PaginationResult Paginate(Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null) - { - if (page < 1) - { - throw new ArgumentException("Page param should be greater than or equal to 1", nameof(page)); - } - - if (perPage < 1) - { - throw new ArgumentException("PerPage param should be greater than or equal to 1", nameof(perPage)); - } - - var count = Count(query.Clone(), null, transaction, timeout); - - IEnumerable list; - - if (count > 0) - { - list = Get(query.Clone().ForPage(page, perPage), transaction, timeout); - } - else - { - list = Enumerable.Empty(); - } - - return new PaginationResult - { - Query = query, - Page = page, - PerPage = perPage, - Count = count, - List = list - }; - } - - public async Task> PaginateAsync(Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - if (page < 1) - { - throw new ArgumentException("Page param should be greater than or equal to 1", nameof(page)); - } - - if (perPage < 1) - { - throw new ArgumentException("PerPage param should be greater than or equal to 1", nameof(perPage)); - } - - var count = await CountAsync(query.Clone(), null, transaction, timeout, cancellationToken); - - IEnumerable list; - - if (count > 0) - { - list = await GetAsync(query.Clone().ForPage(page, perPage), transaction, timeout, cancellationToken); - } - else - { - list = Enumerable.Empty(); - } - - return new PaginationResult - { - Query = query, - Page = page, - PerPage = perPage, - Count = count, - List = list - }; - } - - public void Chunk( - Query query, - int chunkSize, - Func, int, bool> func, - IDbTransaction transaction = null, - int? timeout = null) - { - var result = this.Paginate(query, 1, chunkSize, transaction, timeout); - - if (!func(result.List, 1)) - { - return; - } - - while (result.HasNext) - { - result = result.Next(transaction); - if (!func(result.List, result.Page)) - { - return; - } - } - } - - public async Task ChunkAsync( - Query query, - int chunkSize, - Func, int, bool> func, - IDbTransaction transaction = null, - int? timeout = null, - CancellationToken cancellationToken = default - ) - { - var result = await this.PaginateAsync(query, 1, chunkSize, transaction, cancellationToken: cancellationToken); - - if (!func(result.List, 1)) - { - return; - } - - while (result.HasNext) - { - result = result.Next(transaction); - if (!func(result.List, result.Page)) - { - return; - } - } - } - - public void Chunk(Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null) - { - var result = this.Paginate(query, 1, chunkSize, transaction, timeout); - - action(result.List, 1); - - while (result.HasNext) - { - result = result.Next(transaction); - action(result.List, result.Page); - } - } - - public async Task ChunkAsync( - Query query, - int chunkSize, - Action, int> action, - IDbTransaction transaction = null, - int? timeout = null, - CancellationToken cancellationToken = default - ) - { - var result = await this.PaginateAsync(query, 1, chunkSize, transaction, timeout, cancellationToken); - - action(result.List, 1); - - while (result.HasNext) - { - result = result.Next(transaction); - action(result.List, result.Page); - } - } - - public IEnumerable Select(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null) - { - return this.Connection.Query( - sql, - param, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout - ); - } - - public async Task> SelectAsync(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var commandDefinition = new CommandDefinition( - commandText: sql, - parameters: param, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout, - cancellationToken: cancellationToken); - - return await this.Connection.QueryAsync(commandDefinition); - } - - public IEnumerable Select(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null) - { - return this.Select(sql, param, transaction, timeout); - } - - public async Task> SelectAsync(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - return await this.SelectAsync(sql, param, transaction, timeout, cancellationToken); - } - - public int Statement(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null) - { - return this.Connection.Execute(sql, param, transaction: transaction, commandTimeout: timeout ?? this.QueryTimeout); - } - - public async Task StatementAsync(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) - { - var commandDefinition = new CommandDefinition( - commandText: sql, - parameters: param, - transaction: transaction, - commandTimeout: timeout ?? this.QueryTimeout, - cancellationToken: cancellationToken); - return await this.Connection.ExecuteAsync(commandDefinition); - } - - private static IEnumerable handleIncludes(Query query, IEnumerable result) - { - if (!result.Any()) - { - return result; - } - - var canBeProcessed = query.Includes.Any() && result.ElementAt(0) is IDynamicMetaObjectProvider; - - if (!canBeProcessed) - { - return result; - } - - var dynamicResult = result - .Cast>() - .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) - .ToList(); - - foreach (var include in query.Includes) - { - - if (include.IsMany) - { - if (include.ForeignKey == null) - { - // try to guess the default key - // I will try to fetch the table name if provided and appending the Id as a convention - // Here am using Humanizer package to help getting the singular form of the table - - var fromTable = query.GetOneComponent("from") as FromClause; - - if (fromTable == null) - { - throw new InvalidOperationException($"Cannot guess the foreign key for the included relation '{include.Name}'"); - } - - var table = fromTable.Alias ?? fromTable.Table; - - include.ForeignKey = table.Singularize(false) + "Id"; - } - - var localIds = dynamicResult.Where(x => x[include.LocalKey] != null) - .Select(x => x[include.LocalKey].ToString()) - .ToList(); - - if (!localIds.Any()) - { - continue; - } - - var children = include - .Query - .WhereIn(include.ForeignKey, localIds) - .Get() - .Cast>() - .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) - .GroupBy(x => x[include.ForeignKey].ToString()) - .ToDictionary(x => x.Key, x => x.ToList()); - - foreach (var item in dynamicResult) - { - var localValue = item[include.LocalKey].ToString(); - item[include.Name] = children.ContainsKey(localValue) ? children[localValue] : new List>(); - } - - continue; - } - - if (include.ForeignKey == null) - { - include.ForeignKey = include.Name + "Id"; - } - - var foreignIds = dynamicResult - .Where(x => x[include.ForeignKey] != null) - .Select(x => x[include.ForeignKey].ToString()) - .ToList(); - - if (!foreignIds.Any()) - { - continue; - } - - var related = include - .Query - .WhereIn(include.LocalKey, foreignIds) - .Get() - .Cast>() - .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) - .ToDictionary(x => x[include.LocalKey].ToString()); - - foreach (var item in dynamicResult) - { - var foreignValue = item[include.ForeignKey]?.ToString(); - item[include.Name] = foreignValue != null && related.ContainsKey(foreignValue) ? related[foreignValue] : null; - } - } - - return dynamicResult.Cast(); - } - - private static async Task> handleIncludesAsync(Query query, IEnumerable result, CancellationToken cancellationToken = default) - { - if (!result.Any()) - { - return result; - } - - var canBeProcessed = query.Includes.Any() && result.ElementAt(0) is IDynamicMetaObjectProvider; - - if (!canBeProcessed) - { - return result; - } - - var dynamicResult = result - .Cast>() - .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) - .ToList(); - - foreach (var include in query.Includes) - { - if (include.IsMany) - { - if (include.ForeignKey == null) - { - // try to guess the default key - // I will try to fetch the table name if provided and appending the Id as a convention - // Here am using Humanizer package to help getting the singular form of the table - - var fromTable = query.GetOneComponent("from") as FromClause; - - if (fromTable == null) - { - throw new InvalidOperationException($"Cannot guess the foreign key for the included relation '{include.Name}'"); - } - - var table = fromTable.Alias ?? fromTable.Table; - - include.ForeignKey = table.Singularize(false) + "Id"; - } - - var localIds = dynamicResult.Where(x => x[include.LocalKey] != null) - .Select(x => x[include.LocalKey].ToString()) - .ToList(); - - if (!localIds.Any()) - { - continue; - } - - var children = (await include.Query.WhereIn(include.ForeignKey, localIds).GetAsync(cancellationToken: cancellationToken)) - .Cast>() - .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) - .GroupBy(x => x[include.ForeignKey].ToString()) - .ToDictionary(x => x.Key, x => x.ToList()); - - foreach (var item in dynamicResult) - { - var localValue = item[include.LocalKey].ToString(); - item[include.Name] = children.ContainsKey(localValue) ? children[localValue] : new List>(); - } - - continue; - } - - if (include.ForeignKey == null) - { - include.ForeignKey = include.Name + "Id"; - } - - var foreignIds = dynamicResult.Where(x => x[include.ForeignKey] != null) - .Select(x => x[include.ForeignKey].ToString()) - .ToList(); - - if (!foreignIds.Any()) - { - continue; - } - - var related = (await include.Query.WhereIn(include.LocalKey, foreignIds).GetAsync(cancellationToken: cancellationToken)) - .Cast>() - .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) - .ToDictionary(x => x[include.LocalKey].ToString()); - - foreach (var item in dynamicResult) - { - var foreignValue = item[include.ForeignKey]?.ToString(); - item[include.Name] = foreignValue != null && related.ContainsKey(foreignValue) ? related[foreignValue] : null; - } - } - - return dynamicResult.Cast(); - } - - /// - /// Compile and log query - /// - /// - /// - internal SqlResult CompileAndLog(Query query) - { - var compiled = this.Compiler.Compile(query); - - this.Logger(compiled); - - return compiled; - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - Connection.Dispose(); - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - Connection = null; - Compiler = null; - disposedValue = true; - } - } - - // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~QueryFactory() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} +namespace SqlKata.Execution; + +using System; +using System.Collections.Generic; +using System.Data; +using System.Dynamic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Dapper; +using Humanizer; +using SqlKata.Compilers; + +public class QueryFactory : IDisposable { + public IDbConnection Connection { get; set; } + public Compiler Compiler { get; set; } + public Action Logger = result => { }; + private bool disposedValue; + + public int QueryTimeout { get; set; } = 30; + + public QueryFactory() { } + + public QueryFactory(IDbConnection connection, Compiler compiler, int timeout = 30) { + Connection = connection; + Compiler = compiler; + QueryTimeout = timeout; + } + + public Query Query() { + var query = new XQuery(this.Connection, this.Compiler); + + query.QueryFactory = this; + + query.Logger = Logger; + + return query; + } + + public Query Query(string table) { + return Query().From(table); + } + + /// + /// Create an XQuery instance from a regular Query + /// + /// + /// + public Query FromQuery(Query query) { + var xQuery = new XQuery(this.Connection, this.Compiler); + + xQuery.QueryFactory = this; + + xQuery.Clauses = query.Clauses.Select(x => x.Clone()).ToList(); + + xQuery.SetParent(query.Parent); + xQuery.QueryAlias = query.QueryAlias; + xQuery.IsDistinct = query.IsDistinct; + xQuery.Method = query.Method; + xQuery.Includes = query.Includes; + xQuery.Variables = query.Variables; + + xQuery.SetEngineScope(query.EngineScope); + + xQuery.Logger = Logger; + + return xQuery; + } + + public IEnumerable Get(Query query, IDbTransaction transaction = null, int? timeout = null) { + var compiled = CompileAndLog(query); + + var result = this.Connection.Query( + compiled.Sql, + compiled.NamedBindings, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout + ).ToList(); + + result = handleIncludes(query, result).ToList(); + + return result; + } + + public async Task> GetAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var compiled = CompileAndLog(query); + var commandDefinition = new CommandDefinition( + commandText: compiled.Sql, + parameters: compiled.NamedBindings, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout, + cancellationToken: cancellationToken); + + var result = (await this.Connection.QueryAsync(commandDefinition)).ToList(); + + result = (await handleIncludesAsync(query, result, cancellationToken)).ToList(); + + return result; + } + + public IEnumerable> GetDictionary(Query query, IDbTransaction transaction = null, int? timeout = null) { + var compiled = CompileAndLog(query); + + var result = this.Connection.Query( + compiled.Sql, + compiled.NamedBindings, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout + ); + + return result.Cast>(); + } + + public async Task>> GetDictionaryAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var compiled = CompileAndLog(query); + var commandDefinition = new CommandDefinition( + commandText: compiled.Sql, + parameters: compiled.NamedBindings, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout, + cancellationToken: cancellationToken); + + var result = await this.Connection.QueryAsync(commandDefinition); + + return result.Cast>(); + } + + public IEnumerable Get(Query query, IDbTransaction transaction = null, int? timeout = null) { + return Get(query, transaction, timeout); + } + + public async Task> GetAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await GetAsync(query, transaction, timeout, cancellationToken); + } + + public T FirstOrDefault(Query query, IDbTransaction transaction = null, int? timeout = null) { + var list = Get(query.Limit(1), transaction, timeout); + + return list.ElementAtOrDefault(0); + } + + public async Task FirstOrDefaultAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var list = await GetAsync(query.Limit(1), transaction, timeout, cancellationToken); + + return list.ElementAtOrDefault(0); + } + + public dynamic FirstOrDefault(Query query, IDbTransaction transaction = null, int? timeout = null) { + return FirstOrDefault(query, transaction, timeout); + } + + public async Task FirstOrDefaultAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await FirstOrDefaultAsync(query, transaction, timeout, cancellationToken); + } + + public T First(Query query, IDbTransaction transaction = null, int? timeout = null) { + var item = FirstOrDefault(query, transaction, timeout); + + if (item == null) { + throw new InvalidOperationException("The sequence contains no elements"); + } + + return item; + } + + public async Task FirstAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var item = await FirstOrDefaultAsync(query, transaction, timeout, cancellationToken); + + if (item == null) { + throw new InvalidOperationException("The sequence contains no elements"); + } + + return item; + } + + public dynamic First(Query query, IDbTransaction transaction = null, int? timeout = null) { + return First(query, transaction, timeout); + } + + public async Task FirstAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await FirstAsync(query, transaction, timeout, cancellationToken); + } + + public int Execute( + Query query, + IDbTransaction transaction = null, + int? timeout = null + ) { + var compiled = CompileAndLog(query); + + return this.Connection.Execute( + compiled.Sql, + compiled.NamedBindings, + transaction, + timeout ?? this.QueryTimeout + ); + } + + public async Task ExecuteAsync( + Query query, + IDbTransaction transaction = null, + int? timeout = null, + CancellationToken cancellationToken = default + ) { + var compiled = CompileAndLog(query); + var commandDefinition = new CommandDefinition( + commandText: compiled.Sql, + parameters: compiled.NamedBindings, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout, + cancellationToken: cancellationToken); + + return await this.Connection.ExecuteAsync(commandDefinition); + } + + public T ExecuteScalar(Query query, IDbTransaction transaction = null, int? timeout = null) { + var compiled = CompileAndLog(query.Limit(1)); + + return this.Connection.ExecuteScalar( + compiled.Sql, + compiled.NamedBindings, + transaction, + timeout ?? this.QueryTimeout + ); + } + + public async Task ExecuteScalarAsync( + Query query, + IDbTransaction transaction = null, + int? timeout = null, + CancellationToken cancellationToken = default + ) { + var compiled = CompileAndLog(query.Limit(1)); + var commandDefinition = new CommandDefinition( + commandText: compiled.Sql, + parameters: compiled.NamedBindings, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout, + cancellationToken: cancellationToken); + + return await this.Connection.ExecuteScalarAsync(commandDefinition); + } + + public SqlMapper.GridReader GetMultiple( + Query[] queries, + IDbTransaction transaction = null, + int? timeout = null + ) { + var compiled = this.Compiler.Compile(queries); + + return this.Connection.QueryMultiple( + compiled.Sql, + compiled.NamedBindings, + transaction, + timeout ?? this.QueryTimeout + ); + } + + public async Task GetMultipleAsync( + Query[] queries, + IDbTransaction transaction = null, + int? timeout = null, + CancellationToken cancellationToken = default) { + var compiled = this.Compiler.Compile(queries); + var commandDefinition = new CommandDefinition( + commandText: compiled.Sql, + parameters: compiled.NamedBindings, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout, + cancellationToken: cancellationToken); + + return await this.Connection.QueryMultipleAsync(commandDefinition); + } + + public IEnumerable> Get( + Query[] queries, + IDbTransaction transaction = null, + int? timeout = null + ) { + + var multi = this.GetMultiple( + queries, + transaction, + timeout + ); + + using (multi) { + for (var i = 0; i < queries.Length; i++) { + yield return multi.Read(); + } + } + } + + public async Task>> GetAsync( + Query[] queries, + IDbTransaction transaction = null, + int? timeout = null, + CancellationToken cancellationToken = default + ) { + var multi = await this.GetMultipleAsync( + queries, + transaction, + timeout, + cancellationToken + ); + + var list = new List>(); + + using (multi) { + for (var i = 0; i < queries.Length; i++) { + list.Add(multi.Read()); + } + } + + return list; + } + + public bool Exists(Query query, IDbTransaction transaction = null, int? timeout = null) { + var clone = query.Clone() + .ClearComponent("select") + .SelectRaw("1 as [Exists]") + .Limit(1); + + var rows = Get(clone, transaction, timeout); + + return rows.Any(); + } + + public async Task ExistsAsync(Query query, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var clone = query.Clone() + .ClearComponent("select") + .SelectRaw("1 as [Exists]") + .Limit(1); + + var rows = await GetAsync(clone, transaction, timeout, cancellationToken); + + return rows.Any(); + } + + public T Aggregate( + Query query, + string aggregateOperation, + string[] columns = null, + IDbTransaction transaction = null, + int? timeout = null + ) { + return this.ExecuteScalar(query.AsAggregate(aggregateOperation, columns), transaction, timeout ?? this.QueryTimeout); + } + + public async Task AggregateAsync( + Query query, + string aggregateOperation, + string[] columns = null, + IDbTransaction transaction = null, + int? timeout = null, + CancellationToken cancellationToken = default + ) { + return await this.ExecuteScalarAsync( + query.AsAggregate(aggregateOperation, columns), + transaction, + timeout, + cancellationToken + ); + } + + public T Count(Query query, string[] columns = null, IDbTransaction transaction = null, int? timeout = null) { + return this.ExecuteScalar( + query.AsCount(columns), + transaction, + timeout + ); + } + + public async Task CountAsync(Query query, string[] columns = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await this.ExecuteScalarAsync(query.AsCount(columns), transaction, timeout, cancellationToken); + } + + public T Average(Query query, string column, IDbTransaction transaction = null, int? timeout = null) { + return this.Aggregate(query, "avg", new[] { column }); + } + + public async Task AverageAsync(Query query, string column, CancellationToken cancellationToken = default) { + return await this.AggregateAsync(query, "avg", new[] { column }, cancellationToken: cancellationToken); + } + + public T Sum(Query query, string column) { + return this.Aggregate(query, "sum", new[] { column }); + } + + public async Task SumAsync(Query query, string column, CancellationToken cancellationToken = default) { + return await this.AggregateAsync(query, "sum", new[] { column }, cancellationToken: cancellationToken); + } + + public T Min(Query query, string column) { + return this.Aggregate(query, "min", new[] { column }); + } + + public async Task MinAsync(Query query, string column, CancellationToken cancellationToken = default) { + return await this.AggregateAsync(query, "min", new[] { column }, cancellationToken: cancellationToken); + } + + public T Max(Query query, string column) { + return this.Aggregate(query, "max", new[] { column }); + } + + public async Task MaxAsync(Query query, string column, CancellationToken cancellationToken = default) { + return await this.AggregateAsync(query, "max", new[] { column }, cancellationToken: cancellationToken); + } + + public PaginationResult Paginate(Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null) { + if (page < 1) { + throw new ArgumentException("Page param should be greater than or equal to 1", nameof(page)); + } + + if (perPage < 1) { + throw new ArgumentException("PerPage param should be greater than or equal to 1", nameof(perPage)); + } + + var count = Count(query.Clone(), null, transaction, timeout); + + IEnumerable list; + + if (count > 0) { + list = Get(query.Clone().ForPage(page, perPage), transaction, timeout); + } + else { + list = Enumerable.Empty(); + } + + return new PaginationResult { + Query = query, + Page = page, + PerPage = perPage, + Count = count, + List = list + }; + } + + public async Task> PaginateAsync(Query query, int page, int perPage = 25, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + if (page < 1) { + throw new ArgumentException("Page param should be greater than or equal to 1", nameof(page)); + } + + if (perPage < 1) { + throw new ArgumentException("PerPage param should be greater than or equal to 1", nameof(perPage)); + } + + var count = await CountAsync(query.Clone(), null, transaction, timeout, cancellationToken); + + IEnumerable list; + + if (count > 0) { + list = await GetAsync(query.Clone().ForPage(page, perPage), transaction, timeout, cancellationToken); + } + else { + list = Enumerable.Empty(); + } + + return new PaginationResult { + Query = query, + Page = page, + PerPage = perPage, + Count = count, + List = list + }; + } + + public void Chunk( + Query query, + int chunkSize, + Func, int, bool> func, + IDbTransaction transaction = null, + int? timeout = null) { + var result = this.Paginate(query, 1, chunkSize, transaction, timeout); + + if (!func(result.List, 1)) { + return; + } + + while (result.HasNext) { + result = result.Next(transaction); + if (!func(result.List, result.Page)) { + return; + } + } + } + + public async Task ChunkAsync( + Query query, + int chunkSize, + Func, int, bool> func, + IDbTransaction transaction = null, + int? timeout = null, + CancellationToken cancellationToken = default + ) { + var result = await this.PaginateAsync(query, 1, chunkSize, transaction, cancellationToken: cancellationToken); + + if (!func(result.List, 1)) { + return; + } + + while (result.HasNext) { + result = result.Next(transaction); + if (!func(result.List, result.Page)) { + return; + } + } + } + + public void Chunk(Query query, int chunkSize, Action, int> action, IDbTransaction transaction = null, int? timeout = null) { + var result = this.Paginate(query, 1, chunkSize, transaction, timeout); + + action(result.List, 1); + + while (result.HasNext) { + result = result.Next(transaction); + action(result.List, result.Page); + } + } + + public async Task ChunkAsync( + Query query, + int chunkSize, + Action, int> action, + IDbTransaction transaction = null, + int? timeout = null, + CancellationToken cancellationToken = default + ) { + var result = await this.PaginateAsync(query, 1, chunkSize, transaction, timeout, cancellationToken); + + action(result.List, 1); + + while (result.HasNext) { + result = result.Next(transaction); + action(result.List, result.Page); + } + } + + public IEnumerable Select(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null) { + return this.Connection.Query( + sql, + param, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout + ); + } + + public async Task> SelectAsync(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var commandDefinition = new CommandDefinition( + commandText: sql, + parameters: param, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout, + cancellationToken: cancellationToken); + + return await this.Connection.QueryAsync(commandDefinition); + } + + public IEnumerable Select(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null) { + return this.Select(sql, param, transaction, timeout); + } + + public async Task> SelectAsync(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + return await this.SelectAsync(sql, param, transaction, timeout, cancellationToken); + } + + public int Statement(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null) { + return this.Connection.Execute(sql, param, transaction: transaction, commandTimeout: timeout ?? this.QueryTimeout); + } + + public async Task StatementAsync(string sql, object param = null, IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { + var commandDefinition = new CommandDefinition( + commandText: sql, + parameters: param, + transaction: transaction, + commandTimeout: timeout ?? this.QueryTimeout, + cancellationToken: cancellationToken); + return await this.Connection.ExecuteAsync(commandDefinition); + } + + private static IEnumerable handleIncludes(Query query, IEnumerable result) { + if (!result.Any()) { + return result; + } + + var canBeProcessed = query.Includes.Any() && result.ElementAt(0) is IDynamicMetaObjectProvider; + + if (!canBeProcessed) { + return result; + } + + var dynamicResult = result + .Cast>() + .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) + .ToList(); + + foreach (var include in query.Includes) { + + if (include.IsMany) { + if (include.ForeignKey == null) { + // try to guess the default key + // I will try to fetch the table name if provided and appending the Id as a convention + // Here am using Humanizer package to help getting the singular form of the table + + var fromTable = query.GetOneComponent("from") as FromClause; + + if (fromTable == null) { + throw new InvalidOperationException($"Cannot guess the foreign key for the included relation '{include.Name}'"); + } + + var table = fromTable.Alias ?? fromTable.Table; + + include.ForeignKey = table.Singularize(false) + "Id"; + } + + var localIds = dynamicResult.Where(x => x[include.LocalKey] != null) + .Select(x => x[include.LocalKey].ToString()) + .ToList(); + + if (!localIds.Any()) { + continue; + } + + var children = include + .Query + .WhereIn(include.ForeignKey, localIds) + .Get() + .Cast>() + .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) + .GroupBy(x => x[include.ForeignKey].ToString()) + .ToDictionary(x => x.Key, x => x.ToList()); + + foreach (var item in dynamicResult) { + var localValue = item[include.LocalKey].ToString(); + item[include.Name] = children.ContainsKey(localValue) ? children[localValue] : new List>(); + } + + continue; + } + + if (include.ForeignKey == null) { + include.ForeignKey = include.Name + "Id"; + } + + var foreignIds = dynamicResult + .Where(x => x[include.ForeignKey] != null) + .Select(x => x[include.ForeignKey].ToString()) + .ToList(); + + if (!foreignIds.Any()) { + continue; + } + + var related = include + .Query + .WhereIn(include.LocalKey, foreignIds) + .Get() + .Cast>() + .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) + .ToDictionary(x => x[include.LocalKey].ToString()); + + foreach (var item in dynamicResult) { + var foreignValue = item[include.ForeignKey]?.ToString(); + item[include.Name] = foreignValue != null && related.ContainsKey(foreignValue) ? related[foreignValue] : null; + } + } + + return dynamicResult.Cast(); + } + + private static async Task> handleIncludesAsync(Query query, IEnumerable result, CancellationToken cancellationToken = default) { + if (!result.Any()) { + return result; + } + + var canBeProcessed = query.Includes.Any() && result.ElementAt(0) is IDynamicMetaObjectProvider; + + if (!canBeProcessed) { + return result; + } + + var dynamicResult = result + .Cast>() + .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) + .ToList(); + + foreach (var include in query.Includes) { + if (include.IsMany) { + if (include.ForeignKey == null) { + // try to guess the default key + // I will try to fetch the table name if provided and appending the Id as a convention + // Here am using Humanizer package to help getting the singular form of the table + + var fromTable = query.GetOneComponent("from") as FromClause; + + if (fromTable == null) { + throw new InvalidOperationException($"Cannot guess the foreign key for the included relation '{include.Name}'"); + } + + var table = fromTable.Alias ?? fromTable.Table; + + include.ForeignKey = table.Singularize(false) + "Id"; + } + + var localIds = dynamicResult.Where(x => x[include.LocalKey] != null) + .Select(x => x[include.LocalKey].ToString()) + .ToList(); + + if (!localIds.Any()) { + continue; + } + + var children = (await include.Query.WhereIn(include.ForeignKey, localIds).GetAsync(cancellationToken: cancellationToken)) + .Cast>() + .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) + .GroupBy(x => x[include.ForeignKey].ToString()) + .ToDictionary(x => x.Key, x => x.ToList()); + + foreach (var item in dynamicResult) { + var localValue = item[include.LocalKey].ToString(); + item[include.Name] = children.ContainsKey(localValue) ? children[localValue] : new List>(); + } + + continue; + } + + if (include.ForeignKey == null) { + include.ForeignKey = include.Name + "Id"; + } + + var foreignIds = dynamicResult.Where(x => x[include.ForeignKey] != null) + .Select(x => x[include.ForeignKey].ToString()) + .ToList(); + + if (!foreignIds.Any()) { + continue; + } + + var related = (await include.Query.WhereIn(include.LocalKey, foreignIds).GetAsync(cancellationToken: cancellationToken)) + .Cast>() + .Select(x => new Dictionary(x, StringComparer.OrdinalIgnoreCase)) + .ToDictionary(x => x[include.LocalKey].ToString()); + + foreach (var item in dynamicResult) { + var foreignValue = item[include.ForeignKey]?.ToString(); + item[include.Name] = foreignValue != null && related.ContainsKey(foreignValue) ? related[foreignValue] : null; + } + } + + return dynamicResult.Cast(); + } + + /// + /// Compile and log query + /// + /// + /// + internal SqlResult CompileAndLog(Query query) { + var compiled = this.Compiler.Compile(query); + + this.Logger(compiled); + + return compiled; + } + + protected virtual void Dispose(bool disposing) { + if (!disposedValue) { + if (disposing) { + Connection.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + Connection = null; + Compiler = null; + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~QueryFactory() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/SqlKata.Execution/XQuery.cs b/SqlKata.Execution/XQuery.cs index 3cb5f14f..fcc49ad1 100644 --- a/SqlKata.Execution/XQuery.cs +++ b/SqlKata.Execution/XQuery.cs @@ -1,48 +1,42 @@ -using System; -using System.Data; -using System.Linq; -using SqlKata.Compilers; - -namespace SqlKata.Execution -{ - public class XQuery : Query - { - public IDbConnection Connection { get; set; } - public Compiler Compiler { get; set; } - public Action Logger = result => { }; - public QueryFactory QueryFactory { get; set; } - - public XQuery(IDbConnection connection, Compiler compiler) - { - this.QueryFactory = new QueryFactory(connection, compiler); - this.Connection = connection; - this.Compiler = compiler; - } - - public override Query Clone() - { - - var query = new XQuery(this.QueryFactory.Connection, this.QueryFactory.Compiler); - - if (this.QueryFactory?.QueryTimeout != null) - { - query.QueryFactory.QueryTimeout = this.QueryFactory?.QueryTimeout ?? 30; - } - - query.Clauses = this.Clauses.Select(x => x.Clone()).ToList(); - query.Logger = this.Logger; - - query.QueryAlias = QueryAlias; - query.IsDistinct = IsDistinct; - query.Method = Method; - query.Includes = Includes; - query.Variables = Variables; - - query.SetEngineScope(EngineScope); - - return query; - } - - } - -} +namespace SqlKata.Execution; + +using System; +using System.Data; +using System.Linq; +using SqlKata.Compilers; + +public class XQuery : Query { + public IDbConnection Connection { get; set; } + public Compiler Compiler { get; set; } + public Action Logger = result => { }; + public QueryFactory QueryFactory { get; set; } + + public XQuery(IDbConnection connection, Compiler compiler) { + this.QueryFactory = new QueryFactory(connection, compiler); + this.Connection = connection; + this.Compiler = compiler; + } + + public override Query Clone() { + + var query = new XQuery(this.QueryFactory.Connection, this.QueryFactory.Compiler); + + if (this.QueryFactory?.QueryTimeout != null) { + query.QueryFactory.QueryTimeout = this.QueryFactory?.QueryTimeout ?? 30; + } + + query.Clauses = this.Clauses.Select(x => x.Clone()).ToList(); + query.Logger = this.Logger; + + query.QueryAlias = QueryAlias; + query.IsDistinct = IsDistinct; + query.Method = Method; + query.Includes = Includes; + query.Variables = Variables; + + query.SetEngineScope(EngineScope); + + return query; + } + +} From 79529ee7578755ed4e12d6bd6dc6f65ffe758503 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Sun, 28 Sep 2025 00:20:55 +0300 Subject: [PATCH 2/2] list initializer --- QueryBuilder.Tests/ParameterTypeTests.cs | 6 +++--- QueryBuilder/BaseQuery.cs | 2 +- QueryBuilder/Compilers/Compiler.cs | 12 +++++------- QueryBuilder/Compilers/ConditionsCompilerProvider.cs | 2 +- QueryBuilder/Compilers/CteFinder.cs | 2 +- QueryBuilder/Helper.cs | 2 +- QueryBuilder/Query.Aggregate.cs | 4 ++-- QueryBuilder/Query.cs | 6 +++--- QueryBuilder/SqlResult.cs | 4 ++-- SqlKata.Execution/QueryFactory.cs | 4 ++-- 10 files changed, 21 insertions(+), 23 deletions(-) diff --git a/QueryBuilder.Tests/ParameterTypeTests.cs b/QueryBuilder.Tests/ParameterTypeTests.cs index 7088febe..8c26cc31 100644 --- a/QueryBuilder.Tests/ParameterTypeTests.cs +++ b/QueryBuilder.Tests/ParameterTypeTests.cs @@ -16,8 +16,8 @@ public enum EnumExample { } public class ParameterTypeGenerator : IEnumerable { - private readonly List _data = new List - { + private readonly List _data = + [ new object[] {"1", 1}, new object[] {Convert.ToSingle("10.5", CultureInfo.InvariantCulture).ToString(), 10.5}, new object[] {"-2", -2}, @@ -28,7 +28,7 @@ public class ParameterTypeGenerator : IEnumerable { new object[] {"0 /* First */", EnumExample.First}, new object[] {"1 /* Second */", EnumExample.Second}, new object[] {"'a string'", "a string"}, - }; + ]; public IEnumerator GetEnumerator() => _data.GetEnumerator(); diff --git a/QueryBuilder/BaseQuery.cs b/QueryBuilder/BaseQuery.cs index f147bb92..0cb454fe 100644 --- a/QueryBuilder/BaseQuery.cs +++ b/QueryBuilder/BaseQuery.cs @@ -9,7 +9,7 @@ public abstract class AbstractQuery { } public abstract partial class BaseQuery : AbstractQuery where Q : BaseQuery { - public List Clauses { get; set; } = new List(); + public List Clauses { get; set; } = []; private bool orFlag = false; private bool notFlag = false; diff --git a/QueryBuilder/Compilers/Compiler.cs b/QueryBuilder/Compilers/Compiler.cs index 205f988b..b4ef154c 100644 --- a/QueryBuilder/Compilers/Compiler.cs +++ b/QueryBuilder/Compilers/Compiler.cs @@ -45,8 +45,8 @@ protected Compiler() { /// A list of white-listed operators /// /// - protected readonly HashSet operators = new HashSet - { + protected readonly HashSet operators = + [ "=", "<", ">", "<=", ">=", "<>", "!=", "<=>", "like", "not like", "ilike", "not ilike", @@ -54,11 +54,9 @@ protected Compiler() { "rlike", "not rlike", "regexp", "not regexp", "similar to", "not similar to" - }; + ]; - protected HashSet userOperators = new HashSet { - - }; + protected HashSet userOperators = []; protected Dictionary generateNamedBindings(object[] bindings) { return Helper.Flatten(bindings).Select((v, i) => new { i, v }) @@ -89,7 +87,7 @@ private Query TransformAggregateQuery(Query query) { } var outerClause = new AggregateClause() { - Columns = new List { "*" }, + Columns = ["*"], Type = clause.Type }; diff --git a/QueryBuilder/Compilers/ConditionsCompilerProvider.cs b/QueryBuilder/Compilers/ConditionsCompilerProvider.cs index bef95fdc..cb50f614 100644 --- a/QueryBuilder/Compilers/ConditionsCompilerProvider.cs +++ b/QueryBuilder/Compilers/ConditionsCompilerProvider.cs @@ -7,7 +7,7 @@ namespace SqlKata.Compilers; internal class ConditionsCompilerProvider { private readonly Type compilerType; - private readonly Dictionary methodsCache = new Dictionary(); + private readonly Dictionary methodsCache = []; private readonly object syncRoot = new object(); public ConditionsCompilerProvider(Compiler compiler) { diff --git a/QueryBuilder/Compilers/CteFinder.cs b/QueryBuilder/Compilers/CteFinder.cs index 41d580f0..c89d61ea 100644 --- a/QueryBuilder/Compilers/CteFinder.cs +++ b/QueryBuilder/Compilers/CteFinder.cs @@ -17,7 +17,7 @@ public List Find() { if (null != orderedCteList) return orderedCteList; - namesOfPreviousCtes = new HashSet(); + namesOfPreviousCtes = []; orderedCteList = findInternal(query); diff --git a/QueryBuilder/Helper.cs b/QueryBuilder/Helper.cs index 997d381a..857bab9c 100644 --- a/QueryBuilder/Helper.cs +++ b/QueryBuilder/Helper.cs @@ -119,7 +119,7 @@ public static List ExpandExpression(string expression) { if (!match.Success) { // we did not found a match return the string as is. - return new List { expression }; + return [expression]; } var table = expression.Substring(0, expression.IndexOf(".{")); diff --git a/QueryBuilder/Query.Aggregate.cs b/QueryBuilder/Query.Aggregate.cs index 6785b9a6..272b1e6a 100644 --- a/QueryBuilder/Query.Aggregate.cs +++ b/QueryBuilder/Query.Aggregate.cs @@ -11,14 +11,14 @@ public Query AsAggregate(string type, string[] columns = null) { this.ClearComponent("aggregate") .AddComponent("aggregate", new AggregateClause { Type = type, - Columns = columns?.ToList() ?? new List(), + Columns = columns?.ToList() ?? [], }); return this; } public Query AsCount(string[] columns = null) { - var cols = columns?.ToList() ?? new List { }; + var cols = columns?.ToList() ?? []; if (!cols.Any()) { cols.Add("*"); diff --git a/QueryBuilder/Query.cs b/QueryBuilder/Query.cs index a7012d10..37517820 100755 --- a/QueryBuilder/Query.cs +++ b/QueryBuilder/Query.cs @@ -12,8 +12,8 @@ public partial class Query : BaseQuery { public bool IsDistinct { get; set; } = false; public string QueryAlias { get; set; } public string Method { get; set; } = "select"; - public List Includes = new List(); - public Dictionary Variables = new Dictionary(); + public List Includes = []; + public Dictionary Variables = []; public Query() : base() { } @@ -125,7 +125,7 @@ public Query With(string alias, IEnumerable columns, IEnumerable(), + Values = [], }; foreach (var values in valuesCollectionList) { diff --git a/QueryBuilder/SqlResult.cs b/QueryBuilder/SqlResult.cs index 5b78cbca..0e6e336e 100644 --- a/QueryBuilder/SqlResult.cs +++ b/QueryBuilder/SqlResult.cs @@ -15,9 +15,9 @@ public SqlResult(string parameterPlaceholder, string escapeCharacter) { } public Query Query { get; set; } public string RawSql { get; set; } = ""; - public List Bindings { get; set; } = new List(); + public List Bindings { get; set; } = []; public string Sql { get; set; } = ""; - public Dictionary NamedBindings = new Dictionary(); + public Dictionary NamedBindings = []; private static readonly Type[] NumberTypes = { diff --git a/SqlKata.Execution/QueryFactory.cs b/SqlKata.Execution/QueryFactory.cs index a007b776..6bca292a 100644 --- a/SqlKata.Execution/QueryFactory.cs +++ b/SqlKata.Execution/QueryFactory.cs @@ -632,7 +632,7 @@ private static IEnumerable handleIncludes(Query query, IEnumerable resu foreach (var item in dynamicResult) { var localValue = item[include.LocalKey].ToString(); - item[include.Name] = children.ContainsKey(localValue) ? children[localValue] : new List>(); + item[include.Name] = children.ContainsKey(localValue) ? children[localValue] : []; } continue; @@ -718,7 +718,7 @@ private static async Task> handleIncludesAsync(Query query, IE foreach (var item in dynamicResult) { var localValue = item[include.LocalKey].ToString(); - item[include.Name] = children.ContainsKey(localValue) ? children[localValue] : new List>(); + item[include.Name] = children.ContainsKey(localValue) ? children[localValue] : []; } continue;