diff --git a/QueryBuilder.Tests/GeneralTests.cs b/QueryBuilder.Tests/GeneralTests.cs index ae20a469..5496f063 100644 --- a/QueryBuilder.Tests/GeneralTests.cs +++ b/QueryBuilder.Tests/GeneralTests.cs @@ -57,37 +57,7 @@ public void InnerScopeEngineWithinSubQuery() 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() @@ -229,7 +199,7 @@ public void OneFromPerEngine() var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; var c = Compilers.Compile(engines, query); - Assert.Equal(2, query.Clauses.OfType().Count()); + Assert.Equal(2, query.FromClauses.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); @@ -244,25 +214,25 @@ 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(); + query.For(engine, q => q.From(table)); + query.For(engine, q => q.From("updated")); + var froms = query.FromClauses.ForEngine(engine); Assert.Single(froms); - Assert.Equal("updated", froms.Single().Table); + Assert.Equal("updated", (froms.Single() as FromClause).Table); } [Theory] [InlineData(null, "generic")] [InlineData(EngineCodes.SqlServer, "mssql")] [InlineData(EngineCodes.MySql, "generic")] - public void GetOneComponent_Prefers_Engine(string engine, string column) + public void ForEngineOrDefault_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; + var where = query.Wheres.Clauses.ForEngineOrDefault(engine) as BasicCondition; Assert.NotNull(where); Assert.Equal(column, where.Column); @@ -271,14 +241,58 @@ public void GetOneComponent_Prefers_Engine(string engine, string column) [Fact] public void AddOrReplace_Throws_MoreThanOne() { - var query = new Query() - .Where("a", "b") - .Where("c", "d"); + var query = new Query(); + query.FromClauses.Add(new FromClause { Table = "a" }); + query.FromClauses.Add(new FromClause { Table = "b" }); - Action act = () => query.AddOrReplaceComponent("where", new BasicCondition()); + Action act = () => query.From("c"); Assert.Throws(act); } + [Fact] + public void DedicatedFromAndSelectClauses() + { + var query = new Query("users") + .Select("id", "name") + .ForSqlServer(q => q.From("mssql_users").Select("created_at")); + + Assert.Equal(2, query.FromClauses.Count); + Assert.Equal(3, query.SelectClauses.Count); + } + + [Fact] + public void DedicatedExplicitClauseProperties() + { + var query = new Query("users") + .With("active_users", new Query("users").Where("active", true)) + .Select("id", "name") + .OrderBy("name") + .GroupBy("id", "name") + .Limit(10) + .Offset(20) + .Union(new Query("archived_users")); + + Assert.Single(query.CteClauses); + Assert.Equal(2, query.SelectClauses.Count); + Assert.Single(query.OrderByClauses); + Assert.Equal(2, query.GroupByClauses.Count); + Assert.Single(query.LimitClauses); + Assert.Equal(10, query.LimitClauses[0].Limit); + Assert.Single(query.OffsetClauses); + Assert.Equal(20, query.OffsetClauses[0].Offset); + Assert.Single(query.CombineClauses); + + var cloned = query.Clone(); + Assert.NotSame(query.CteClauses, cloned.CteClauses); + Assert.NotSame(query.OrderByClauses, cloned.OrderByClauses); + Assert.NotSame(query.GroupByClauses, cloned.GroupByClauses); + Assert.NotSame(query.LimitClauses, cloned.LimitClauses); + Assert.NotSame(query.OffsetClauses, cloned.OffsetClauses); + Assert.NotSame(query.CombineClauses, cloned.CombineClauses); + Assert.Single(cloned.CteClauses); + Assert.Equal(10, cloned.LimitClauses[0].Limit); + } + [Fact] public void OneLimitPerEngine() { @@ -286,7 +300,7 @@ public void OneLimitPerEngine() .ForSqlServer(q => q.Limit(5)) .ForSqlServer(q => q.Limit(10)); - var limits = query.GetComponents("limit", EngineCodes.SqlServer); + var limits = query.LimitClauses.ForEngine(EngineCodes.SqlServer).ToList(); Assert.Single(limits); Assert.Equal(10, limits.Single().Limit); } @@ -301,7 +315,7 @@ public void CompilerSpecificLimit() var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; var c = Compilers.Compile(engines, query); - Assert.Equal(2, query.GetComponents("limit").Count); + Assert.Equal(2, query.LimitClauses.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()); @@ -314,9 +328,9 @@ public void OneOffsetPerEngine() .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); + var offsets = query.OffsetClauses.ForEngine(EngineCodes.SqlServer).ToList(); + Assert.Single(offsets); + Assert.Equal(10, offsets.Single().Offset); } [Fact] @@ -329,7 +343,7 @@ public void CompilerSpecificOffset() var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; var c = Compilers.Compile(engines, query); - Assert.Equal(2, query.GetComponents("offset").Count); + Assert.Equal(2, query.OffsetClauses.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()); @@ -666,5 +680,79 @@ public void Clone_ShouldPreserveAllProperties() Assert.Equal(query.Includes[0].Name, clone.Includes[0].Name); Assert.Equal(query.Variables["myvar"], clone.Variables["myvar"]); } + + [Fact] + public void Compile_Aggregate_DoesNotMutateOriginalQuery() + { + var query = new Query("users") + .OrderBy("id") + .Limit(10) + .GroupBy("category") + .AsCount(); + + var compiler = new PostgresCompiler(); + var res1 = compiler.Compile(query); + var res2 = compiler.Compile(query); + + Assert.Single(query.OrderByClauses); + Assert.Single(query.LimitClauses); + Assert.Single(query.GroupByClauses); + Assert.Equal(res1.RawSql, res2.RawSql); + } + + [Fact] + public void Compile_DistinctAggregate_DoesNotMutateOriginalQuery() + { + var query = new Query("users") + .Distinct() + .AsAvg("score"); + + var compiler = new PostgresCompiler(); + var res1 = compiler.Compile(query); + var res2 = compiler.Compile(query); + + Assert.True(query.IsDistinct); + Assert.Single(query.AggregateClauses); + Assert.Empty(query.SelectClauses); + Assert.Equal(res1.RawSql, res2.RawSql); + } + + [Fact] + public void Compile_SqlServer_Top_DoesNotMutateOriginalQuery() + { + var query = new Query("users").Limit(5); + var compiler = new SqlServerCompiler { UseLegacyPagination = true }; + + var res1 = compiler.Compile(query); + var res2 = compiler.Compile(query); + + Assert.Single(query.LimitClauses); + Assert.Equal(res1.RawSql, res2.RawSql); + } + + [Fact] + public void Compile_Firebird_Limit_DoesNotMutateOriginalQuery() + { + var query = new Query("users").Limit(5); + var compiler = new FirebirdCompiler(); + + var res1 = compiler.Compile(query); + var res2 = compiler.Compile(query); + + Assert.Single(query.LimitClauses); + Assert.Equal(res1.RawSql, res2.RawSql); + } + + [Fact] + public void Compile_WhereExists_DoesNotMutateSubquery() + { + var inner = new Query("orders").Select("id", "total"); + var outer = new Query("users").WhereExists(inner); + + var compiler = new PostgresCompiler(); + compiler.Compile(outer); + + Assert.Equal(2, inner.SelectClauses.Count); + } } } diff --git a/QueryBuilder.Tests/Infrastructure/TestCompiler.cs b/QueryBuilder.Tests/Infrastructure/TestCompiler.cs index aded9fd1..efc11d96 100644 --- a/QueryBuilder.Tests/Infrastructure/TestCompiler.cs +++ b/QueryBuilder.Tests/Infrastructure/TestCompiler.cs @@ -1,5 +1,3 @@ -using System; -using System.Reflection; using SqlKata.Compilers; namespace SqlKata.Tests.Infrastructure @@ -10,43 +8,22 @@ namespace SqlKata.Tests.Infrastructure 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 @@ -55,4 +32,3 @@ class TestEmptyIdentifiersCompiler : TestCompiler protected override string ClosingIdentifier { get; set; } = ""; } } - diff --git a/QueryBuilder.Tests/OperatorWhitelistTests.cs b/QueryBuilder.Tests/OperatorWhitelistTests.cs index 55c3aafc..6e6303be 100644 --- a/QueryBuilder.Tests/OperatorWhitelistTests.cs +++ b/QueryBuilder.Tests/OperatorWhitelistTests.cs @@ -20,7 +20,7 @@ public void DenyInvalidOperatorsInWhere(string op) { var compiler = new SqlServerCompiler(); - Assert.Throws(() => + Assert.Throws(() => { compiler.Compile(new Query("Table").Where("Id", op, 1)); compiler.Compile(new Query("Table").OrWhere("Id", op, 1)); @@ -40,7 +40,7 @@ public void DenyInvalidOperatorsInHaving(string op) { var compiler = new SqlServerCompiler(); - Assert.Throws(() => + Assert.Throws(() => { compiler.Compile(new Query("Table").Having("Id", op, 1)); compiler.Compile(new Query("Table").OrHaving("Id", op, 1)); diff --git a/QueryBuilder.Tests/WhereTests.cs b/QueryBuilder.Tests/WhereTests.cs index 0c1254b2..9688c6c5 100644 --- a/QueryBuilder.Tests/WhereTests.cs +++ b/QueryBuilder.Tests/WhereTests.cs @@ -29,5 +29,222 @@ public void GroupedHavingFilters() Assert.Equal(@"SELECT * FROM ""Table1"" HAVING (SUM(""Column1"") = 10 OR SUM(""Column2"") = 20) AND SUM(""Column3"") = 30", c[EngineCodes.PostgreSql]); } + + [Fact] + public void GroupedHavingWithConditionGroup() + { + var group = new ConditionGroup(); + group.Where("Total", ">", 100).OrWhere("Count", "<", 5); + + var q = new Query("Orders") + .Having(group) + .HavingBetween("Average", 10, 50) + .HavingIn("Category", new[] { 1, 2, 3 }); + + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Orders] HAVING ([Total] > 100 OR [Count] < 5) AND [Average] BETWEEN 10 AND 50 AND [Category] IN (1, 2, 3)", c[EngineCodes.SqlServer]); + } + + [Fact] + public void ConditionGroupStandalone() + { + var group = new ConditionGroup(); + group.Where("A", 1).OrWhere("B", 2); + + Assert.Equal(2, group.Clauses.Count); + Assert.False(((AbstractCondition)group.Clauses[0]).IsOr); + Assert.True(((AbstractCondition)group.Clauses[1]).IsOr); + } + + [Fact] + public void Or_FollowedByWhere_DoesNotAffectSubsequentHaving() + { + var q = new Query("Table1") + .Where("A", 10) + .Or() + .Where("B", 20) + .HavingRaw("SUM([C]) = ?", 1) + .HavingRaw("SUM([D]) = ?", 2); + + var c = Compile(q); + + Assert.True(((AbstractCondition)q.Wheres.Clauses[1]).IsOr); + Assert.False(((AbstractCondition)q.Havings.Clauses[1]).IsOr); + Assert.Equal( + @"SELECT * FROM [Table1] WHERE [A] = 10 OR [B] = 20 HAVING SUM([C]) = 1 AND SUM([D]) = 2", + c[EngineCodes.SqlServer] + ); + } + + [Fact] + public void Or_FollowedByHaving_DoesNotAffectSubsequentWhere() + { + var q = new Query("Table1") + .HavingRaw("SUM([C]) = ?", 1) + .Or() + .HavingRaw("SUM([D]) = ?", 2) + .Where("A", 10) + .Where("B", 20); + + var c = Compile(q); + + Assert.True(((AbstractCondition)q.Havings.Clauses[1]).IsOr); + Assert.False(((AbstractCondition)q.Wheres.Clauses[1]).IsOr); + Assert.Equal( + @"SELECT * FROM [Table1] WHERE [A] = 10 AND [B] = 20 HAVING SUM([C]) = 1 OR SUM([D]) = 2", + c[EngineCodes.SqlServer] + ); + } + + [Fact] + public void Or_Interleaved_WhereThenHavingThenWhere() + { + var q = new Query("Table1") + .Where("A", 1) + .Or() + .HavingRaw("SUM([C]) = ?", 2) + .Where("B", 3); + + var c = Compile(q); + + // The Or() was consumed by Having, so B remains AND + Assert.False(((AbstractCondition)q.Wheres.Clauses[1]).IsOr); + Assert.Equal( + @"SELECT * FROM [Table1] WHERE [A] = 1 AND [B] = 3 HAVING SUM([C]) = 2", + c[EngineCodes.SqlServer] + ); + } + + [Fact] + public void Or_Interleaved_HavingThenWhereThenHaving() + { + var q = new Query("Table1") + .HavingRaw("SUM([C]) = ?", 1) + .Or() + .Where("A", 2) + .HavingRaw("SUM([D]) = ?", 3); + + var c = Compile(q); + + // The Or() was consumed by Where, so SUM(D) remains AND + Assert.False(((AbstractCondition)q.Havings.Clauses[1]).IsOr); + Assert.Equal( + @"SELECT * FROM [Table1] WHERE [A] = 2 HAVING SUM([C]) = 1 AND SUM([D]) = 3", + c[EngineCodes.SqlServer] + ); + } + + [Fact] + public void CallbackSubqueries_ResolveOuterVariables_WhereIn() + { + var q = new Query("Orders") + .Define("tenantId", 42) + .WhereIn("UserId", sub => sub.From("Users").Where("TenantId", Expressions.Variable("tenantId"))); + + var c = Compilers.Compile(q); + + Assert.Equal("SELECT * FROM [Orders] WHERE [UserId] IN (SELECT * FROM [Users] WHERE [TenantId] = ?)", c[EngineCodes.SqlServer].RawSql); + Assert.Contains(42, c[EngineCodes.SqlServer].Bindings); + } + + [Fact] + public void CallbackSubqueries_ResolveOuterVariables_Where() + { + var q = new Query("Orders") + .Define("tenantId", 42) + .Where("Total", ">", sub => sub.From("Stats").Select("AvgTotal").Where("TenantId", Expressions.Variable("tenantId"))); + + var c = Compilers.Compile(q); + + Assert.Contains(42, c[EngineCodes.SqlServer].Bindings); + } + + [Fact] + public void CallbackSubqueries_ResolveOuterVariables_WhereExists() + { + var q = new Query("Orders") + .Define("tenantId", 42) + .WhereExists(sub => sub.From("Users").Where("TenantId", Expressions.Variable("tenantId"))); + + var c = Compilers.Compile(q); + + Assert.Contains(42, c[EngineCodes.SqlServer].Bindings); + } + + [Fact] + public void CallbackSubqueries_ResolveOuterVariables_Join() + { + var q = new Query("Orders") + .Define("tenantId", 42) + .Join("Users", j => j.On("Orders.UserId", "Users.Id") + .WhereIn("Users.TenantId", sub => sub.From("Tenants").Select("Id").Where("Id", Expressions.Variable("tenantId")))); + + var c = Compilers.Compile(q); + + Assert.Contains(42, c[EngineCodes.SqlServer].Bindings); + } + + [Fact] + public void ExternalSubquery_ResolvesOuterVariables_WithoutParentConnection() + { + // sub is completely isolated, no Parent or callbacks used + var sub = new Query("Users").Where("TenantId", Expressions.Variable("tenantId")); + + var q = new Query("Orders") + .Define("tenantId", 42) + .WhereIn("UserId", sub); + + var c = Compilers.Compile(q); + + Assert.Equal("SELECT * FROM [Orders] WHERE [UserId] IN (SELECT * FROM [Users] WHERE [TenantId] = ?)", c[EngineCodes.SqlServer].RawSql); + Assert.Contains(42, c[EngineCodes.SqlServer].Bindings); + } + + [Fact] + public void CallbackSubqueries_ResolveOuterVariables_From() + { + var q = new Query() + .Define("tenantId", 42) + .From(sub => sub.From("Users").Where("TenantId", Expressions.Variable("tenantId")), "u"); + + var c = Compilers.Compile(q); + + Assert.Equal("SELECT * FROM (SELECT * FROM [Users] WHERE [TenantId] = ?) AS [u]", c[EngineCodes.SqlServer].RawSql); + Assert.Contains(42, c[EngineCodes.SqlServer].Bindings); + } + + [Fact] + public void ExternalSubquery_ResolveOuterVariables_From() + { + var sub = new Query("Users").Where("TenantId", Expressions.Variable("tenantId")); + + var q = new Query() + .Define("tenantId", 42) + .From(sub, "u"); + + var c = Compilers.Compile(q); + + Assert.Equal("SELECT * FROM (SELECT * FROM [Users] WHERE [TenantId] = ?) AS [u]", c[EngineCodes.SqlServer].RawSql); + Assert.Contains(42, c[EngineCodes.SqlServer].Bindings); + } + + [Fact] + public void SqlServer_LegacyPagination_InSubquery_ResolvesOuterVariables() + { + var compiler = new SqlServerCompiler { UseLegacyPagination = true }; + + var q = new Query("Orders") + .Define("tenantId", 42) + .WhereIn("UserId", sub => sub.From("Users") + .Where("TenantId", Expressions.Variable("tenantId")) + .OrderBy("Id") + .Offset(10) + .Limit(5)); + + var result = compiler.Compile(q); + + Assert.Contains(42, result.Bindings); + } } } diff --git a/QueryBuilder/Base.Where.cs b/QueryBuilder/Base.Where.cs deleted file mode 100644 index c40cc40a..00000000 --- a/QueryBuilder/Base.Where.cs +++ /dev/null @@ -1,699 +0,0 @@ -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 - } -} diff --git a/QueryBuilder/BaseQuery.cs b/QueryBuilder/BaseQuery.cs deleted file mode 100644 index 86b44a23..00000000 --- a/QueryBuilder/BaseQuery.cs +++ /dev/null @@ -1,310 +0,0 @@ -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); - } - - } -} diff --git a/QueryBuilder/Clauses/ConditionClause.cs b/QueryBuilder/Clauses/ConditionClause.cs index 287149e5..4e48e82c 100644 --- a/QueryBuilder/Clauses/ConditionClause.cs +++ b/QueryBuilder/Clauses/ConditionClause.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace SqlKata { @@ -119,7 +120,7 @@ public override AbstractClause Clone() /// /// Represents a comparison between a column and a full "subquery". /// - public class QueryCondition : AbstractCondition where T : BaseQuery + public class QueryCondition : AbstractCondition { public string Column { get; set; } public string Operator { get; set; } @@ -128,7 +129,7 @@ public class QueryCondition : AbstractCondition where T : BaseQuery /// public override AbstractClause Clone() { - return new QueryCondition + return new QueryCondition { Engine = Engine, Column = Column, @@ -144,7 +145,7 @@ public override AbstractClause Clone() /// /// Represents a comparison between a full "subquery" and a value. /// - public class SubQueryCondition : AbstractCondition where T : BaseQuery + public class SubQueryCondition : AbstractCondition { public object Value { get; set; } public string Operator { get; set; } @@ -153,7 +154,7 @@ public class SubQueryCondition : AbstractCondition where T : BaseQuery /// public override AbstractClause Clone() { - return new SubQueryCondition + return new SubQueryCondition { Engine = Engine, Value = Value, @@ -169,17 +170,17 @@ public override AbstractClause Clone() /// /// Represents a "is in" condition. /// - public class InCondition : AbstractCondition + public class InCondition : AbstractCondition { public string Column { get; set; } - public IEnumerable Values { get; set; } + public IEnumerable Values { get; set; } public override AbstractClause Clone() { - return new InCondition + return new InCondition { Engine = Engine, Column = Column, - Values = new List(Values), + Values = new List(Values), IsOr = IsOr, IsNot = IsNot, Component = Component, @@ -212,14 +213,14 @@ public override AbstractClause Clone() /// /// Represents a "is between" condition. /// - public class BetweenCondition : AbstractCondition + public class BetweenCondition : AbstractCondition { public string Column { get; set; } - public T Higher { get; set; } - public T Lower { get; set; } + public object Higher { get; set; } + public object Lower { get; set; } public override AbstractClause Clone() { - return new BetweenCondition + return new BetweenCondition { Engine = Engine, Column = Column, @@ -280,15 +281,23 @@ public override AbstractClause Clone() /// Represents a "nested" clause condition. /// i.e OR (myColumn = "A") /// - public class NestedCondition : AbstractCondition where T : BaseQuery + public class NestedCondition : AbstractCondition { - public T Query { get; set; } + public IClauseContainer Conditions { get; set; } + + [Obsolete("Use Conditions instead.")] + public IClauseContainer Query + { + get => Conditions; + set => Conditions = value; + } + public override AbstractClause Clone() { - return new NestedCondition + return new NestedCondition { Engine = Engine, - Query = Query.Clone(), + Conditions = Conditions?.Clone(), IsOr = IsOr, IsNot = IsNot, Component = Component, diff --git a/QueryBuilder/Compilers/Compiler.Conditions.cs b/QueryBuilder/Compilers/Compiler.Conditions.cs index 6615c246..e252b1b1 100644 --- a/QueryBuilder/Compilers/Compiler.Conditions.cs +++ b/QueryBuilder/Compilers/Compiler.Conditions.cs @@ -7,38 +7,26 @@ 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) + return clause switch { - throw new Exception($"Failed to invoke '{methodName}'", ex); - } - + BasicDateCondition date => CompileBasicDateCondition(ctx, date), + BasicStringCondition str => CompileBasicStringCondition(ctx, str), + BasicCondition basic => CompileBasicCondition(ctx, basic), + TwoColumnsCondition two => CompileTwoColumnsCondition(ctx, two), + BetweenCondition between => CompileBetweenCondition(ctx, between), + InCondition inCondition => CompileInCondition(ctx, inCondition), + InQueryCondition inQuery => CompileInQueryCondition(ctx, inQuery), + NullCondition nullCondition => CompileNullCondition(ctx, nullCondition), + BooleanCondition booleanCondition => CompileBooleanCondition(ctx, booleanCondition), + NestedCondition nested => CompileNestedCondition(ctx, nested), + ExistsCondition exists => CompileExistsCondition(ctx, exists), + QueryCondition queryCondition => CompileQueryCondition(ctx, queryCondition), + SubQueryCondition subQuery => CompileSubQueryCondition(ctx, subQuery), + RawCondition raw => CompileRawCondition(ctx, raw), + _ => throw new ArgumentOutOfRangeException(nameof(clause), $"Unsupported condition type: {clause.GetType().Name}") + }; } protected virtual string CompileConditions(SqlResult ctx, List conditions) @@ -68,18 +56,18 @@ protected virtual string CompileRawCondition(SqlResult ctx, RawCondition x) return WrapIdentifiers(x.Expression); } - protected virtual string CompileQueryCondition(SqlResult ctx, QueryCondition x) where T : BaseQuery + protected virtual string CompileQueryCondition(SqlResult ctx, QueryCondition x) { - var subCtx = CompileSelectQuery(x.Query); + var subCtx = CompileSubQuery(ctx, 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 + protected virtual string CompileSubQueryCondition(SqlResult ctx, SubQueryCondition x) { - var subCtx = CompileSelectQuery(x.Query); + var subCtx = CompileSubQuery(ctx, x.Query); ctx.Bindings.AddRange(subCtx.Bindings); @@ -168,20 +156,31 @@ protected virtual string CompileBasicDateCondition(SqlResult ctx, BasicDateCondi return x.IsNot ? $"NOT ({sql})" : sql; } - protected virtual string CompileNestedCondition(SqlResult ctx, NestedCondition x) where Q : BaseQuery + protected virtual string CompileNestedCondition(SqlResult ctx, NestedCondition x) { - if (!(x.Query.HasComponent("where", EngineCode) || x.Query.HasComponent("having", EngineCode))) + if (x.Conditions is ConditionGroup group) + { + if (!group.HasConditions(EngineCode)) + { + return null; + } + + var sql = CompileConditions(ctx, group.GetConditions(EngineCode)); + return x.IsNot ? $"NOT ({sql})" : $"({sql})"; + } + + if (!(x.Conditions.HasComponent("where", EngineCode) || x.Conditions.HasComponent("having", EngineCode))) { return null; } - var clause = x.Query.HasComponent("where", EngineCode) ? "where" : "having"; + var clause = x.Conditions.HasComponent("where", EngineCode) ? "where" : "having"; - var clauses = x.Query.GetComponents(clause, EngineCode); + var clauses = x.Conditions.GetComponents(clause, EngineCode); - var sql = CompileConditions(ctx, clauses); + var sql2 = CompileConditions(ctx, clauses); - return x.IsNot ? $"NOT ({sql})" : $"({sql})"; + return x.IsNot ? $"NOT ({sql2})" : $"({sql2})"; } protected string CompileTwoColumnsCondition(SqlResult ctx, TwoColumnsCondition clause) @@ -190,7 +189,7 @@ protected string CompileTwoColumnsCondition(SqlResult ctx, TwoColumnsCondition c return $"{op}{Wrap(clause.First)} {checkOperator(clause.Operator)} {Wrap(clause.Second)}"; } - protected virtual string CompileBetweenCondition(SqlResult ctx, BetweenCondition item) + protected virtual string CompileBetweenCondition(SqlResult ctx, BetweenCondition item) { var between = item.IsNot ? "NOT BETWEEN" : "BETWEEN"; var lower = Parameter(ctx, item.Lower); @@ -199,7 +198,7 @@ protected virtual string CompileBetweenCondition(SqlResult ctx, BetweenCondit return Wrap(item.Column) + $" {between} {lower} AND {higher}"; } - protected virtual string CompileInCondition(SqlResult ctx, InCondition item) + protected virtual string CompileInCondition(SqlResult ctx, InCondition item) { var column = Wrap(item.Column); @@ -210,15 +209,15 @@ protected virtual string CompileInCondition(SqlResult ctx, InCondition ite var inOperator = item.IsNot ? "NOT IN" : "IN"; - var values = Parameterize(ctx, item.Values); + var parameterizedValues = Parameterize(ctx, item.Values); - return column + $" {inOperator} ({values})"; + return column + $" {inOperator} ({parameterizedValues})"; } protected virtual string CompileInQueryCondition(SqlResult ctx, InQueryCondition item) { - var subCtx = CompileSelectQuery(item.Query); + var subCtx = CompileSubQuery(ctx, item.Query); ctx.Bindings.AddRange(subCtx.Bindings); @@ -253,10 +252,11 @@ protected virtual string CompileExistsCondition(SqlResult ctx, ExistsCondition i if (OmitSelectInsideExists) { - query.ClearComponent("select").SelectRaw("1"); + query.SelectClauses.Clear(); + query.SelectRaw("1"); } - var subCtx = CompileSelectQuery(query); + var subCtx = CompileSubQuery(ctx, query); ctx.Bindings.AddRange(subCtx.Bindings); diff --git a/QueryBuilder/Compilers/Compiler.cs b/QueryBuilder/Compilers/Compiler.cs index 5ac6c080..a09d8124 100644 --- a/QueryBuilder/Compilers/Compiler.cs +++ b/QueryBuilder/Compilers/Compiler.cs @@ -7,7 +7,6 @@ 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; } = "\""; @@ -24,7 +23,6 @@ public partial class Compiler protected Compiler() { - _compileConditionMethodsProvider = new ConditionsCompilerProvider(this); } public virtual string EngineCode { get; } @@ -79,74 +77,111 @@ protected SqlResult PrepareResult(SqlResult ctx) private Query TransformAggregateQuery(Query query) { - var clause = query.GetOneComponent("aggregate", EngineCode); - - if (clause.Columns.Count == 1 && !query.IsDistinct) return query; + var clause = query.AggregateClauses.ForEngineOrDefault(EngineCode); + + // Case 1: Simple single-column aggregate without DISTINCT. + // Example: query.From("users").AsCount("id") + // Output: SELECT COUNT("id") FROM "users" + // No subquery is required; standard SQL natively supports single-column aggregates. + // Limits, orders, and group-by clauses are stripped because they do not apply to a scalar aggregate. + if (clause.Columns.Count == 1 && !query.IsDistinct) + { + var single = query.Clone(); + single.LimitClauses.Clear(); + single.OrderByClauses.Clear(); + single.GroupByClauses.Clear(); + return single; + } - if (query.IsDistinct) - { - query.ClearComponent("aggregate", EngineCode); - query.ClearComponent("select", EngineCode); - query.Select(clause.Columns.ToArray()); + // Case 2 & 3: Multi-column aggregates or DISTINCT aggregates require wrapping the query in a subquery: + // Output template: SELECT (*) FROM () AS Query + var transformed = query.Clone(); + transformed.LimitClauses.Clear(); + transformed.OrderByClauses.Clear(); + transformed.GroupByClauses.Clear(); + + if (transformed.IsDistinct) + { + // Case 2: DISTINCT aggregate. + // Example: query.From("users").Distinct().AsCount("first_name", "last_name") + // SQL dialects generally do not allow multi-column COUNT(DISTINCT a, b). + // Instead, the inner query projects the distinct columns: + // Inner: SELECT DISTINCT "first_name", "last_name" FROM "users" + // Outer: SELECT COUNT(*) FROM (SELECT DISTINCT "first_name", "last_name" FROM "users") AS countQuery + transformed.AggregateClauses.ClearForEngine(EngineCode); + transformed.SelectClauses.ClearForEngine(EngineCode); + transformed.Select(clause.Columns.ToArray()); } else { + // Case 3: Multi-column aggregate without DISTINCT. + // Example: query.From("users").AsCount("first_name", "last_name") + // In standard SQL, COUNT(a, b) is invalid syntax because aggregates take a single expression or '*'. + // In SQL aggregate semantics, counting multiple columns means counting rows where none of those columns are NULL: + // Inner: SELECT * FROM "users" WHERE "first_name" IS NOT NULL AND "last_name" IS NOT NULL + // Outer: SELECT COUNT(*) FROM (SELECT * FROM "users" WHERE "first_name" IS NOT NULL AND "last_name" IS NOT NULL) AS countQuery foreach (var column in clause.Columns) { - query.WhereNotNull(column); + transformed.WhereNotNull(column); } } var outerClause = new AggregateClause() { Columns = new List { "*" }, - Type = clause.Type + Type = clause.Type, + Engine = EngineCode }; - return new Query() - .AddComponent("aggregate", outerClause) - .From(query, $"{clause.Type}Query"); + var outerQuery = new Query() + .From(transformed, $"{clause.Type}Query"); + outerQuery.AggregateClauses.Add(outerClause); + return outerQuery; } 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 + QueryStack.Push(query); + try { - if (query.Method == "aggregate") + SqlResult ctx; + + if (query.Method == "insert") + { + ctx = CompileInsertQuery(query); + } + else if (query.Method == "update") + { + ctx = CompileUpdateQuery(query); + } + else if (query.Method == "delete") { - query.ClearComponent("limit") - .ClearComponent("order") - .ClearComponent("group"); + ctx = CompileDeleteQuery(query); + } + else + { + if (query.Method == "aggregate") + { + query = TransformAggregateQuery(query); + } - query = TransformAggregateQuery(query); + ctx = CompileSelectQuery(query); } - ctx = CompileSelectQuery(query); - } + // handle CTEs + if (query.CteClauses.ForEngine(EngineCode).Any()) + { + ctx = CompileCteQuery(ctx, query); + } + + ctx.RawSql = Helper.ExpandParameters(ctx.RawSql, parameterPlaceholder, EscapeCharacter, ctx.Bindings.ToArray()); - // handle CTEs - if (query.HasComponent("cte", EngineCode)) + return ctx; + } + finally { - ctx = CompileCteQuery(ctx, query); + QueryStack.Pop(); } - - ctx.RawSql = Helper.ExpandParameters(ctx.RawSql, parameterPlaceholder, EscapeCharacter, ctx.Bindings.ToArray()); - - return ctx; } /// @@ -198,11 +233,45 @@ public virtual SqlResult Compile(IEnumerable queries) return ctx; } + [ThreadStatic] + private static Stack _queryStack; + + protected static Stack QueryStack => _queryStack ??= new Stack(); + + public virtual object FindVariable(string name) + { + if (_queryStack != null) + { + foreach (var query in _queryStack) + { + if (query?.Variables != null && query.Variables.TryGetValue(name, out var value)) + { + return value; + } + } + } + + throw new Exception($"Variable '{name}' not found"); + } + + protected virtual SqlResult CompileSubQuery(SqlResult parentCtx, Query query) + { + QueryStack.Push(query); + try + { + return CompileSelectQuery(query); + } + finally + { + QueryStack.Pop(); + } + } + protected virtual SqlResult CompileSelectQuery(Query query) { var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) { - Query = query.Clone(), + Query = query, }; var results = new[] { @@ -255,13 +324,12 @@ protected virtual SqlResult CompileDeleteQuery(Query query) Query = query }; - if (!ctx.Query.HasComponent("from", EngineCode)) + var fromClause = ctx.Query.FromClauses.ForEngineOrDefault(EngineCode); + if (fromClause is null) { throw new InvalidOperationException("No table set to delete"); } - var fromClause = ctx.Query.GetOneComponent("from", EngineCode); - string table = null; if (fromClause is FromClause fromClauseCast) @@ -317,13 +385,12 @@ protected virtual SqlResult CompileUpdateQuery(Query query) Query = query }; - if (!ctx.Query.HasComponent("from", EngineCode)) + var fromClause = ctx.Query.FromClauses.ForEngineOrDefault(EngineCode); + if (fromClause is null) { throw new InvalidOperationException("No table set to update"); } - var fromClause = ctx.Query.GetOneComponent("from", EngineCode); - string table = null; if (fromClause is FromClause fromClauseCast) @@ -343,7 +410,7 @@ protected virtual SqlResult CompileUpdateQuery(Query query) } // check for increment statements - var clause = ctx.Query.GetOneComponent("update", EngineCode); + var clause = ctx.Query.UpdateClauses.ForEngineOrDefault(EngineCode); string wheres; @@ -366,7 +433,7 @@ protected virtual SqlResult CompileUpdateQuery(Query query) } - var toUpdate = ctx.Query.GetOneComponent("update", EngineCode); + var toUpdate = clause as InsertClause; var parts = new List(); for (var i = 0; i < toUpdate.Columns.Count; i++) @@ -395,12 +462,9 @@ protected virtual SqlResult CompileInsertQuery(Query query) Query = query }; - if (!ctx.Query.HasComponent("from", EngineCode)) - throw new InvalidOperationException("No table set to insert"); - - var fromClause = ctx.Query.GetOneComponent("from", EngineCode); + var fromClause = ctx.Query.FromClauses.ForEngineOrDefault(EngineCode); if (fromClause is null) - throw new InvalidOperationException("Invalid table expression"); + throw new InvalidOperationException("No table set to insert"); string table = null; if (fromClause is FromClause fromClauseCast) @@ -414,7 +478,7 @@ protected virtual SqlResult CompileInsertQuery(Query query) if (table is null) throw new InvalidOperationException("Invalid table expression"); - var inserts = ctx.Query.GetComponents("insert", EngineCode); + var inserts = ctx.Query.InsertClauses.ForEngine(EngineCode).ToList(); if (inserts[0] is InsertQueryClause insertQueryClause) return CompileInsertQueryClause(ctx, table, insertQueryClause); else @@ -426,7 +490,7 @@ protected virtual SqlResult CompileInsertQueryClause( { string columns = GetInsertColumnsList(clause.Columns); - var subCtx = CompileSelectQuery(clause.Query); + var subCtx = CompileSubQuery(ctx, clause.Query); ctx.Bindings.AddRange(subCtx.Bindings); ctx.RawSql = $"{SingleInsertStartClause} {table}{columns} {subCtx.RawSql}"; @@ -525,7 +589,7 @@ public virtual string CompileColumn(SqlResult ctx, AbstractColumn column) alias = $" {ColumnAsKeyword}{WrapValue(queryColumn.Query.QueryAlias)}"; } - var subCtx = CompileSelectQuery(queryColumn.Query); + var subCtx = CompileSubQuery(ctx, queryColumn.Query); ctx.Bindings.AddRange(subCtx.Bindings); @@ -566,7 +630,10 @@ protected virtual string CompileFilterConditions(SqlResult ctx, AggregatedColumn return null; } - var wheres = aggregatedColumn.Filter.GetComponents("where"); + var wheres = aggregatedColumn.Filter.Wheres.Clauses + .ForEngine(EngineCode) + .Cast() + .ToList(); return CompileConditions(ctx, wheres); } @@ -587,7 +654,7 @@ public virtual SqlResult CompileCte(AbstractFrom cte) } else if (cte is QueryFromClause queryFromClause) { - var subCtx = CompileSelectQuery(queryFromClause.Query); + var subCtx = CompileSubQuery(ctx, queryFromClause.Query); ctx.Bindings.AddRange(subCtx.Bindings); ctx.RawSql = $"{WrapValue(queryFromClause.Alias)} AS ({subCtx.RawSql})"; @@ -610,10 +677,9 @@ protected virtual SqlResult OnBeforeSelect(SqlResult ctx) protected virtual string CompileColumns(SqlResult ctx) { - if (ctx.Query.HasComponent("aggregate", EngineCode)) + var aggregate = ctx.Query.AggregateClauses.ForEngineOrDefault(EngineCode); + if (aggregate != null) { - var aggregate = ctx.Query.GetOneComponent("aggregate", EngineCode); - var aggregateColumns = aggregate.Columns .Select(x => CompileColumn(ctx, new Column { Name = x })) .ToList(); @@ -635,8 +701,8 @@ protected virtual string CompileColumns(SqlResult ctx) return "SELECT 1"; } - var columns = ctx.Query - .GetComponents("select", EngineCode) + var columns = ctx.Query.SelectClauses + .ForEngine(EngineCode) .Select(x => CompileColumn(ctx, x)) .ToList(); @@ -652,22 +718,21 @@ public virtual string CompileUnion(SqlResult ctx) { // Handle UNION, EXCEPT and INTERSECT - if (!ctx.Query.GetComponents("combine", EngineCode).Any()) + var clauses = ctx.Query.CombineClauses.ForEngine(EngineCode).ToList(); + if (!clauses.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); + var subCtx = CompileSubQuery(ctx, combineClause.Query); ctx.Bindings.AddRange(subCtx.Bindings); @@ -702,7 +767,7 @@ public virtual string CompileTableExpression(SqlResult ctx, AbstractFrom from) var alias = string.IsNullOrEmpty(fromQuery.QueryAlias) ? "" : $" {TableAsKeyword}" + WrapValue(fromQuery.QueryAlias); - var subCtx = CompileSelectQuery(fromQuery); + var subCtx = CompileSubQuery(ctx, fromQuery); ctx.Bindings.AddRange(subCtx.Bindings); @@ -719,10 +784,9 @@ public virtual string CompileTableExpression(SqlResult ctx, AbstractFrom from) public virtual string CompileFrom(SqlResult ctx) { - if (ctx.Query.HasComponent("from", EngineCode)) + var from = ctx.Query.FromClauses.ForEngineOrDefault(EngineCode); + if (from != null) { - var from = ctx.Query.GetOneComponent("from", EngineCode); - return "FROM " + CompileTableExpression(ctx, from); } @@ -731,23 +795,24 @@ public virtual string CompileFrom(SqlResult ctx) public virtual string CompileJoins(SqlResult ctx) { - if (!ctx.Query.HasComponent("join", EngineCode)) + var joins = ctx.Query.Joins + .ForEngine(EngineCode) + .Select(x => CompileJoin(ctx, x.Join)) + .ToList(); + + if (!joins.Any()) { 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 from = join.FromClause; + var conditions = join.Conditions.GetConditions(EngineCode); var joinTable = CompileTableExpression(ctx, from); var constraints = CompileConditions(ctx, conditions); @@ -759,12 +824,15 @@ public virtual string CompileJoin(SqlResult ctx, Join join, bool isNested = fals public virtual string CompileWheres(SqlResult ctx) { - if (!ctx.Query.HasComponent("where", EngineCode)) + if (!ctx.Query.Wheres.HasConditions(EngineCode)) { return null; } - var conditions = ctx.Query.GetComponents("where", EngineCode); + var conditions = ctx.Query.Wheres.Clauses + .ForEngine(EngineCode) + .Cast() + .ToList(); var sql = CompileConditions(ctx, conditions).Trim(); return string.IsNullOrEmpty(sql) ? null : $"WHERE {sql}"; @@ -772,27 +840,23 @@ public virtual string CompileWheres(SqlResult ctx) public virtual string CompileGroups(SqlResult ctx) { - if (!ctx.Query.HasComponent("group", EngineCode)) + var columns = ctx.Query.GroupByClauses + .ForEngine(EngineCode) + .Select(x => CompileColumn(ctx, x)) + .ToList(); + + if (!columns.Any()) { 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) + var columns = ctx.Query.OrderByClauses + .ForEngine(EngineCode) .Select(x => { @@ -805,14 +869,19 @@ public virtual string CompileOrders(SqlResult ctx) var direction = (x as OrderBy).Ascending ? "" : " DESC"; return Wrap((x as OrderBy).Column) + direction; - }); + }).ToList(); + + if (!columns.Any()) + { + return null; + } return "ORDER BY " + string.Join(", ", columns); } public virtual string CompileHaving(SqlResult ctx) { - if (!ctx.Query.HasComponent("having", EngineCode)) + if (!ctx.Query.Havings.HasConditions(EngineCode)) { return null; } @@ -820,7 +889,8 @@ public virtual string CompileHaving(SqlResult ctx) var sql = new List(); string boolOperator; - var having = ctx.Query.GetComponents("having", EngineCode) + var having = ctx.Query.Havings.Clauses + .ForEngine(EngineCode) .Cast() .ToList(); @@ -992,7 +1062,7 @@ public virtual object Resolve(SqlResult ctx, object parameter) // 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); + var value = FindVariable(variable.Name); return value; } @@ -1017,7 +1087,7 @@ public virtual string Parameter(SqlResult ctx, object parameter) // 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); + var value = FindVariable(variable.Name); ctx.Bindings.Add(value); return parameterPlaceholder; } diff --git a/QueryBuilder/Compilers/ConditionsCompilerProvider.cs b/QueryBuilder/Compilers/ConditionsCompilerProvider.cs deleted file mode 100644 index 6439da94..00000000 --- a/QueryBuilder/Compilers/ConditionsCompilerProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -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; - } - } -} diff --git a/QueryBuilder/Compilers/CteFinder.cs b/QueryBuilder/Compilers/CteFinder.cs index fbb0800a..c0564b94 100644 --- a/QueryBuilder/Compilers/CteFinder.cs +++ b/QueryBuilder/Compilers/CteFinder.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace SqlKata.Compilers { @@ -32,7 +33,7 @@ public List Find() private List findInternal(Query queryToSearch) { - var cteList = queryToSearch.GetComponents("cte", engineCode); + var cteList = queryToSearch.CteClauses.ForEngine(engineCode).ToList(); var resultList = new List(); diff --git a/QueryBuilder/Compilers/FirebirdCompiler.cs b/QueryBuilder/Compilers/FirebirdCompiler.cs index 61ab9547..74637c63 100644 --- a/QueryBuilder/Compilers/FirebirdCompiler.cs +++ b/QueryBuilder/Compilers/FirebirdCompiler.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -17,7 +18,7 @@ protected override SqlResult CompileInsertQuery(Query query) { var ctx = base.CompileInsertQuery(query); - var inserts = ctx.Query.GetComponents("insert", EngineCode); + var inserts = ctx.Query.InsertClauses.ForEngine(EngineCode).ToList(); if (inserts.Count > 1) { @@ -57,17 +58,13 @@ protected override string CompileColumns(SqlResult ctx) { ctx.Bindings.Insert(0, limit); - ctx.Query.ClearComponent("limit"); - - return $"SELECT FIRST {parameterPlaceholder}" + compiled.Substring(6); + return string.Concat($"SELECT FIRST {parameterPlaceholder}", compiled.AsSpan(6)); } else if (limit == 0 && offset > 0) { ctx.Bindings.Insert(0, offset); - ctx.Query.ClearComponent("offset"); - - return $"SELECT SKIP {parameterPlaceholder}" + compiled.Substring(6); + return string.Concat($"SELECT SKIP {parameterPlaceholder}", compiled.AsSpan(6)); } return compiled; diff --git a/QueryBuilder/Compilers/OracleCompiler.cs b/QueryBuilder/Compilers/OracleCompiler.cs index 610ec20d..8bca3edf 100644 --- a/QueryBuilder/Compilers/OracleCompiler.cs +++ b/QueryBuilder/Compilers/OracleCompiler.cs @@ -52,7 +52,7 @@ public override string CompileLimit(SqlResult ctx) var safeOrder = ""; - if (!ctx.Query.HasComponent("order")) + if (!ctx.Query.OrderByClauses.Any()) { safeOrder = "ORDER BY (SELECT 0 FROM DUAL) "; } diff --git a/QueryBuilder/Compilers/SqlServerCompiler.cs b/QueryBuilder/Compilers/SqlServerCompiler.cs index 98b488bf..d48141e3 100644 --- a/QueryBuilder/Compilers/SqlServerCompiler.cs +++ b/QueryBuilder/Compilers/SqlServerCompiler.cs @@ -32,7 +32,7 @@ protected override SqlResult CompileSelectQuery(Query query) var offset = query.GetOffset(EngineCode); - if (!query.HasComponent("select")) + if (!query.SelectClauses.Any()) { query.Select("*"); } @@ -41,7 +41,7 @@ protected override SqlResult CompileSelectQuery(Query query) query.SelectRaw($"ROW_NUMBER() OVER ({order}) AS [row_num]", ctx.Bindings.ToArray()); - query.ClearComponent("order"); + query.OrderByClauses.Clear(); var result = base.CompileSelectQuery(query); @@ -81,10 +81,8 @@ protected override string CompileColumns(SqlResult ctx) // top bindings should be inserted first ctx.Bindings.Insert(0, limit); - ctx.Query.ClearComponent("limit"); - // handle distinct - if (compiled.IndexOf("SELECT DISTINCT") == 0) + if (compiled.StartsWith("SELECT DISTINCT")) { return $"SELECT DISTINCT TOP ({parameterPlaceholder}){compiled.Substring(15)}"; } @@ -113,7 +111,7 @@ public override string CompileLimit(SqlResult ctx) } var safeOrder = ""; - if (!ctx.Query.HasComponent("order")) + if (!ctx.Query.OrderByClauses.Any()) { safeOrder = "ORDER BY (SELECT 0) "; } diff --git a/QueryBuilder/ConditionGroup.cs b/QueryBuilder/ConditionGroup.cs new file mode 100644 index 00000000..d2b39e3f --- /dev/null +++ b/QueryBuilder/ConditionGroup.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SqlKata +{ + public class ConditionGroup : IConditional + { + private bool orFlag = false; + private bool notFlag = false; + + public Action OnGetOr { get; set; } + public Action OnGetNot { get; set; } + + public string EngineScope { get; set; } = null; + public List Clauses { get; set; } + + public ConditionGroup() + { + Clauses = []; + } + + public virtual ConditionGroup Clone() + { + return new ConditionGroup + { + EngineScope = EngineScope, + Clauses = Clauses.Select(x => x.Clone()).ToList() + }; + } + + IClauseContainer IClauseContainer.Clone() => Clone(); + + public bool HasConditions(string engineCode = null) + { + engineCode ??= EngineScope; + return Clauses.Any(x => engineCode == null || x.Engine == null || engineCode == x.Engine); + } + + public List GetConditions(string engineCode = null) + { + return GetComponents(null, engineCode); + } + + public List GetComponents(string component = null, string engineCode = null) where C : AbstractClause + { + engineCode ??= EngineScope; + + return Clauses + .Where(x => engineCode == null || x.Engine == null || engineCode == x.Engine) + .OfType() + .ToList(); + } + + public List GetComponents(string component = null, string engineCode = null) + { + return GetComponents(component, engineCode); + } + + public C GetOneComponent(string component, string engineCode = null) where C : AbstractClause + { + engineCode ??= EngineScope; + + var all = GetComponents(component, engineCode); + return all.FirstOrDefault(c => c.Engine == engineCode) ?? all.FirstOrDefault(c => c.Engine == null); + } + + public AbstractClause GetOneComponent(string component, string engineCode = null) + { + return GetOneComponent(component, engineCode); + } + + public bool HasComponent(string component, string engineCode = null) + { + engineCode ??= EngineScope; + return GetComponents(component, engineCode).Count != 0; + } + + public ConditionGroup Clear(string engineCode = null) + { + engineCode ??= EngineScope; + + if (engineCode == null) + { + Clauses.Clear(); + } + else + { + Clauses.RemoveAll(x => x.Engine == null || x.Engine == engineCode); + } + + return this; + } + + public bool GetOr() + { + var ret = orFlag; + orFlag = false; + OnGetOr?.Invoke(); + return ret; + } + + public bool GetNot() + { + var ret = notFlag; + notFlag = false; + OnGetNot?.Invoke(); + return ret; + } + + public void SetOr(bool value) + { + orFlag = value; + } + + public void SetNot(bool value) + { + notFlag = value; + } + + public void AddComponent(string component, AbstractClause clause, string engineCode = null) + { + clause.Engine = engineCode ?? EngineScope; + if (component != null && clause.Component == null) + { + clause.Component = component; + } + Clauses.Add(clause); + } + + public IClauseContainer Add(AbstractClause clause, string engineCode = null) + { + AddComponent(clause.Component, clause, engineCode); + return this; + } + + public IClauseContainer Add(IReadOnlyCollection clauses, string engineCode = null) + { + foreach (var clause in clauses) + { + Add(clause, engineCode); + } + return this; + } + + public IConditional NewChild() => new ConditionGroup { EngineScope = this.EngineScope }; + } +} diff --git a/QueryBuilder/Extensions/ClauseExtensions.cs b/QueryBuilder/Extensions/ClauseExtensions.cs new file mode 100644 index 00000000..61d08d5c --- /dev/null +++ b/QueryBuilder/Extensions/ClauseExtensions.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SqlKata +{ + public static class ClauseExtensions + { + public static IEnumerable ForEngine(this IEnumerable clauses, string engineCode) where T : AbstractClause + { + if (clauses == null) + { + return Enumerable.Empty(); + } + + return clauses.Where(x => engineCode == null || x.Engine == null || engineCode == x.Engine); + } + + public static T ForEngineOrDefault(this IEnumerable clauses, string engineCode) where T : AbstractClause + { + if (clauses == null) + { + return null; + } + + var list = clauses as IList ?? clauses.ToList(); + return list.FirstOrDefault(c => c.Engine == engineCode) ?? list.FirstOrDefault(c => c.Engine == null); + } + + public static void ClearForEngine(this List clauses, string engineCode = null) where T : AbstractClause + { + if (clauses == null) return; + if (engineCode == null) + { + clauses.Clear(); + } + else + { + clauses.RemoveAll(x => x.Engine == null || x.Engine == engineCode); + } + } + } +} diff --git a/QueryBuilder/Extensions/ConditionExtensions.cs b/QueryBuilder/Extensions/ConditionExtensions.cs new file mode 100644 index 00000000..3e1b45d1 --- /dev/null +++ b/QueryBuilder/Extensions/ConditionExtensions.cs @@ -0,0 +1,746 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace SqlKata +{ + public static class ConditionExtensions + { + public static T Or(this T target) where T : IConditional + { + target.SetOr(true); + return target; + } + + public static T Not(this T target, bool flag = true) where T : IConditional + { + target.SetNot(flag); + return target; + } + + public static T And(this T target) where T : IConditional + { + target.SetOr(false); + return target; + } + + public static T Where(this T target, string column, string op, object value) where T : IConditional + { + if (value == null) + { + return target.Not(op != "=").WhereNull(column); + } + + if (value is bool boolValue) + { + if (op != "=") + { + target.Not(); + } + + return boolValue ? target.WhereTrue(column) : target.WhereFalse(column); + } + + target.AddComponent("where", new BasicCondition + { + Column = column, + Operator = op, + Value = value, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T WhereNot(this T target, string column, string op, object value) where T : IConditional + { + return target.Not().Where(column, op, value); + } + + public static T OrWhere(this T target, string column, string op, object value) where T : IConditional + { + return target.Or().Where(column, op, value); + } + + public static T OrWhereNot(this T target, string column, string op, object value) where T : IConditional + { + return target.Or().Not().Where(column, op, value); + } + + public static T Where(this T target, string column, object value) where T : IConditional + { + return target.Where(column, "=", value); + } + + public static T WhereNot(this T target, string column, object value) where T : IConditional + { + return target.WhereNot(column, "=", value); + } + + public static T OrWhere(this T target, string column, object value) where T : IConditional + { + return target.OrWhere(column, "=", value); + } + + public static T OrWhereNot(this T target, string column, object value) where T : IConditional + { + return target.OrWhereNot(column, "=", value); + } + + public static T Where(this T target, object constraints) where T : IConditional + { + var dictionary = new Dictionary(); + + foreach (var item in constraints.GetType().GetRuntimeProperties()) + { + dictionary.Add(item.Name, item.GetValue(constraints)); + } + + return target.Where(dictionary); + } + + public static T Where(this T target, IEnumerable> values) where T : IConditional + { + var orFlag = target.GetOr(); + var notFlag = target.GetNot(); + + foreach (var tuple in values) + { + if (orFlag) + { + target.Or(); + } + else + { + target.And(); + } + + target.Not(notFlag).Where(tuple.Key, tuple.Value); + } + + return target; + } + + public static T WhereRaw(this T target, string sql, params object[] bindings) where T : IConditional + { + target.AddComponent("where", new RawCondition + { + Expression = sql, + Bindings = bindings, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T OrWhereRaw(this T target, string sql, params object[] bindings) where T : IConditional + { + return target.Or().WhereRaw(sql, bindings); + } + + public static T Where(this T target, Func callback) where T : IConditional + { + var child = (T)target.NewChild(); + var query = callback.Invoke(child); + + // omit empty queries + if (!query.HasComponent("where")) + { + return target; + } + + target.AddComponent("where", new NestedCondition + { + Conditions = query, + IsNot = target.GetNot(), + IsOr = target.GetOr(), + }); + + return target; + } + + public static T WhereNot(this T target, Func callback) where T : IConditional + { + return target.Not().Where(callback); + } + + public static T OrWhere(this T target, Func callback) where T : IConditional + { + return target.Or().Where(callback); + } + + public static T OrWhereNot(this T target, Func callback) where T : IConditional + { + return target.Not().Or().Where(callback); + } + + public static T WhereColumns(this T target, string first, string op, string second) where T : IConditional + { + target.AddComponent("where", new TwoColumnsCondition + { + First = first, + Second = second, + Operator = op, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T OrWhereColumns(this T target, string first, string op, string second) where T : IConditional + { + return target.Or().WhereColumns(first, op, second); + } + + public static T WhereNull(this T target, string column) where T : IConditional + { + target.AddComponent("where", new NullCondition + { + Column = column, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T WhereNotNull(this T target, string column) where T : IConditional + { + return target.Not().WhereNull(column); + } + + public static T OrWhereNull(this T target, string column) where T : IConditional + { + return target.Or().WhereNull(column); + } + + public static T OrWhereNotNull(this T target, string column) where T : IConditional + { + return target.Or().Not().WhereNull(column); + } + + public static T WhereTrue(this T target, string column) where T : IConditional + { + target.AddComponent("where", new BooleanCondition + { + Column = column, + Value = true, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T OrWhereTrue(this T target, string column) where T : IConditional + { + return target.Or().WhereTrue(column); + } + + public static T WhereFalse(this T target, string column) where T : IConditional + { + target.AddComponent("where", new BooleanCondition + { + Column = column, + Value = false, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T OrWhereFalse(this T target, string column) where T : IConditional + { + return target.Or().WhereFalse(column); + } + + public static T WhereLike(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + target.AddComponent("where", new BasicStringCondition + { + Operator = "like", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T WhereNotLike(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Not().WhereLike(column, value, caseSensitive, escapeCharacter); + } + + public static T OrWhereLike(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Or().WhereLike(column, value, caseSensitive, escapeCharacter); + } + + public static T OrWhereNotLike(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Or().Not().WhereLike(column, value, caseSensitive, escapeCharacter); + } + + public static T WhereStarts(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + target.AddComponent("where", new BasicStringCondition + { + Operator = "starts", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T WhereNotStarts(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Not().WhereStarts(column, value, caseSensitive, escapeCharacter); + } + + public static T OrWhereStarts(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Or().WhereStarts(column, value, caseSensitive, escapeCharacter); + } + + public static T OrWhereNotStarts(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Or().Not().WhereStarts(column, value, caseSensitive, escapeCharacter); + } + + public static T WhereEnds(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + target.AddComponent("where", new BasicStringCondition + { + Operator = "ends", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T WhereNotEnds(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Not().WhereEnds(column, value, caseSensitive, escapeCharacter); + } + + public static T OrWhereEnds(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Or().WhereEnds(column, value, caseSensitive, escapeCharacter); + } + + public static T OrWhereNotEnds(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Or().Not().WhereEnds(column, value, caseSensitive, escapeCharacter); + } + + public static T WhereContains(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + target.AddComponent("where", new BasicStringCondition + { + Operator = "contains", + Column = column, + Value = value, + CaseSensitive = caseSensitive, + EscapeCharacter = escapeCharacter, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T WhereNotContains(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Not().WhereContains(column, value, caseSensitive, escapeCharacter); + } + + public static T OrWhereContains(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Or().WhereContains(column, value, caseSensitive, escapeCharacter); + } + + public static T OrWhereNotContains(this T target, string column, object value, bool caseSensitive = false, string escapeCharacter = null) where T : IConditional + { + return target.Or().Not().WhereContains(column, value, caseSensitive, escapeCharacter); + } + + public static T WhereBetween(this T target, string column, TVal lower, TVal higher) where T : IConditional + { + target.AddComponent("where", new BetweenCondition + { + Column = column, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + Lower = lower, + Higher = higher + }); + + return target; + } + + public static T OrWhereBetween(this T target, string column, TVal lower, TVal higher) where T : IConditional + { + return target.Or().WhereBetween(column, lower, higher); + } + + public static T WhereNotBetween(this T target, string column, TVal lower, TVal higher) where T : IConditional + { + return target.Not().WhereBetween(column, lower, higher); + } + + public static T OrWhereNotBetween(this T target, string column, TVal lower, TVal higher) where T : IConditional + { + return target.Or().Not().WhereBetween(column, lower, higher); + } + + public static T WhereIn(this T target, string column, IEnumerable values) where T : IConditional + { + if (values is string val) + { + target.AddComponent("where", new InCondition + { + Column = column, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + Values = new List { val } + }); + + return target; + } + + target.AddComponent("where", new InCondition + { + Column = column, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + Values = values.Distinct().Cast().ToList() + }); + + return target; + } + + public static T OrWhereIn(this T target, string column, IEnumerable values) where T : IConditional + { + return target.Or().WhereIn(column, values); + } + + public static T WhereNotIn(this T target, string column, IEnumerable values) where T : IConditional + { + return target.Not().WhereIn(column, values); + } + + public static T OrWhereNotIn(this T target, string column, IEnumerable values) where T : IConditional + { + return target.Or().Not().WhereIn(column, values); + } + + public static T WhereIn(this T target, string column, Query query) where T : IConditional + { + target.AddComponent("where", new InQueryCondition + { + Column = column, + IsOr = target.GetOr(), + IsNot = target.GetNot(), + Query = query, + }); + + return target; + } + + public static T WhereIn(this T target, string column, Func callback) where T : IConditional + { + return target.WhereIn(column, callback.Invoke(new Query())); + } + + public static T OrWhereIn(this T target, string column, Query query) where T : IConditional + { + return target.Or().WhereIn(column, query); + } + + public static T OrWhereIn(this T target, string column, Func callback) where T : IConditional + { + return target.Or().WhereIn(column, callback); + } + + public static T WhereNotIn(this T target, string column, Query query) where T : IConditional + { + return target.Not().WhereIn(column, query); + } + + public static T WhereNotIn(this T target, string column, Func callback) where T : IConditional + { + return target.Not().WhereIn(column, callback); + } + + public static T OrWhereNotIn(this T target, string column, Query query) where T : IConditional + { + return target.Or().Not().WhereIn(column, query); + } + + public static T OrWhereNotIn(this T target, string column, Func callback) where T : IConditional + { + return target.Or().Not().WhereIn(column, callback); + } + + public static T Where(this T target, string column, string op, Query query) where T : IConditional + { + target.AddComponent("where", new QueryCondition + { + Column = column, + Operator = op, + Query = query, + IsNot = target.GetNot(), + IsOr = target.GetOr(), + }); + + return target; + } + + public static T Where(this T target, string column, string op, Func callback) where T : IConditional + { + return target.Where(column, op, callback.Invoke(new Query())); + } + + public static T WhereSub(this T target, Query query, object value) where T : IConditional + { + return target.WhereSub(query, "=", value); + } + + public static T WhereSub(this T target, Query query, string op, object value) where T : IConditional + { + target.AddComponent("where", new SubQueryCondition + { + Value = value, + Operator = op, + Query = query, + IsNot = target.GetNot(), + IsOr = target.GetOr(), + }); + + return target; + } + + public static T OrWhereSub(this T target, Query query, object value) where T : IConditional + { + return target.Or().WhereSub(query, value); + } + + public static T OrWhereSub(this T target, Query query, string op, object value) where T : IConditional + { + return target.Or().WhereSub(query, op, value); + } + + public static T OrWhere(this T target, string column, string op, Query query) where T : IConditional + { + return target.Or().Where(column, op, query); + } + + public static T OrWhere(this T target, string column, string op, Func callback) where T : IConditional + { + return target.Or().Where(column, op, callback); + } + + public static T WhereExists(this T target, Query query) where T : IConditional + { + if (!query.FromClauses.Any()) + { + throw new ArgumentException($"'{nameof(FromClause)}' cannot be empty if used inside a '{nameof(WhereExists)}' condition"); + } + + target.AddComponent("where", new ExistsCondition + { + Query = query, + IsNot = target.GetNot(), + IsOr = target.GetOr(), + }); + + return target; + } + + public static T WhereExists(this T target, Func callback) where T : IConditional + { + return target.WhereExists(callback.Invoke(new Query())); + } + + public static T WhereNotExists(this T target, Query query) where T : IConditional + { + return target.Not().WhereExists(query); + } + + public static T WhereNotExists(this T target, Func callback) where T : IConditional + { + return target.Not().WhereExists(callback); + } + + public static T OrWhereExists(this T target, Query query) where T : IConditional + { + return target.Or().WhereExists(query); + } + + public static T OrWhereExists(this T target, Func callback) where T : IConditional + { + return target.Or().WhereExists(callback); + } + + public static T OrWhereNotExists(this T target, Query query) where T : IConditional + { + return target.Or().Not().WhereExists(query); + } + + public static T OrWhereNotExists(this T target, Func callback) where T : IConditional + { + return target.Or().Not().WhereExists(callback); + } + + #region date + public static T WhereDatePart(this T target, string part, string column, string op, object value) where T : IConditional + { + target.AddComponent("where", new BasicDateCondition + { + Operator = op, + Column = column, + Value = value, + Part = part?.ToLowerInvariant(), + IsOr = target.GetOr(), + IsNot = target.GetNot(), + }); + + return target; + } + + public static T WhereNotDatePart(this T target, string part, string column, string op, object value) where T : IConditional + { + return target.Not().WhereDatePart(part, column, op, value); + } + + public static T OrWhereDatePart(this T target, string part, string column, string op, object value) where T : IConditional + { + return target.Or().WhereDatePart(part, column, op, value); + } + + public static T OrWhereNotDatePart(this T target, string part, string column, string op, object value) where T : IConditional + { + return target.Or().Not().WhereDatePart(part, column, op, value); + } + + public static T WhereDate(this T target, string column, string op, object value) where T : IConditional + { + return target.WhereDatePart("date", column, op, value); + } + + public static T WhereNotDate(this T target, string column, string op, object value) where T : IConditional + { + return target.Not().WhereDate(column, op, value); + } + + public static T OrWhereDate(this T target, string column, string op, object value) where T : IConditional + { + return target.Or().WhereDate(column, op, value); + } + + public static T OrWhereNotDate(this T target, string column, string op, object value) where T : IConditional + { + return target.Or().Not().WhereDate(column, op, value); + } + + public static T WhereTime(this T target, string column, string op, object value) where T : IConditional + { + return target.WhereDatePart("time", column, op, value); + } + + public static T WhereNotTime(this T target, string column, string op, object value) where T : IConditional + { + return target.Not().WhereTime(column, op, value); + } + + public static T OrWhereTime(this T target, string column, string op, object value) where T : IConditional + { + return target.Or().WhereTime(column, op, value); + } + + public static T OrWhereNotTime(this T target, string column, string op, object value) where T : IConditional + { + return target.Or().Not().WhereTime(column, op, value); + } + + public static T WhereDatePart(this T target, string part, string column, object value) where T : IConditional + { + return target.WhereDatePart(part, column, "=", value); + } + + public static T WhereNotDatePart(this T target, string part, string column, object value) where T : IConditional + { + return target.WhereNotDatePart(part, column, "=", value); + } + + public static T OrWhereDatePart(this T target, string part, string column, object value) where T : IConditional + { + return target.OrWhereDatePart(part, column, "=", value); + } + + public static T OrWhereNotDatePart(this T target, string part, string column, object value) where T : IConditional + { + return target.OrWhereNotDatePart(part, column, "=", value); + } + + public static T WhereDate(this T target, string column, object value) where T : IConditional + { + return target.WhereDate(column, "=", value); + } + + public static T WhereNotDate(this T target, string column, object value) where T : IConditional + { + return target.WhereNotDate(column, "=", value); + } + + public static T OrWhereDate(this T target, string column, object value) where T : IConditional + { + return target.OrWhereDate(column, "=", value); + } + + public static T OrWhereNotDate(this T target, string column, object value) where T : IConditional + { + return target.OrWhereNotDate(column, "=", value); + } + + public static T WhereTime(this T target, string column, object value) where T : IConditional + { + return target.WhereTime(column, "=", value); + } + + public static T WhereNotTime(this T target, string column, object value) where T : IConditional + { + return target.WhereNotTime(column, "=", value); + } + + public static T OrWhereTime(this T target, string column, object value) where T : IConditional + { + return target.OrWhereTime(column, "=", value); + } + + public static T OrWhereNotTime(this T target, string column, object value) where T : IConditional + { + return target.OrWhereNotTime(column, "=", value); + } + #endregion + } +} diff --git a/QueryBuilder/IClauseContainer.cs b/QueryBuilder/IClauseContainer.cs new file mode 100644 index 00000000..9986668a --- /dev/null +++ b/QueryBuilder/IClauseContainer.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace SqlKata +{ + public interface IClauseContainer + { + bool HasComponent(string component, string engineCode = null); + List GetComponents(string component, string engineCode = null) where C : AbstractClause; + IClauseContainer Clone(); + } +} diff --git a/QueryBuilder/IConditional.cs b/QueryBuilder/IConditional.cs new file mode 100644 index 00000000..fea924f0 --- /dev/null +++ b/QueryBuilder/IConditional.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace SqlKata +{ + public interface IConditional : IClauseContainer + { + string EngineScope { get; } + List Clauses { get; } + bool GetOr(); + bool GetNot(); + void SetOr(bool value); + void SetNot(bool value); + void AddComponent(string component, AbstractClause clause, string engineCode = null); + IConditional NewChild(); + } +} diff --git a/QueryBuilder/Join.cs b/QueryBuilder/Join.cs index ae0969a6..27d835c0 100644 --- a/QueryBuilder/Join.cs +++ b/QueryBuilder/Join.cs @@ -1,77 +1,270 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace SqlKata { - public class Join : BaseQuery + public class Join : IConditional { protected string _type = "inner join"; + private bool orFlag = false; + private bool notFlag = false; + protected List _clauses = new List(); - public string Type + public ConditionGroup Conditions { get; set; } = new ConditionGroup(); + public AbstractFrom FromClause { get; set; } + + public string EngineScope { get; set; } = null; + + [Obsolete("Use dedicated properties (Conditions, FromClause) instead.")] + public List Clauses { get { - return _type; + var all = new List(_clauses); + if (FromClause != null) + { + all.Add(FromClause); + } + all.AddRange(Conditions.Clauses); + return all; } set { - _type = value.ToUpperInvariant(); + _clauses.Clear(); + Conditions.Clauses.Clear(); + FromClause = null; + if (value != null) + { + foreach (var clause in value) + { + if (clause is AbstractFrom from) + { + FromClause = from; + } + else if (clause is AbstractCondition) + { + Conditions.Clauses.Add(clause); + } + else + { + _clauses.Add(clause); + } + } + } } } - public Join() : base() + public string Type + { + get => _type; + set => _type = value?.ToUpperInvariant(); + } + + public Join() { } - public override Join Clone() + public virtual Join Clone() { - var clone = base.Clone(); - clone._type = _type; + var clone = new Join + { + _type = _type, + EngineScope = EngineScope, + FromClause = FromClause?.Clone() as AbstractFrom, + Conditions = Conditions.Clone(), + _clauses = _clauses.Select(x => x.Clone()).ToList() + }; return clone; } + IClauseContainer IClauseContainer.Clone() => 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 = "=") + public Join JoinWith(string table) { - return AddComponent("where", new TwoColumnsCondition + FromClause = new FromClause { Table = table, Engine = EngineScope, Component = "from" }; + return this; + } + + public Join JoinWith(Query query, string alias = null) + { + query = query.Clone(); + if (alias != null) { - First = first, - Second = second, - Operator = op, - IsOr = GetOr(), - IsNot = GetNot() - }); + query.As(alias); + } + FromClause = new QueryFromClause { Query = query, Engine = EngineScope, Component = "from" }; + return this; + } + public Join JoinWith(Func callback, string alias = null) + { + return JoinWith(callback.Invoke(new Query()), alias); } - public Join OrOn(string first, string second, string op = "=") + public Join JoinWithRaw(string sql, params object[] bindings) { - return Or().On(first, second, op); + FromClause = new RawFromClause { Expression = sql, Bindings = bindings, Engine = EngineScope, Component = "from" }; + return this; } - public override Join NewQuery() + /// + /// Alias for "JoinWith" operator. + /// + public Join From(string table) => JoinWith(table); + public Join From(Query query, string alias = null) => JoinWith(query, alias); + public Join From(Func callback, string alias = null) => JoinWith(callback, alias); + public Join FromRaw(string sql, params object[] bindings) => JoinWithRaw(sql, bindings); + + [Obsolete("Use dedicated properties (Conditions, FromClause) instead.")] + public Join AddComponent(string component, AbstractClause clause, string engineCode = null) + { + engineCode ??= EngineScope; + clause.Engine = engineCode; + clause.Component = component; + + if (clause is AbstractFrom from) + { + FromClause = from; + } + else if (clause is AbstractCondition) + { + Conditions.AddComponent(component, clause, engineCode); + } + else + { + _clauses.Add(clause); + } + + return this; + } + + + + [Obsolete("Use dedicated properties (Conditions, FromClause) instead.")] + public List GetComponents(string component, string engineCode = null) where C : AbstractClause { - return new Join(); + engineCode ??= EngineScope; + + if (component == "from" && FromClause is C cFrom && (engineCode == null || FromClause.Engine == null || FromClause.Engine == engineCode)) + { + return new List { cFrom }; + } + + if (component == "where") + { + return Conditions.GetComponents(null, engineCode); + } + + return _clauses + .Where(x => x.Component == component) + .Where(x => engineCode == null || x.Engine == null || engineCode == x.Engine) + .Cast() + .ToList(); } + + [Obsolete("Use dedicated properties (Conditions, FromClause) instead.")] + public List GetComponents(string component, string engineCode = null) + { + return GetComponents(component, engineCode); + } + + [Obsolete("Use dedicated properties (Conditions, FromClause) instead.")] + public C GetOneComponent(string component, string engineCode = null) where C : AbstractClause + { + engineCode ??= EngineScope; + var all = GetComponents(component, engineCode); + return all.FirstOrDefault(c => c.Engine == engineCode) ?? all.FirstOrDefault(c => c.Engine == null); + } + + [Obsolete("Use dedicated properties (Conditions, FromClause) instead.")] + public AbstractClause GetOneComponent(string component, string engineCode = null) + { + return GetOneComponent(component, engineCode); + } + + [Obsolete("Use dedicated properties (Conditions, FromClause) instead.")] + public bool HasComponent(string component, string engineCode = null) + { + engineCode ??= EngineScope; + if (component == "from") return FromClause != null && (engineCode == null || FromClause.Engine == null || FromClause.Engine == engineCode); + if (component == "where") return Conditions.HasConditions(engineCode); + return _clauses.Any(x => x.Component == component && (engineCode == null || x.Engine == null || engineCode == x.Engine)); + } + + [Obsolete("Use dedicated properties (Conditions, FromClause) instead.")] + public Join ClearComponent(string component, string engineCode = null) + { + engineCode ??= EngineScope; + + if (component == "from") + { + if (engineCode == null || FromClause?.Engine == engineCode) + { + FromClause = null; + } + } + else if (component == "where") + { + Conditions.Clear(engineCode); + } + else + { + _clauses.RemoveAll(x => x.Component == component && (engineCode == null || x.Engine == null || engineCode == x.Engine)); + } + + return this; + } + + public Join Or() + { + orFlag = true; + return this; + } + + public Join Not(bool flag = true) + { + notFlag = flag; + return this; + } + + public bool GetOr() + { + var ret = orFlag; + orFlag = false; + return ret; + } + + public bool GetNot() + { + var ret = notFlag; + notFlag = false; + return ret; + } + + public void SetOr(bool value) => orFlag = value; + public void SetNot(bool value) => notFlag = value; + + void IConditional.AddComponent(string component, AbstractClause clause, string engineCode) + { + Conditions.AddComponent("where", clause, engineCode); + } + + IConditional IConditional.NewChild() => new Join { EngineScope = this.EngineScope }; + + public Join On(string first, string second, string op = "=") => this.WhereColumns(first, op, second); + public Join OrOn(string first, string second, string op = "=") => this.Or().WhereColumns(first, op, second); } } diff --git a/QueryBuilder/Query.Aggregate.cs b/QueryBuilder/Query.Aggregate.cs index d4fc5057..2c51261a 100644 --- a/QueryBuilder/Query.Aggregate.cs +++ b/QueryBuilder/Query.Aggregate.cs @@ -10,11 +10,12 @@ public Query AsAggregate(string type, string[] columns = null) Method = "aggregate"; - this.ClearComponent("aggregate") - .AddComponent("aggregate", new AggregateClause + AggregateClauses.Clear(); + AggregateClauses.Add(new AggregateClause { Type = type, - Columns = columns?.ToList() ?? new List(), + Columns = columns?.ToList() ?? [], + Engine = EngineScope }); return this; @@ -22,19 +23,19 @@ public Query AsAggregate(string type, string[] columns = null) public Query AsCount(string[] columns = null) { - var cols = columns?.ToList() ?? new List { }; + var cols = columns?.ToList() ?? []; - if (!cols.Any()) + if (cols.Count == 0) { cols.Add("*"); } - return AsAggregate("count", cols.ToArray()); + return AsAggregate("count", [.. cols]); } public Query AsAvg(string column) { - return AsAggregate("avg", new string[] { column }); + return AsAggregate("avg", [column]); } public Query AsAverage(string column) { @@ -43,17 +44,17 @@ public Query AsAverage(string column) public Query AsSum(string column) { - return AsAggregate("sum", new[] { column }); + return AsAggregate("sum", [column]); } public Query AsMax(string column) { - return AsAggregate("max", new[] { column }); + return AsAggregate("max", [column]); } public Query AsMin(string column) { - return AsAggregate("min", new[] { column }); + return AsAggregate("min", [column]); } } } diff --git a/QueryBuilder/Query.Combine.cs b/QueryBuilder/Query.Combine.cs index b7238264..baf9bc43 100644 --- a/QueryBuilder/Query.Combine.cs +++ b/QueryBuilder/Query.Combine.cs @@ -13,12 +13,15 @@ public Query Combine(string operation, bool all, Query query) throw new InvalidOperationException("Only select queries can be combined."); } - return AddComponent("combine", new Combine + CombineClauses.Add(new Combine { Query = query, Operation = operation, All = all, + Engine = EngineScope }); + + return this; } public Query CombineRaw(string sql, params object[] bindings) @@ -28,11 +31,14 @@ public Query CombineRaw(string sql, params object[] bindings) throw new InvalidOperationException("Only select queries can be combined."); } - return AddComponent("combine", new RawCombine + CombineClauses.Add(new RawCombine { Expression = sql, Bindings = bindings, + Engine = EngineScope }); + + return this; } public Query Union(Query query, bool all = false) diff --git a/QueryBuilder/Query.From.cs b/QueryBuilder/Query.From.cs new file mode 100644 index 00000000..574d8e34 --- /dev/null +++ b/QueryBuilder/Query.From.cs @@ -0,0 +1,51 @@ +using System; + +namespace SqlKata +{ + public partial class Query + { + /// + /// Add a from Clause + /// + /// + /// + public Query From(string table) + { + return AddOrReplace(FromClauses, new FromClause + { + Table = table, + }); + } + + public Query From(Query query, string alias = null) + { + query = query.Clone(); + + if (alias != null) + { + query.As(alias); + } + + return AddOrReplace(FromClauses, new QueryFromClause + { + Query = query + }); + } + + public Query FromRaw(string sql, params object[] bindings) + { + return AddOrReplace(FromClauses, new RawFromClause + { + Expression = sql, + Bindings = bindings, + }); + } + + public Query From(Func callback, string alias = null) + { + var query = NewQuery(); + + return From(callback.Invoke(query), alias); + } + } +} diff --git a/QueryBuilder/Query.Having.cs b/QueryBuilder/Query.Having.cs index ee367941..27c27b78 100644 --- a/QueryBuilder/Query.Having.cs +++ b/QueryBuilder/Query.Having.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Reflection; namespace SqlKata { @@ -9,648 +7,554 @@ 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(), - }); + Havings.Where(column, op, value); + return this; } public Query HavingNot(string column, string op, object value) { - return Not().Having(column, op, value); + Havings.WhereNot(column, op, value); + return this; } public Query OrHaving(string column, string op, object value) { - return Or().Having(column, op, value); + Havings.OrWhere(column, op, value); + return this; } public Query OrHavingNot(string column, string op, object value) { - return this.Or().Not().Having(column, op, value); + Havings.OrWhereNot(column, op, value); + return this; } - public Query Having(string column, object value) + public Query Having(string column, object value) => Having(column, "=", value); + public Query HavingNot(string column, object value) => HavingNot(column, "=", value); + public Query OrHaving(string column, object value) => OrHaving(column, "=", value); + public Query OrHavingNot(string column, object value) => OrHavingNot(column, "=", value); + + public Query Having(object constraints) { - return Having(column, "=", value); + Havings.Where(constraints); + return this; } - public Query HavingNot(string column, object value) + + public Query Having(IEnumerable> values) { - return HavingNot(column, "=", value); + Havings.Where(values); + return this; } - public Query OrHaving(string column, object value) + + public Query HavingRaw(string sql, params object[] bindings) { - return OrHaving(column, "=", value); + Havings.WhereRaw(sql, bindings); + return this; } - public Query OrHavingNot(string column, object value) + + public Query OrHavingRaw(string sql, params object[] bindings) { - return OrHavingNot(column, "=", value); + Havings.OrWhereRaw(sql, bindings); + return this; } - /// - /// Perform a Having constraint - /// - /// - /// - public Query Having(object constraints) + public Query Having(Func callback) { - var dictionary = new Dictionary(); + var query = callback.Invoke(NewQuery()); - foreach (var item in constraints.GetType().GetRuntimeProperties()) + if (query.Havings.HasConditions(EngineScope)) { - dictionary.Add(item.Name, item.GetValue(constraints)); + Havings.Clauses.Add(new NestedCondition + { + Conditions = query.Havings, + IsNot = Havings.GetNot(), + IsOr = Havings.GetOr(), + Engine = EngineScope, + Component = "having" + }); } - return Having(dictionary); + return this; } - public Query Having(IEnumerable> values) + public Query Having(ConditionGroup group) { - var query = this; - var orFlag = GetOr(); - var notFlag = GetNot(); - - foreach (var tuple in values) + if (group.HasConditions(EngineScope)) { - if (orFlag) - { - query = query.Or(); - } - else + Havings.Clauses.Add(new NestedCondition { - query.And(); - } - - query = this.Not(notFlag).Having(tuple.Key, tuple.Value); + Conditions = group, + IsNot = Havings.GetNot(), + IsOr = Havings.GetOr(), + Engine = EngineScope, + Component = "having" + }); } - return query; + return this; } - public Query HavingRaw(string sql, params object[] bindings) + public Query HavingNot(Func callback) { - return AddComponent("having", new RawCondition - { - Expression = sql, - Bindings = bindings, - IsOr = GetOr(), - IsNot = GetNot(), - }); + Havings.Not(); + return Having(callback); } - public Query OrHavingRaw(string sql, params object[] bindings) + public Query HavingNot(ConditionGroup group) { - return Or().HavingRaw(sql, bindings); + Havings.Not(); + return Having(group); } - /// - /// Apply a nested Having clause - /// - /// - /// - public Query Having(Func callback) + public Query OrHaving(Func callback) { - var query = callback.Invoke(NewChild()); - - return AddComponent("having", new NestedCondition - { - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); + Havings.Or(); + return Having(callback); } - public Query HavingNot(Func callback) + public Query OrHaving(ConditionGroup group) { - return Not().Having(callback); + Havings.Or(); + return Having(group); } - public Query OrHaving(Func callback) + public Query OrHavingNot(Func callback) { - return Or().Having(callback); + Havings.Or().Not(); + return Having(callback); } - public Query OrHavingNot(Func callback) + public Query OrHavingNot(ConditionGroup group) { - return Not().Or().Having(callback); + Havings.Or().Not(); + return Having(group); } public Query HavingColumns(string first, string op, string second) { - return AddComponent("having", new TwoColumnsCondition - { - First = first, - Second = second, - Operator = op, - IsOr = GetOr(), - IsNot = GetNot(), - }); + Havings.WhereColumns(first, op, second); + return this; } public Query OrHavingColumns(string first, string op, string second) { - return Or().HavingColumns(first, op, second); + Havings.OrWhereColumns(first, op, second); + return this; } public Query HavingNull(string column) { - return AddComponent("having", new NullCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - }); + Havings.WhereNull(column); + return this; } public Query HavingNotNull(string column) { - return Not().HavingNull(column); + Havings.WhereNotNull(column); + return this; } public Query OrHavingNull(string column) { - return this.Or().HavingNull(column); + Havings.OrWhereNull(column); + return this; } public Query OrHavingNotNull(string column) { - return Or().Not().HavingNull(column); + Havings.OrWhereNotNull(column); + return this; } public Query HavingTrue(string column) { - return AddComponent("having", new BooleanCondition - { - Column = column, - Value = true, - }); + Havings.WhereTrue(column); + return this; } - public Query OrHavingTrue(string column) + public Query HavingFalse(string column) { - return Or().HavingTrue(column); + Havings.WhereFalse(column); + return this; } - public Query HavingFalse(string column) + public Query OrHavingTrue(string column) { - return AddComponent("having", new BooleanCondition - { - Column = column, - Value = false, - }); + Havings.OrWhereTrue(column); + return this; } public Query OrHavingFalse(string column) { - return Or().HavingFalse(column); + Havings.OrWhereFalse(column); + return this; } 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(), - }); + Havings.WhereLike(column, value, caseSensitive, escapeCharacter); + return this; } public Query HavingNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Not().HavingLike(column, value, caseSensitive, escapeCharacter); + Havings.WhereNotLike(column, value, caseSensitive, escapeCharacter); + return this; } public Query OrHavingLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Or().HavingLike(column, value, caseSensitive, escapeCharacter); + Havings.OrWhereLike(column, value, caseSensitive, escapeCharacter); + return this; } public Query OrHavingNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Or().Not().HavingLike(column, value, caseSensitive, escapeCharacter); + Havings.OrWhereNotLike(column, value, caseSensitive, escapeCharacter); + return this; } + 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(), - }); + Havings.WhereStarts(column, value, caseSensitive, escapeCharacter); + return this; } public Query HavingNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Not().HavingStarts(column, value, caseSensitive, escapeCharacter); + Havings.WhereNotStarts(column, value, caseSensitive, escapeCharacter); + return this; } public Query OrHavingStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Or().HavingStarts(column, value, caseSensitive, escapeCharacter); + Havings.OrWhereStarts(column, value, caseSensitive, escapeCharacter); + return this; } public Query OrHavingNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Or().Not().HavingStarts(column, value, caseSensitive, escapeCharacter); + Havings.OrWhereNotStarts(column, value, caseSensitive, escapeCharacter); + return this; } 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(), - }); + Havings.WhereEnds(column, value, caseSensitive, escapeCharacter); + return this; } public Query HavingNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Not().HavingEnds(column, value, caseSensitive, escapeCharacter); + Havings.WhereNotEnds(column, value, caseSensitive, escapeCharacter); + return this; } public Query OrHavingEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Or().HavingEnds(column, value, caseSensitive, escapeCharacter); + Havings.OrWhereEnds(column, value, caseSensitive, escapeCharacter); + return this; } public Query OrHavingNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Or().Not().HavingEnds(column, value, caseSensitive, escapeCharacter); + Havings.OrWhereNotEnds(column, value, caseSensitive, escapeCharacter); + return this; } 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(), - }); + Havings.WhereContains(column, value, caseSensitive, escapeCharacter); + return this; } public Query HavingNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Not().HavingContains(column, value, caseSensitive, escapeCharacter); + Havings.WhereNotContains(column, value, caseSensitive, escapeCharacter); + return this; } public Query OrHavingContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Or().HavingContains(column, value, caseSensitive, escapeCharacter); + Havings.OrWhereContains(column, value, caseSensitive, escapeCharacter); + return this; } public Query OrHavingNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) { - return Or().Not().HavingContains(column, value, caseSensitive, escapeCharacter); + Havings.OrWhereNotContains(column, value, caseSensitive, escapeCharacter); + return this; } public Query HavingBetween(string column, T lower, T higher) { - return AddComponent("having", new BetweenCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Lower = lower, - Higher = higher - }); + Havings.WhereBetween(column, lower, higher); + return this; } - public Query OrHavingBetween(string column, T lower, T higher) + public Query HavingNotBetween(string column, T lower, T higher) { - return Or().HavingBetween(column, lower, higher); + Havings.WhereNotBetween(column, lower, higher); + return this; } - public Query HavingNotBetween(string column, T lower, T higher) + + public Query OrHavingBetween(string column, T lower, T higher) { - return Not().HavingBetween(column, lower, higher); + Havings.OrWhereBetween(column, lower, higher); + return this; } + public Query OrHavingNotBetween(string column, T lower, T higher) { - return Or().Not().HavingBetween(column, lower, higher); + Havings.OrWhereNotBetween(column, lower, higher); + return this; } 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() - }); - - + Havings.WhereIn(column, values); + return this; } - public Query OrHavingIn(string column, IEnumerable values) + public Query HavingNotIn(string column, IEnumerable values) { - return Or().HavingIn(column, values); + Havings.WhereNotIn(column, values); + return this; } - public Query HavingNotIn(string column, IEnumerable values) + public Query OrHavingIn(string column, IEnumerable values) { - return Not().HavingIn(column, values); + Havings.OrWhereIn(column, values); + return this; } public Query OrHavingNotIn(string column, IEnumerable values) { - return Or().Not().HavingIn(column, values); + Havings.OrWhereNotIn(column, values); + return this; } - public Query HavingIn(string column, Query query) { - return AddComponent("having", new InQueryCondition - { - Column = column, - IsOr = GetOr(), - IsNot = GetNot(), - Query = query, - }); + Havings.WhereIn(column, query); + return this; } + public Query HavingIn(string column, Func callback) { - var query = callback.Invoke(new Query()); - - return HavingIn(column, query); + return HavingIn(column, callback.Invoke(NewQuery())); } - public Query OrHavingIn(string column, Query query) + public Query HavingNotIn(string column, Query query) { - return Or().HavingIn(column, query); + Havings.WhereNotIn(column, query); + return this; } - public Query OrHavingIn(string column, Func callback) + public Query HavingNotIn(string column, Func callback) { - return Or().HavingIn(column, callback); + return HavingNotIn(column, callback.Invoke(NewQuery())); } - public Query HavingNotIn(string column, Query query) + + public Query OrHavingIn(string column, Query query) { - return Not().HavingIn(column, query); + Havings.OrWhereIn(column, query); + return this; } - public Query HavingNotIn(string column, Func callback) + public Query OrHavingIn(string column, Func callback) { - return Not().HavingIn(column, callback); + return OrHavingIn(column, callback.Invoke(NewQuery())); } public Query OrHavingNotIn(string column, Query query) { - return Or().Not().HavingIn(column, query); + Havings.OrWhereNotIn(column, query); + return this; } public Query OrHavingNotIn(string column, Func callback) { - return Or().Not().HavingIn(column, callback); + return OrHavingNotIn(column, callback.Invoke(NewQuery())); } + public Query Having(string column, string op, Query query) + { + Havings.Where(column, op, query); + return this; + } - /// - /// Perform a sub query Having clause - /// - /// - /// - /// - /// public Query Having(string column, string op, Func callback) { - var query = callback.Invoke(NewChild()); + return Having(column, op, callback.Invoke(NewQuery())); + } - return Having(column, op, query); + public Query OrHaving(string column, string op, Query query) + { + Havings.OrWhere(column, op, query); + return this; } - public Query Having(string column, string op, Query query) + public Query OrHaving(string column, string op, Func callback) { - return AddComponent("having", new QueryCondition - { - Column = column, - Operator = op, - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); + return OrHaving(column, op, callback.Invoke(NewQuery())); } - public Query OrHaving(string column, string op, Query query) + public Query HavingSub(Query query, object value) { - return Or().Having(column, op, query); + Havings.WhereSub(query, value); + return this; } - public Query OrHaving(string column, string op, Func callback) + + public Query HavingSub(Query query, string op, object value) { - return Or().Having(column, op, callback); + Havings.WhereSub(query, op, value); + return this; } - public Query HavingExists(Query query) + public Query OrHavingSub(Query query, object value) { - if (!query.HasComponent("from")) - { - throw new ArgumentException($"{nameof(FromClause)} cannot be empty if used inside a {nameof(HavingExists)} condition"); - } + Havings.OrWhereSub(query, value); + return this; + } - // simplify the query as much as possible - query = query.Clone().ClearComponent("select") - .SelectRaw("1") - .Limit(1); + public Query OrHavingSub(Query query, string op, object value) + { + Havings.OrWhereSub(query, op, value); + return this; + } - return AddComponent("having", new ExistsCondition - { - Query = query, - IsNot = GetNot(), - IsOr = GetOr(), - }); + public Query HavingExists(Query query) + { + Havings.WhereExists(query); + return this; } + public Query HavingExists(Func callback) { - var childQuery = new Query().SetParent(this); - return HavingExists(callback.Invoke(childQuery)); + return HavingExists(callback.Invoke(NewQuery())); } public Query HavingNotExists(Query query) { - return Not().HavingExists(query); + Havings.WhereNotExists(query); + return this; } public Query HavingNotExists(Func callback) { - return Not().HavingExists(callback); + return HavingNotExists(callback.Invoke(NewQuery())); } public Query OrHavingExists(Query query) { - return Or().HavingExists(query); + Havings.OrWhereExists(query); + return this; } + public Query OrHavingExists(Func callback) { - return Or().HavingExists(callback); + return OrHavingExists(callback.Invoke(NewQuery())); } + public Query OrHavingNotExists(Query query) { - return Or().Not().HavingExists(query); + Havings.OrWhereNotExists(query); + return this; } + public Query OrHavingNotExists(Func callback) { - return Or().Not().HavingExists(callback); + return OrHavingNotExists(callback.Invoke(NewQuery())); } - #region date + #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(), - }); + Havings.WhereDatePart(part, column, op, value); + return this; } + public Query HavingNotDatePart(string part, string column, string op, object value) { - return Not().HavingDatePart(part, column, op, value); + Havings.WhereNotDatePart(part, column, op, value); + return this; } public Query OrHavingDatePart(string part, string column, string op, object value) { - return Or().HavingDatePart(part, column, op, value); + Havings.OrWhereDatePart(part, column, op, value); + return this; } public Query OrHavingNotDatePart(string part, string column, string op, object value) { - return Or().Not().HavingDatePart(part, column, op, value); + Havings.OrWhereNotDatePart(part, column, op, value); + return this; } public Query HavingDate(string column, string op, object value) { - return HavingDatePart("date", column, op, value); + Havings.WhereDate(column, op, value); + return this; } + public Query HavingNotDate(string column, string op, object value) { - return Not().HavingDate(column, op, value); + Havings.WhereNotDate(column, op, value); + return this; } + public Query OrHavingDate(string column, string op, object value) { - return Or().HavingDate(column, op, value); + Havings.OrWhereDate(column, op, value); + return this; } + public Query OrHavingNotDate(string column, string op, object value) { - return Or().Not().HavingDate(column, op, value); + Havings.OrWhereNotDate(column, op, value); + return this; } 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); + Havings.WhereTime(column, op, value); + return this; } - public Query HavingDatePart(string part, string column, object value) - { - return HavingDatePart(part, column, "=", value); - } - public Query HavingNotDatePart(string part, string column, object value) + public Query HavingNotTime(string column, string op, object value) { - return HavingNotDatePart(part, column, "=", value); + Havings.WhereNotTime(column, op, value); + return this; } - public Query OrHavingDatePart(string part, string column, object value) + public Query OrHavingTime(string column, string op, object value) { - return OrHavingDatePart(part, column, "=", value); + Havings.OrWhereTime(column, op, value); + return this; } - public Query OrHavingNotDatePart(string part, string column, object value) + public Query OrHavingNotTime(string column, string op, object value) { - return OrHavingNotDatePart(part, column, "=", value); + Havings.OrWhereNotTime(column, op, value); + return this; } - 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 HavingDatePart(string part, string column, object value) => HavingDatePart(part, column, "=", value); + public Query HavingNotDatePart(string part, string column, object value) => HavingNotDatePart(part, column, "=", value); + public Query OrHavingDatePart(string part, string column, object value) => OrHavingDatePart(part, column, "=", value); + public Query OrHavingNotDatePart(string part, string column, object value) => OrHavingNotDatePart(part, 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); - } + public Query HavingDate(string column, object value) => HavingDate(column, "=", value); + public Query HavingNotDate(string column, object value) => HavingNotDate(column, "=", value); + public Query OrHavingDate(string column, object value) => OrHavingDate(column, "=", value); + public Query OrHavingNotDate(string column, object value) => OrHavingNotDate(column, "=", value); + public Query HavingTime(string column, object value) => HavingTime(column, "=", value); + public Query HavingNotTime(string column, object value) => HavingNotTime(column, "=", value); + public Query OrHavingTime(string column, object value) => OrHavingTime(column, "=", value); + public Query OrHavingNotTime(string column, object value) => OrHavingNotTime(column, "=", value); #endregion } } diff --git a/QueryBuilder/Query.Insert.cs b/QueryBuilder/Query.Insert.cs index dbec60af..04fcf4e0 100644 --- a/QueryBuilder/Query.Insert.cs +++ b/QueryBuilder/Query.Insert.cs @@ -31,10 +31,12 @@ public Query AsInsert(IEnumerable columns, IEnumerable values) Method = "insert"; - ClearComponent("insert").AddComponent("insert", new InsertClause + InsertClauses.ClearForEngine(EngineScope); + InsertClauses.Add(new InsertClause { Columns = columnsList, - Values = valuesList + Values = valuesList, + Engine = EngineScope }); return this; @@ -49,11 +51,13 @@ public Query AsInsert(IEnumerable> values, bool ret Method = "insert"; - ClearComponent("insert").AddComponent("insert", new InsertClause + InsertClauses.ClearForEngine(EngineScope); + InsertClauses.Add(new InsertClause { Columns = values.Select(x => x.Key).ToList(), Values = values.Select(x => x.Value).ToList(), ReturnId = returnId, + Engine = EngineScope }); return this; @@ -77,7 +81,7 @@ public Query AsInsert(IEnumerable columns, IEnumerable columns, IEnumerable columns, Query query) { Method = "insert"; - ClearComponent("insert").AddComponent("insert", new InsertQueryClause + InsertClauses.ClearForEngine(EngineScope); + InsertClauses.Add(new InsertQueryClause { Columns = columns.ToList(), Query = query.Clone(), + Engine = EngineScope }); return this; diff --git a/QueryBuilder/Query.Join.cs b/QueryBuilder/Query.Join.cs index 3fe3bbb1..55929816 100644 --- a/QueryBuilder/Query.Join.cs +++ b/QueryBuilder/Query.Join.cs @@ -9,10 +9,15 @@ private Query Join(Func callback) { var join = callback.Invoke(new Join().AsInner()); - return AddComponent("join", new BaseJoin + var baseJoin = new BaseJoin { - Join = join - }); + Join = join, + Component = "join", + Engine = EngineScope + }; + Joins.Add(baseJoin); + + return this; } public Query Join( diff --git a/QueryBuilder/Query.Select.cs b/QueryBuilder/Query.Select.cs index 9502c024..3f85e763 100644 --- a/QueryBuilder/Query.Select.cs +++ b/QueryBuilder/Query.Select.cs @@ -24,9 +24,10 @@ public Query Select(IEnumerable columns) foreach (var column in columns) { - AddComponent("select", new Column + SelectClauses.Add(new Column { - Name = column + Name = column, + Engine = EngineScope }); } @@ -41,10 +42,11 @@ public Query SelectRaw(string sql, params object[] bindings) { Method = "select"; - AddComponent("select", new RawColumn + SelectClauses.Add(new RawColumn { Expression = sql, Bindings = bindings, + Engine = EngineScope }); return this; @@ -56,9 +58,10 @@ public Query Select(Query query, string alias) query = query.Clone(); - AddComponent("select", new QueryColumn + SelectClauses.Add(new QueryColumn { Query = query.As(alias), + Engine = EngineScope }); return this; @@ -66,18 +69,19 @@ public Query Select(Query query, string alias) public Query Select(Func callback, string alias) { - return Select(callback.Invoke(NewChild()), alias); + return Select(callback.Invoke(NewQuery()), alias); } public Query SelectAggregate(string aggregate, string column, Query filter = null) { Method = "select"; - AddComponent("select", new AggregatedColumn + SelectClauses.Add(new AggregatedColumn { Column = new Column { Name = column }, Aggregate = aggregate, Filter = filter, + Engine = EngineScope }); return this; @@ -90,7 +94,7 @@ public Query SelectAggregate(string aggregate, string column, Func return SelectAggregate(aggregate, column); } - return SelectAggregate(aggregate, column, filter.Invoke(NewChild())); + return SelectAggregate(aggregate, column, filter.Invoke(NewQuery())); } public Query SelectSum(string column, Func filter = null) diff --git a/QueryBuilder/Query.Update.cs b/QueryBuilder/Query.Update.cs index d88aeb00..9d596c8a 100644 --- a/QueryBuilder/Query.Update.cs +++ b/QueryBuilder/Query.Update.cs @@ -28,10 +28,12 @@ public Query AsUpdate(IEnumerable columns, IEnumerable values) Method = "update"; - ClearComponent("update").AddComponent("update", new InsertClause + UpdateClauses.ClearForEngine(EngineScope); + UpdateClauses.Add(new InsertClause { Columns = columns.ToList(), - Values = values.ToList() + Values = values.ToList(), + Engine = EngineScope }); return this; @@ -46,10 +48,12 @@ public Query AsUpdate(IEnumerable> values) Method = "update"; - ClearComponent("update").AddComponent("update", new InsertClause + UpdateClauses.ClearForEngine(EngineScope); + UpdateClauses.Add(new InsertClause { Columns = values.Select(x => x.Key).ToList(), Values = values.Select(x => x.Value).ToList(), + Engine = EngineScope }); return this; @@ -58,13 +62,11 @@ public Query AsUpdate(IEnumerable> values) public Query AsIncrement(string column, int value = 1) { Method = "update"; - AddOrReplaceComponent("update", new IncrementClause + return AddOrReplace(UpdateClauses, new IncrementClause { Column = column, Value = value }); - - return this; } public Query AsDecrement(string column, int value = 1) diff --git a/QueryBuilder/Query.Where.cs b/QueryBuilder/Query.Where.cs new file mode 100644 index 00000000..ba24e7f9 --- /dev/null +++ b/QueryBuilder/Query.Where.cs @@ -0,0 +1,560 @@ +using System; +using System.Collections.Generic; + +namespace SqlKata +{ + public partial class Query + { + public Query Where(string column, string op, object value) + { + Wheres.Where(column, op, value); + return this; + } + + public Query WhereNot(string column, string op, object value) + { + Wheres.WhereNot(column, op, value); + return this; + } + + public Query OrWhere(string column, string op, object value) + { + Wheres.OrWhere(column, op, value); + return this; + } + + public Query OrWhereNot(string column, string op, object value) + { + Wheres.OrWhereNot(column, op, value); + return this; + } + + public Query Where(string column, object value) => Where(column, "=", value); + public Query WhereNot(string column, object value) => WhereNot(column, "=", value); + public Query OrWhere(string column, object value) => OrWhere(column, "=", value); + public Query OrWhereNot(string column, object value) => OrWhereNot(column, "=", value); + + public Query Where(object constraints) + { + Wheres.Where(constraints); + return this; + } + + public Query Where(IEnumerable> values) + { + Wheres.Where(values); + return this; + } + + public Query WhereRaw(string sql, params object[] bindings) + { + Wheres.WhereRaw(sql, bindings); + return this; + } + + public Query OrWhereRaw(string sql, params object[] bindings) + { + Wheres.OrWhereRaw(sql, bindings); + return this; + } + + public Query Where(Func callback) + { + var query = callback.Invoke(NewQuery()); + + if (query.Wheres.HasConditions(EngineScope)) + { + Wheres.Clauses.Add(new NestedCondition + { + Conditions = query.Wheres, + IsNot = Wheres.GetNot(), + IsOr = Wheres.GetOr(), + Engine = EngineScope, + Component = "where" + }); + } + + return this; + } + + public Query Where(ConditionGroup group) + { + if (group.HasConditions(EngineScope)) + { + Wheres.Clauses.Add(new NestedCondition + { + Conditions = group, + IsNot = Wheres.GetNot(), + IsOr = Wheres.GetOr(), + Engine = EngineScope, + Component = "where" + }); + } + + return this; + } + + public Query WhereNot(Func callback) + { + Wheres.Not(); + return Where(callback); + } + + public Query WhereNot(ConditionGroup group) + { + Wheres.Not(); + return Where(group); + } + + public Query OrWhere(Func callback) + { + Wheres.Or(); + return Where(callback); + } + + public Query OrWhere(ConditionGroup group) + { + Wheres.Or(); + return Where(group); + } + + public Query OrWhereNot(Func callback) + { + Wheres.Or().Not(); + return Where(callback); + } + + public Query OrWhereNot(ConditionGroup group) + { + Wheres.Or().Not(); + return Where(group); + } + + public Query WhereColumns(string first, string op, string second) + { + Wheres.WhereColumns(first, op, second); + return this; + } + + public Query OrWhereColumns(string first, string op, string second) + { + Wheres.OrWhereColumns(first, op, second); + return this; + } + + public Query WhereNull(string column) + { + Wheres.WhereNull(column); + return this; + } + + public Query WhereNotNull(string column) + { + Wheres.WhereNotNull(column); + return this; + } + + public Query OrWhereNull(string column) + { + Wheres.OrWhereNull(column); + return this; + } + + public Query OrWhereNotNull(string column) + { + Wheres.OrWhereNotNull(column); + return this; + } + + public Query WhereTrue(string column) + { + Wheres.WhereTrue(column); + return this; + } + + public Query WhereFalse(string column) + { + Wheres.WhereFalse(column); + return this; + } + + public Query OrWhereTrue(string column) + { + Wheres.OrWhereTrue(column); + return this; + } + + public Query OrWhereFalse(string column) + { + Wheres.OrWhereFalse(column); + return this; + } + + public Query WhereLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.WhereLike(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query WhereNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.WhereNotLike(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query OrWhereLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.OrWhereLike(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query OrWhereNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.OrWhereNotLike(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query WhereStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.WhereStarts(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query WhereNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.WhereNotStarts(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query OrWhereStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.OrWhereStarts(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query OrWhereNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.OrWhereNotStarts(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query WhereEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.WhereEnds(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query WhereNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.WhereNotEnds(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query OrWhereEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.OrWhereEnds(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query OrWhereNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.OrWhereNotEnds(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query WhereContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.WhereContains(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query WhereNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.WhereNotContains(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query OrWhereContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.OrWhereContains(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query OrWhereNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null) + { + Wheres.OrWhereNotContains(column, value, caseSensitive, escapeCharacter); + return this; + } + + public Query WhereBetween(string column, T lower, T higher) + { + Wheres.WhereBetween(column, lower, higher); + return this; + } + + public Query WhereNotBetween(string column, T lower, T higher) + { + Wheres.WhereNotBetween(column, lower, higher); + return this; + } + + public Query OrWhereBetween(string column, T lower, T higher) + { + Wheres.OrWhereBetween(column, lower, higher); + return this; + } + + public Query OrWhereNotBetween(string column, T lower, T higher) + { + Wheres.OrWhereNotBetween(column, lower, higher); + return this; + } + + public Query WhereIn(string column, IEnumerable values) + { + Wheres.WhereIn(column, values); + return this; + } + + public Query WhereNotIn(string column, IEnumerable values) + { + Wheres.WhereNotIn(column, values); + return this; + } + + public Query OrWhereIn(string column, IEnumerable values) + { + Wheres.OrWhereIn(column, values); + return this; + } + + public Query OrWhereNotIn(string column, IEnumerable values) + { + Wheres.OrWhereNotIn(column, values); + return this; + } + + public Query WhereIn(string column, Query query) + { + Wheres.WhereIn(column, query); + return this; + } + + public Query WhereIn(string column, Func callback) + { + return WhereIn(column, callback.Invoke(NewQuery())); + } + + public Query WhereNotIn(string column, Query query) + { + Wheres.WhereNotIn(column, query); + return this; + } + + public Query WhereNotIn(string column, Func callback) + { + return WhereNotIn(column, callback.Invoke(NewQuery())); + } + + public Query OrWhereIn(string column, Query query) + { + Wheres.OrWhereIn(column, query); + return this; + } + + public Query OrWhereIn(string column, Func callback) + { + return OrWhereIn(column, callback.Invoke(NewQuery())); + } + + public Query OrWhereNotIn(string column, Query query) + { + Wheres.OrWhereNotIn(column, query); + return this; + } + + public Query OrWhereNotIn(string column, Func callback) + { + return OrWhereNotIn(column, callback.Invoke(NewQuery())); + } + + public Query Where(string column, string op, Query query) + { + Wheres.Where(column, op, query); + return this; + } + + public Query Where(string column, string op, Func callback) + { + return Where(column, op, callback.Invoke(NewQuery())); + } + + public Query OrWhere(string column, string op, Query query) + { + Wheres.OrWhere(column, op, query); + return this; + } + + public Query OrWhere(string column, string op, Func callback) + { + return OrWhere(column, op, callback.Invoke(NewQuery())); + } + + public Query WhereSub(Query query, object value) + { + Wheres.WhereSub(query, value); + return this; + } + + public Query WhereSub(Query query, string op, object value) + { + Wheres.WhereSub(query, op, value); + return this; + } + + public Query OrWhereSub(Query query, object value) + { + Wheres.OrWhereSub(query, value); + return this; + } + + public Query OrWhereSub(Query query, string op, object value) + { + Wheres.OrWhereSub(query, op, value); + return this; + } + + public Query WhereExists(Query query) + { + Wheres.WhereExists(query); + return this; + } + + public Query WhereExists(Func callback) + { + return WhereExists(callback.Invoke(NewQuery())); + } + + public Query WhereNotExists(Query query) + { + Wheres.WhereNotExists(query); + return this; + } + + public Query WhereNotExists(Func callback) + { + return WhereNotExists(callback.Invoke(NewQuery())); + } + + public Query OrWhereExists(Query query) + { + Wheres.OrWhereExists(query); + return this; + } + + public Query OrWhereExists(Func callback) + { + return OrWhereExists(callback.Invoke(NewQuery())); + } + + public Query OrWhereNotExists(Query query) + { + Wheres.OrWhereNotExists(query); + return this; + } + + public Query OrWhereNotExists(Func callback) + { + return OrWhereNotExists(callback.Invoke(NewQuery())); + } + + #region Date + public Query WhereDatePart(string part, string column, string op, object value) + { + Wheres.WhereDatePart(part, column, op, value); + return this; + } + + public Query WhereNotDatePart(string part, string column, string op, object value) + { + Wheres.WhereNotDatePart(part, column, op, value); + return this; + } + + public Query OrWhereDatePart(string part, string column, string op, object value) + { + Wheres.OrWhereDatePart(part, column, op, value); + return this; + } + + public Query OrWhereNotDatePart(string part, string column, string op, object value) + { + Wheres.OrWhereNotDatePart(part, column, op, value); + return this; + } + + public Query WhereDate(string column, string op, object value) + { + Wheres.WhereDate(column, op, value); + return this; + } + + public Query WhereNotDate(string column, string op, object value) + { + Wheres.WhereNotDate(column, op, value); + return this; + } + + public Query OrWhereDate(string column, string op, object value) + { + Wheres.OrWhereDate(column, op, value); + return this; + } + + public Query OrWhereNotDate(string column, string op, object value) + { + Wheres.OrWhereNotDate(column, op, value); + return this; + } + + public Query WhereTime(string column, string op, object value) + { + Wheres.WhereTime(column, op, value); + return this; + } + + public Query WhereNotTime(string column, string op, object value) + { + Wheres.WhereNotTime(column, op, value); + return this; + } + + public Query OrWhereTime(string column, string op, object value) + { + Wheres.OrWhereTime(column, op, value); + return this; + } + + public Query OrWhereNotTime(string column, string op, object value) + { + Wheres.OrWhereNotTime(column, op, value); + return this; + } + + public Query WhereDatePart(string part, string column, object value) => WhereDatePart(part, column, "=", value); + public Query WhereNotDatePart(string part, string column, object value) => WhereNotDatePart(part, column, "=", value); + public Query OrWhereDatePart(string part, string column, object value) => OrWhereDatePart(part, column, "=", value); + public Query OrWhereNotDatePart(string part, string column, object value) => OrWhereNotDatePart(part, column, "=", value); + + public Query WhereDate(string column, object value) => WhereDate(column, "=", value); + public Query WhereNotDate(string column, object value) => WhereNotDate(column, "=", value); + public Query OrWhereDate(string column, object value) => OrWhereDate(column, "=", value); + public Query OrWhereNotDate(string column, object value) => OrWhereNotDate(column, "=", value); + + public Query WhereTime(string column, object value) => WhereTime(column, "=", value); + public Query WhereNotTime(string column, object value) => WhereNotTime(column, "=", value); + public Query OrWhereTime(string column, object value) => OrWhereTime(column, "=", value); + public Query OrWhereNotTime(string column, object value) => OrWhereNotTime(column, "=", value); + #endregion + } +} diff --git a/QueryBuilder/Query.cs b/QueryBuilder/Query.cs index 8f5bd22f..d1ff0671 100755 --- a/QueryBuilder/Query.cs +++ b/QueryBuilder/Query.cs @@ -6,26 +6,150 @@ namespace SqlKata { - public partial class Query : BaseQuery + public partial class Query { + private ConditionGroup _wheres; + public ConditionGroup Wheres + { + get => _wheres; + set + { + _wheres = value; + if (_wheres != null) + { + _wheres.OnGetOr = () => Havings?.SetOr(false); + _wheres.OnGetNot = () => Havings?.SetNot(false); + } + } + } + + private ConditionGroup _havings; + public ConditionGroup Havings + { + get => _havings; + set + { + _havings = value; + if (_havings != null) + { + _havings.OnGetOr = () => Wheres?.SetOr(false); + _havings.OnGetNot = () => Wheres?.SetNot(false); + } + } + } + + public List Joins { get; set; } = []; + public List FromClauses { get; set; } = []; + public List SelectClauses { get; set; } = []; + public List CteClauses { get; set; } = []; + public List OrderByClauses { get; set; } = []; + public List GroupByClauses { get; set; } = []; + public List LimitClauses { get; set; } = []; + public List OffsetClauses { get; set; } = []; + public List AggregateClauses { get; set; } = []; + public List CombineClauses { get; set; } = []; + public List InsertClauses { get; set; } = []; + public List UpdateClauses { get; set; } = []; + + private string _engineScope = null; + public string EngineScope + { + get => _engineScope; + set + { + _engineScope = value; + if (Wheres != null) Wheres.EngineScope = value; + if (Havings != null) Havings.EngineScope = value; + } + } + + public Query SetEngineScope(string engine) + { + this.EngineScope = engine; + return this; + } + + 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 List Includes = []; + public Dictionary Variables = []; - public Query() : base() + public Query() { + Wheres = new ConditionGroup(); + Havings = new ConditionGroup(); } - public Query(string table, string comment = null) : base() + public Query(string table, string comment = null) : this() { From(table); Comment(comment); } + /// + /// Copy all clauses, conditions, and joins from the source query. + /// + public Query CopyClausesFrom(Query source) + { + this.Wheres = new ConditionGroup { Clauses = source.Wheres.Clauses.Select(x => x.Clone()).ToList() }; + this.Havings = new ConditionGroup { Clauses = source.Havings.Clauses.Select(x => x.Clone()).ToList() }; + this.Joins = source.Joins.Select(x => (BaseJoin)x.Clone()).ToList(); + this.FromClauses = source.FromClauses.Select(x => (AbstractFrom)x.Clone()).ToList(); + this.SelectClauses = source.SelectClauses.Select(x => (AbstractColumn)x.Clone()).ToList(); + this.CteClauses = source.CteClauses.Select(x => (AbstractFrom)x.Clone()).ToList(); + this.OrderByClauses = source.OrderByClauses.Select(x => (AbstractOrderBy)x.Clone()).ToList(); + this.GroupByClauses = source.GroupByClauses.Select(x => (AbstractColumn)x.Clone()).ToList(); + this.LimitClauses = source.LimitClauses.Select(x => (LimitClause)x.Clone()).ToList(); + this.OffsetClauses = source.OffsetClauses.Select(x => (OffsetClause)x.Clone()).ToList(); + this.AggregateClauses = source.AggregateClauses.Select(x => (AggregateClause)x.Clone()).ToList(); + this.CombineClauses = source.CombineClauses.Select(x => (AbstractCombine)x.Clone()).ToList(); + this.InsertClauses = source.InsertClauses.Select(x => (AbstractInsertClause)x.Clone()).ToList(); + this.UpdateClauses = source.UpdateClauses.Select(x => x.Clone()).ToList(); + return this; + } + + public virtual Query Clone() + { + var clone = new Query(); + clone.CopyClausesFrom(this); + clone.EngineScope = EngineScope; + clone.QueryAlias = QueryAlias; + clone.IsDistinct = IsDistinct; + clone.Method = Method; + clone.Includes = Includes.Select(i => i.Clone()).ToList(); + clone.Variables = new Dictionary(Variables); + return clone; + } + + internal Query AddOrReplace(List list, T clause, string engineCode = null) where T : AbstractClause + { + engineCode ??= EngineScope; + clause.Engine = engineCode; + var current = list.SingleOrDefault(c => c.Engine == engineCode); + if (current != null) + list.Remove(current); + list.Add(clause); + return this; + } + + public Query Or() + { + Wheres.Or(); + Havings.Or(); + return this; + } + + public Query Not(bool flag = true) + { + Wheres.Not(flag); + Havings.Not(flag); + return this; + } + public string GetComment() => comment ?? ""; public bool HasOffset(string engineCode = null) => GetOffset(engineCode) > 0; @@ -34,32 +158,20 @@ public Query(string table, string comment = null) : base() internal long GetOffset(string engineCode = null) { - engineCode = engineCode ?? EngineScope; - var offset = this.GetOneComponent("offset", engineCode); + engineCode ??= EngineScope; + var offset = OffsetClauses.ForEngineOrDefault(engineCode); return offset?.Offset ?? 0; } internal int GetLimit(string engineCode = null) { - engineCode = engineCode ?? EngineScope; - var limit = this.GetOneComponent("limit", engineCode); + engineCode ??= EngineScope; + var limit = LimitClauses.ForEngineOrDefault(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.Select(i => i.Clone()).ToList(); - clone.Variables = new Dictionary(Variables); - return clone; - } - public Query As(string alias) { QueryAlias = alias; @@ -104,11 +216,13 @@ public Query With(Query query) // clear the query alias query.QueryAlias = null; - return AddComponent("cte", new QueryFromClause + CteClauses.Add(new QueryFromClause { Query = query, Alias = alias, + Engine = EngineScope }); + return this; } public Query With(Func fn) @@ -144,6 +258,7 @@ public Query With(string alias, IEnumerable columns, IEnumerable(), + Engine = EngineScope }; foreach (var values in valuesCollectionList) @@ -157,37 +272,36 @@ public Query With(string alias, IEnumerable columns, IEnumerable x.Clone()).ToList(); + xQuery.CopyClausesFrom(query); - xQuery.SetParent(query.Parent); xQuery.QueryAlias = query.QueryAlias; xQuery.IsDistinct = query.IsDistinct; xQuery.Method = query.Method; @@ -350,9 +350,9 @@ public async Task>> GetAsync( public bool Exists(Query query, IDbTransaction transaction = null, int? timeout = null) { - var clone = query.Clone() - .ClearComponent("select") - .SelectRaw("1 as [Exists]") + var clone = query.Clone(); + clone.SelectClauses.Clear(); + clone.SelectRaw("1 as [Exists]") .Limit(1); var rows = Get(clone, transaction, timeout); @@ -362,9 +362,9 @@ public bool Exists(Query query, IDbTransaction transaction = null, int? timeout 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]") + var clone = query.Clone(); + clone.SelectClauses.Clear(); + clone.SelectRaw("1 as [Exists]") .Limit(1); var rows = await GetAsync(clone, transaction, timeout, cancellationToken); @@ -685,7 +685,7 @@ private static IEnumerable handleIncludes(Query query, IEnumerable resu // 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; + var fromTable = query.FromClauses.ForEngineOrDefault(query.EngineScope) as FromClause; if (fromTable == null) { @@ -786,7 +786,7 @@ private static async Task> handleIncludesAsync(Query query, IE // 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; + var fromTable = query.FromClauses.ForEngineOrDefault(query.EngineScope) as FromClause; if (fromTable == null) { diff --git a/SqlKata.Execution/XQuery.cs b/SqlKata.Execution/XQuery.cs index ef52cafe..81387ff3 100644 --- a/SqlKata.Execution/XQuery.cs +++ b/SqlKata.Execution/XQuery.cs @@ -30,7 +30,7 @@ public override Query Clone() query.QueryFactory.QueryTimeout = this.QueryFactory?.QueryTimeout ?? 30; } - query.Clauses = this.Clauses.Select(x => x.Clone()).ToList(); + query.CopyClausesFrom(this); query.Logger = this.Logger; query.QueryAlias = QueryAlias;