diff --git a/QueryBuilder.Tests/ExecutionTests.cs b/QueryBuilder.Tests/ExecutionTests.cs index 7bcea9fc..de3c0d43 100644 --- a/QueryBuilder.Tests/ExecutionTests.cs +++ b/QueryBuilder.Tests/ExecutionTests.cs @@ -14,5 +14,14 @@ public void ShouldThrowException() new Query("Books").Get(); }); } + + [Fact] + public void TimeoutShouldBeCarriedToNewCreatedFactory() + { + var db = new QueryFactory(); + db.QueryTimeout = 4000; + var newFactory = QueryExtensions.CreateQueryFactory(db.Query()); + Assert.Equal(db.QueryTimeout, newFactory.QueryTimeout); + } } } diff --git a/QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs b/QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs index bb5bae61..58879c83 100644 --- a/QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs +++ b/QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs @@ -52,4 +52,4 @@ public void LimitAndOffset() Assert.Equal(2, ctx.Bindings.Count); } } -} \ No newline at end of file +} diff --git a/QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs b/QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs index edf21c27..613b053d 100644 --- a/QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs +++ b/QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs @@ -22,6 +22,9 @@ private static class Messages [EngineCodes.Snowflake] = new SnowflakeCompiler(), [EngineCodes.Sqlite] = new SqliteCompiler(), [EngineCodes.SqlServer] = new SqlServerCompiler() + { + UseLegacyPagination = true + } }; public IEnumerable KnownEngineCodes diff --git a/QueryBuilder.Tests/MySql/MySqlLimitTests.cs b/QueryBuilder.Tests/MySql/MySqlLimitTests.cs index ca2db254..3ac10c2f 100644 --- a/QueryBuilder.Tests/MySql/MySqlLimitTests.cs +++ b/QueryBuilder.Tests/MySql/MySqlLimitTests.cs @@ -55,4 +55,4 @@ public void WithLimitAndOffset() Assert.Equal(2, ctx.Bindings.Count); } } -} \ No newline at end of file +} diff --git a/QueryBuilder.Tests/ParameterTypeTests.cs b/QueryBuilder.Tests/ParameterTypeTests.cs index 17620f3f..4aeb3c1e 100644 --- a/QueryBuilder.Tests/ParameterTypeTests.cs +++ b/QueryBuilder.Tests/ParameterTypeTests.cs @@ -49,4 +49,4 @@ public void CorrectParameterTypeOutput(string rendered, object input) Assert.Equal($"SELECT * FROM [Table] WHERE [Col] = {rendered}", c[EngineCodes.SqlServer]); } } -} \ No newline at end of file +} diff --git a/QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs b/QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs index 22d7a5d6..99af1fa5 100644 --- a/QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs +++ b/QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs @@ -55,4 +55,4 @@ public void WithLimitAndOffset() Assert.Equal(2, ctx.Bindings.Count); } } -} \ No newline at end of file +} diff --git a/QueryBuilder.Tests/SelectTests.cs b/QueryBuilder.Tests/SelectTests.cs index 72a2c7b9..a971d4be 100644 --- a/QueryBuilder.Tests/SelectTests.cs +++ b/QueryBuilder.Tests/SelectTests.cs @@ -1,4 +1,4 @@ -using SqlKata.Compilers; +using SqlKata.Compilers; using SqlKata.Extensions; using SqlKata.Tests.Infrastructure; using System; @@ -35,7 +35,6 @@ public void BasicSelectEnumerable() Assert.Equal("SELECT \"id\", \"name\" FROM \"users\"", c[EngineCodes.Oracle]); } - [Fact] public void SelectAs() { @@ -810,6 +809,17 @@ public void MultipleOrHaving() Assert.Equal("SELECT * FROM [Table1] HAVING [Column1] > 1 OR [Column2] = 1", c[EngineCodes.SqlServer]); } + [Fact] + public void ShouldUseILikeOnPostgresWhenNonCaseSensitive() + { + var q = new Query("Table1") + .WhereLike("Column1", "%Upper Word%", false); + var c = Compile(q); + + Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%upper word%'", c[EngineCodes.SqlServer]); + Assert.Equal("SELECT * FROM \"Table1\" WHERE \"Column1\" ilike '%Upper Word%'", c[EngineCodes.PostgreSql]); + } + [Fact] public void EscapedWhereLike() { @@ -899,5 +909,40 @@ public void EscapeClauseThrowsForMultipleCharacters() .HavingContains("Column1", @"TestString\%", false, @"\aa"); }); } + + + [Fact] + public void BasicSelectRaw_WithNoTable() + { + var q = new Query().SelectRaw("somefunction() as c1"); + + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT somefunction() as c1", c.ToString()); + } + + [Fact] + public void BasicSelect_WithNoTable() + { + var q = new Query().Select("c1"); + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [c1]", c.ToString()); + } + + [Fact] + public void BasicSelect_WithNoTableAndWhereClause() + { + var q = new Query().Select("c1").Where("p", 1); + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [c1] WHERE [p] = 1", c.ToString()); + } + + [Fact] + public void BasicSelect_WithNoTableWhereRawClause() + { + var q = new Query().Select("c1").WhereRaw("1 = 1"); + var c = Compilers.CompileFor(EngineCodes.SqlServer, q); + Assert.Equal("SELECT [c1] WHERE 1 = 1", c.ToString()); + } + } } diff --git a/QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs b/QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs index 08de2af5..c881e4d9 100644 --- a/QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs +++ b/QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs @@ -75,4 +75,4 @@ public void ShouldKeepTheOrdersAsIsIfPaginationProvided() Assert.DoesNotContain("(SELECT 0)", compiler.Compile(query).ToString()); } } -} \ No newline at end of file +} diff --git a/QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs b/QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs index 2959d295..9fc4d12e 100644 --- a/QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs +++ b/QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs @@ -85,4 +85,4 @@ public void ShouldKeepTheOrdersAsIsIfPaginationProvided() Assert.DoesNotContain("(SELECT 0)", compiler.Compile(query).ToString()); } } -} \ No newline at end of file +} diff --git a/QueryBuilder/Base.Where.cs b/QueryBuilder/Base.Where.cs index 133a7480..ffa7369f 100644 --- a/QueryBuilder/Base.Where.cs +++ b/QueryBuilder/Base.Where.cs @@ -381,7 +381,7 @@ public Q OrWhereNotBetween(string column, T lower, T higher) public Q WhereIn(string column, IEnumerable values) { - // If the developer has passed a string most probably he wants List + // If the developer has passed a string they most likely want a List // since string is considered as List if (values is string) { diff --git a/QueryBuilder/Compilers/Compiler.cs b/QueryBuilder/Compilers/Compiler.cs index bb14b9b4..231f1fa6 100644 --- a/QueryBuilder/Compilers/Compiler.cs +++ b/QueryBuilder/Compilers/Compiler.cs @@ -619,14 +619,14 @@ public virtual string CompileTableExpression(SqlResult ctx, AbstractFrom from) public virtual string CompileFrom(SqlResult ctx) { - if (!ctx.Query.HasComponent("from", EngineCode)) + if (ctx.Query.HasComponent("from", EngineCode)) { - throw new InvalidOperationException("No table is set"); - } + var from = ctx.Query.GetOneComponent("from", EngineCode); - var from = ctx.Query.GetOneComponent("from", EngineCode); + return "FROM " + CompileTableExpression(ctx, from); + } - return "FROM " + CompileTableExpression(ctx, from); + return string.Empty; } public virtual string CompileJoins(SqlResult ctx) @@ -659,7 +659,7 @@ public virtual string CompileJoin(SqlResult ctx, Join join, bool isNested = fals public virtual string CompileWheres(SqlResult ctx) { - if (!ctx.Query.HasComponent("from", EngineCode) || !ctx.Query.HasComponent("where", EngineCode)) + if (!ctx.Query.HasComponent("where", EngineCode)) { return null; } diff --git a/QueryBuilder/Compilers/PostgresCompiler.cs b/QueryBuilder/Compilers/PostgresCompiler.cs index d1a4f902..be4dc7e5 100644 --- a/QueryBuilder/Compilers/PostgresCompiler.cs +++ b/QueryBuilder/Compilers/PostgresCompiler.cs @@ -1,3 +1,6 @@ +using System; +using System.Linq; + namespace SqlKata.Compilers { public class PostgresCompiler : Compiler @@ -10,6 +13,60 @@ public PostgresCompiler() public override string EngineCode { get; } = EngineCodes.PostgreSql; + + protected override string CompileBasicStringCondition(SqlResult ctx, BasicStringCondition x) + { + + var column = Wrap(x.Column); + + var value = Resolve(ctx, x.Value) as string; + + if (value == null) + { + throw new ArgumentException("Expecting a non nullable string"); + } + + var method = x.Operator; + + if (new[] { "starts", "ends", "contains", "like", "ilike" }.Contains(x.Operator)) + { + method = x.CaseSensitive ? "LIKE" : "ILIKE"; + + switch (x.Operator) + { + case "starts": + value = $"{value}%"; + break; + case "ends": + value = $"%{value}"; + break; + case "contains": + value = $"%{value}%"; + break; + } + } + + string sql; + + if (x.Value is UnsafeLiteral) + { + sql = $"{column} {checkOperator(method)} {value}"; + } + else + { + sql = $"{column} {checkOperator(method)} {Parameter(ctx, value)}"; + } + + if (!string.IsNullOrEmpty(x.EscapeCharacter)) + { + sql = $"{sql} ESCAPE '{x.EscapeCharacter}'"; + } + + return x.IsNot ? $"NOT ({sql})" : sql; + + } + + protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) { var column = Wrap(condition.Column); diff --git a/QueryBuilder/Compilers/SqlServerCompiler.cs b/QueryBuilder/Compilers/SqlServerCompiler.cs index a33826a2..83d86b45 100644 --- a/QueryBuilder/Compilers/SqlServerCompiler.cs +++ b/QueryBuilder/Compilers/SqlServerCompiler.cs @@ -16,7 +16,7 @@ public SqlServerCompiler() } public override string EngineCode { get; } = EngineCodes.SqlServer; - public bool UseLegacyPagination { get; set; } = true; + public bool UseLegacyPagination { get; set; } = false; public /* friend */ override SqlResult CompileSelectQuery(Query query) { diff --git a/QueryBuilder/Query.Having.cs b/QueryBuilder/Query.Having.cs index 8dcf43ca..ee367941 100644 --- a/QueryBuilder/Query.Having.cs +++ b/QueryBuilder/Query.Having.cs @@ -360,7 +360,7 @@ public Query OrHavingNotBetween(string column, T lower, T higher) public Query HavingIn(string column, IEnumerable values) { - // If the developer has passed a string most probably he wants List + // If the developer has passed a string they most likely want a List // since string is considered as List if (values is string) { diff --git a/QueryBuilder/SqlResult.cs b/QueryBuilder/SqlResult.cs index 2c633e5e..5a5c5815 100644 --- a/QueryBuilder/SqlResult.cs +++ b/QueryBuilder/SqlResult.cs @@ -88,7 +88,7 @@ private string ChangeToSqlValue(object value) } // fallback to string - return "'" + value.ToString() + "'"; + return "'" + value.ToString().Replace("'","''") + "'"; } } } diff --git a/SqlKata.Execution/Properties/AssemblyInfo.cs b/SqlKata.Execution/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5e161ab1 --- /dev/null +++ b/SqlKata.Execution/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("QueryBuilder.Tests")] \ No newline at end of file diff --git a/SqlKata.Execution/Query.Extensions.cs b/SqlKata.Execution/Query.Extensions.cs index d9f5ef30..0d5a70ef 100644 --- a/SqlKata.Execution/Query.Extensions.cs +++ b/SqlKata.Execution/Query.Extensions.cs @@ -366,7 +366,7 @@ internal static XQuery CastToXQuery(Query query, string method = null) internal static QueryFactory CreateQueryFactory(XQuery xQuery) { - var factory = new QueryFactory(xQuery.Connection, xQuery.Compiler); + var factory = new QueryFactory(xQuery.Connection, xQuery.Compiler, xQuery.QueryFactory.QueryTimeout); factory.Logger = xQuery.Logger; diff --git a/SqlKata.Execution/QueryFactory.cs b/SqlKata.Execution/QueryFactory.cs index 89248d0a..9eff197e 100644 --- a/SqlKata.Execution/QueryFactory.cs +++ b/SqlKata.Execution/QueryFactory.cs @@ -749,8 +749,8 @@ private static IEnumerable handleIncludes(Query query, IEnumerable resu foreach (var item in dynamicResult) { - var foreignValue = item[include.ForeignKey].ToString(); - item[include.Name] = related.ContainsKey(foreignValue) ? related[foreignValue] : null; + var foreignValue = item[include.ForeignKey]?.ToString(); + item[include.Name] = foreignValue != null && related.ContainsKey(foreignValue) ? related[foreignValue] : null; } } @@ -843,8 +843,8 @@ private static async Task> handleIncludesAsync(Query query, IE foreach (var item in dynamicResult) { - var foreignValue = item[include.ForeignKey].ToString(); - item[include.Name] = related.ContainsKey(foreignValue) ? related[foreignValue] : null; + var foreignValue = item[include.ForeignKey]?.ToString(); + item[include.Name] = foreignValue != null && related.ContainsKey(foreignValue) ? related[foreignValue] : null; } } @@ -896,4 +896,4 @@ public void Dispose() GC.SuppressFinalize(this); } } -} \ No newline at end of file +}