diff --git a/QueryBuilder.Tests/GeneralTests.cs b/QueryBuilder.Tests/GeneralTests.cs index d25fcc84..7d2168e9 100644 --- a/QueryBuilder.Tests/GeneralTests.cs +++ b/QueryBuilder.Tests/GeneralTests.cs @@ -1,6 +1,8 @@ using SqlKata.Compilers; using SqlKata.Extensions; using SqlKata.Tests.Infrastructure; +using System; +using System.Linq; using Xunit; namespace SqlKata.Tests @@ -160,5 +162,227 @@ public void WrapWithMultipleSpaces() Assert.Equal("[My Table One] AS [Table One]", compiler.Wrap("My Table One as Table One")); } + + [Fact] + public void CompilerSpecificFrom() + { + var query = new Query() + .ForSqlServer(q => q.From("mssql")) + .ForPostgreSql(q => q.From("pgsql")) + .ForMySql(q => q.From("mysql")); + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); + Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); + Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); + } + + [Fact] + public void CompilerSpecificFromRaw() + { + var query = new Query() + .ForSqlServer(q => q.FromRaw("[mssql]")) + .ForPostgreSql(q => q.FromRaw("[pgsql]")) + .ForMySql(q => q.FromRaw("[mysql]")); + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); + Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); + Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); + } + + [Fact] + public void CompilerSpecificFromMixed() + { + var query = new Query() + .ForSqlServer(q => q.From("mssql")) + .ForPostgreSql(q => q.FromRaw("[pgsql]")) + .ForMySql(q => q.From("mysql")); + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); + Assert.Equal("SELECT * FROM \"pgsql\"", c[EngineCodes.PostgreSql].RawSql); + Assert.Equal("SELECT * FROM `mysql`", c[EngineCodes.MySql].RawSql); + } + + [Fact] + public void OneFromPerEngine() + { + var query = new Query("generic") + .ForSqlServer(q => q.From("dnu")) + .ForSqlServer(q => q.From("mssql")); + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal(2, query.Clauses.OfType().Count()); + Assert.Equal("SELECT * FROM [mssql]", c[EngineCodes.SqlServer].RawSql); + Assert.Equal("SELECT * FROM \"generic\"", c[EngineCodes.PostgreSql].RawSql); + Assert.Equal("SELECT * FROM `generic`", c[EngineCodes.MySql].RawSql); + } + + [Theory] + [InlineData(null, null)] + [InlineData(null, "mssql")] + [InlineData("original", null)] + [InlineData("original", "mssql")] + public void AddOrReplace_Works(string table, string engine) + { + var query = new Query(); + if (table != null) + query.From(table); + query.AddOrReplaceComponent("from", new FromClause() { Table = "updated", Engine = engine }); + var froms = query.Clauses.OfType(); + + Assert.Single(froms); + Assert.Equal("updated", froms.Single().Table); + } + + [Theory] + [InlineData(null, "generic")] + [InlineData(EngineCodes.SqlServer, "mssql")] + [InlineData(EngineCodes.MySql, "generic")] + public void GetOneComponent_Prefers_Engine(string engine, string column) + { + var query = new Query() + .Where("generic", "foo") + .ForSqlServer(q => q.Where("mssql", "foo")); + + var where = query.GetOneComponent("where", engine) as BasicCondition; + + Assert.NotNull(where); + Assert.Equal(column, where.Column); + } + + [Fact] + public void AddOrReplace_Throws_MoreThanOne() + { + var query = new Query() + .Where("a", "b") + .Where("c", "d"); + + Action act = () => query.AddOrReplaceComponent("where", new BasicCondition()); + Assert.Throws(act); + } + + [Fact] + public void OneLimitPerEngine() + { + var query = new Query("mytable") + .ForSqlServer(q => q.Limit(5)) + .ForSqlServer(q => q.Limit(10)); + + var limits = query.GetComponents("limit", EngineCodes.SqlServer); + Assert.Single(limits); + Assert.Equal(10, limits.Single().Limit); + } + + [Fact] + public void CompilerSpecificLimit() + { + var query = new Query("mytable") + .ForSqlServer(q => q.Limit(5)) + .ForPostgreSql(q => q.Limit(10)); + + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal(2, query.GetComponents("limit").Count()); + Assert.Equal("SELECT TOP (5) * FROM [mytable]", c[EngineCodes.SqlServer].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 10", c[EngineCodes.PostgreSql].ToString()); + Assert.Equal("SELECT * FROM `mytable`", c[EngineCodes.MySql].ToString()); + } + + [Fact] + public void OneOffsetPerEngine() + { + var query = new Query("mytable") + .ForSqlServer(q => q.Offset(5)) + .ForSqlServer(q => q.Offset(10)); + + var limits = query.GetComponents("offset", EngineCodes.SqlServer); + Assert.Single(limits); + Assert.Equal(10, limits.Single().Offset); + } + + [Fact] + public void CompilerSpecificOffset() + { + var query = new Query("mytable") + .ForMySql(q => q.Offset(5)) + .ForPostgreSql(q => q.Offset(10)); + + var engines = new[] { EngineCodes.SqlServer, EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal(2, query.GetComponents("offset").Count()); + Assert.Equal("SELECT * FROM `mytable` LIMIT 18446744073709551615 OFFSET 5", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" OFFSET 10", c[EngineCodes.PostgreSql].ToString()); + Assert.Equal("SELECT * FROM [mytable]", c[EngineCodes.SqlServer].ToString()); + } + + [Fact] + public void Limit_Takes_Generic_If_Needed() + { + var query = new Query("mytable") + .Limit(5) + .Offset(10) + .ForPostgreSql(q => q.Offset(20)); + + var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 10", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 5 OFFSET 20", c[EngineCodes.PostgreSql].ToString()); + } + + [Fact] + public void Offset_Takes_Generic_If_Needed() + { + var query = new Query("mytable") + .Limit(5) + .Offset(10) + .ForPostgreSql(q => q.Limit(20)); + + var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 10", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 20 OFFSET 10", c[EngineCodes.PostgreSql].ToString()); + } + + [Fact] + public void Can_Change_Generic_Limit_After_SpecificOffset() + { + var query = new Query("mytable") + .Limit(5) + .Offset(10) + .ForPostgreSql(q => q.Offset(20)) + .Limit(7); + + var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM `mytable` LIMIT 7 OFFSET 10", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 7 OFFSET 20", c[EngineCodes.PostgreSql].ToString()); + } + + [Fact] + public void Can_Change_Generic_Offset_After_SpecificLimit() + { + var query = new Query("mytable") + .Limit(5) + .Offset(10) + .ForPostgreSql(q => q.Limit(20)) + .Offset(7); + + var engines = new[] { EngineCodes.MySql, EngineCodes.PostgreSql }; + var c = Compilers.Compile(engines, query); + + Assert.Equal("SELECT * FROM `mytable` LIMIT 5 OFFSET 7", c[EngineCodes.MySql].ToString()); + Assert.Equal("SELECT * FROM \"mytable\" LIMIT 20 OFFSET 7", c[EngineCodes.PostgreSql].ToString()); + } } } diff --git a/QueryBuilder/BaseQuery.cs b/QueryBuilder/BaseQuery.cs index a08a5652..9da54bb0 100644 --- a/QueryBuilder/BaseQuery.cs +++ b/QueryBuilder/BaseQuery.cs @@ -82,6 +82,28 @@ public Q AddComponent(string component, AbstractClause clause, string engineCode 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. /// @@ -123,13 +145,10 @@ public List GetComponents(string component, string engineCode = /// public C GetOneComponent(string component, string engineCode = null) where C : AbstractClause { - if (engineCode == null) - { - engineCode = EngineScope; - } + engineCode = engineCode ?? EngineScope; - return GetComponents(component, engineCode) - .FirstOrDefault(); + var all = GetComponents(component, engineCode); + return all.FirstOrDefault(c => c.Engine == engineCode) ?? all.FirstOrDefault(c => c.Engine == null); } /// @@ -149,7 +168,7 @@ public AbstractClause GetOneComponent(string component, string engineCode = null } /// - /// Return wether the query has clauses for a component. + /// Return whether the query has clauses for a component. /// /// /// @@ -247,9 +266,9 @@ protected bool GetNot() /// public Q From(string table) { - return ClearComponent("from").AddComponent("from", new FromClause + return AddOrReplaceComponent("from", new FromClause { - Table = table + Table = table, }); } @@ -263,7 +282,7 @@ public Q From(Query query, string alias = null) query.As(alias); }; - return ClearComponent("from").AddComponent("from", new QueryFromClause + return AddOrReplaceComponent("from", new QueryFromClause { Query = query }); @@ -271,7 +290,7 @@ public Q From(Query query, string alias = null) public Q FromRaw(string sql, params object[] bindings) { - return ClearComponent("from").AddComponent("from", new RawFromClause + return AddOrReplaceComponent("from", new RawFromClause { Expression = sql, Bindings = bindings, diff --git a/QueryBuilder/Clauses/FromClause.cs b/QueryBuilder/Clauses/FromClause.cs index fb9c51b8..9b21a50b 100644 --- a/QueryBuilder/Clauses/FromClause.cs +++ b/QueryBuilder/Clauses/FromClause.cs @@ -40,6 +40,7 @@ public override AbstractClause Clone() { return new FromClause { + Engine = Engine, Alias = Alias, Table = Table, Component = Component, diff --git a/QueryBuilder/Clauses/LimitClause.cs b/QueryBuilder/Clauses/LimitClause.cs index 4f274c6e..b37d5b29 100644 --- a/QueryBuilder/Clauses/LimitClause.cs +++ b/QueryBuilder/Clauses/LimitClause.cs @@ -1,34 +1,13 @@ namespace SqlKata { - public class LimitOffset : AbstractClause + public class LimitClause : AbstractClause { private int _limit; - private int _offset; - + public int Limit { get => _limit; - - set - { - if (value > 0) - { - _limit = value; - } - } - } - - public int Offset - { - get => _offset; - - set - { - if (value > 0) - { - _offset = value; - } - } + set => _limit = value > 0 ? value : _limit; } public bool HasLimit() @@ -36,35 +15,18 @@ public bool HasLimit() return _limit > 0; } - public bool HasOffset() - { - return _offset > 0; - } - - public LimitOffset ClearLimit() + public LimitClause Clear() { _limit = 0; return this; } - public LimitOffset ClearOffset() - { - _offset = 0; - return this; - } - - public LimitOffset Clear() - { - return ClearLimit().ClearOffset(); - } - /// public override AbstractClause Clone() { - return new LimitOffset + return new LimitClause { Engine = Engine, - Offset = Offset, Limit = Limit, Component = Component, }; diff --git a/QueryBuilder/Clauses/OffsetClause.cs b/QueryBuilder/Clauses/OffsetClause.cs new file mode 100644 index 00000000..39fe8b4e --- /dev/null +++ b/QueryBuilder/Clauses/OffsetClause.cs @@ -0,0 +1,35 @@ +namespace SqlKata +{ + public class OffsetClause : AbstractClause + { + private int _offset; + + public int Offset + { + get => _offset; + set => _offset = value > 0 ? value : _offset; + } + + public bool HasOffset() + { + return _offset > 0; + } + + public OffsetClause Clear() + { + _offset = 0; + return this; + } + + /// + public override AbstractClause Clone() + { + return new OffsetClause + { + Engine = Engine, + Offset = Offset, + Component = Component, + }; + } + } +} \ No newline at end of file diff --git a/QueryBuilder/Compilers/SqlServerCompiler.cs b/QueryBuilder/Compilers/SqlServerCompiler.cs index 65c67ef0..13bc8264 100644 --- a/QueryBuilder/Compilers/SqlServerCompiler.cs +++ b/QueryBuilder/Compilers/SqlServerCompiler.cs @@ -14,7 +14,7 @@ public SqlServerCompiler() protected override SqlResult CompileSelectQuery(Query query) { - if (!UseLegacyPagination || !query.HasOffset()) + if (!UseLegacyPagination || !query.HasOffset(EngineCode)) { return base.CompileSelectQuery(query); } diff --git a/QueryBuilder/Query.cs b/QueryBuilder/Query.cs index 7ddeb65e..2c353292 100755 --- a/QueryBuilder/Query.cs +++ b/QueryBuilder/Query.cs @@ -23,32 +23,24 @@ public Query(string table, string comment = null) : base() } - public bool HasOffset(string engineCode = null) - { - var limitOffset = this.GetOneComponent("limit", engineCode); + public bool HasOffset(string engineCode = null) => GetOffset(engineCode) > 0; - return limitOffset?.HasOffset() ?? false; - } - - public bool HasLimit(string engineCode = null) - { - var limitOffset = this.GetOneComponent("limit", engineCode); - - return limitOffset?.HasLimit() ?? false; - } + public bool HasLimit(string engineCode = null) => GetLimit(engineCode) > 0; internal int GetOffset(string engineCode = null) { - var limitOffset = this.GetOneComponent("limit", engineCode); + engineCode = engineCode ?? EngineScope; + var offset = this.GetOneComponent("offset", engineCode); - return limitOffset?.Offset ?? 0; + return offset?.Offset ?? 0; } internal int GetLimit(string engineCode = null) { - var limitOffset = this.GetOneComponent("limit", engineCode); + engineCode = engineCode ?? EngineScope; + var limit = this.GetOneComponent("limit", engineCode); - return limitOffset?.Limit ?? 0; + return limit?.Limit ?? 0; } public override Query Clone() @@ -134,34 +126,22 @@ public Query WithRaw(string alias, string sql, params object[] bindings) public Query Limit(int value) { - var clause = GetOneComponent("limit", EngineScope) as LimitOffset; - - if (clause != null) - { - clause.Limit = value; - return this; - } - - return AddComponent("limit", new LimitOffset + var newClause = new LimitClause { Limit = value - }); + }; + + return AddOrReplaceComponent("limit", newClause); } public Query Offset(int value) { - var clause = GetOneComponent("limit", EngineScope) as LimitOffset; - - if (clause != null) - { - clause.Offset = value; - return this; - } - - return AddComponent("limit", new LimitOffset + var newClause = new OffsetClause { Offset = value - }); + }; + + return AddOrReplaceComponent("offset", newClause); } ///