diff --git a/Program/Demo.db b/Program/Demo.db new file mode 100644 index 00000000..d2055d82 Binary files /dev/null and b/Program/Demo.db differ diff --git a/Program/Program.cs b/Program/Program.cs index c8d79ed3..d840ff1f 100644 --- a/Program/Program.cs +++ b/Program/Program.cs @@ -10,6 +10,8 @@ using Npgsql; using System.Data; using Dapper; +using System.Data.SQLite; +using Sqlkata.Compilers; namespace Program { @@ -36,19 +38,17 @@ static void Main(string[] args) "Server=tcp:localhost,1433;Initial Catalog=Lite;User ID=sa;Password=P@ssw0rd" ); - var db = new QueryFactory(connection, new SqlServerCompiler - { - UseLegacyPagination = true - }); + // SQLiteConnection.CreateFile("Demo.db"); + + connection = new SQLiteConnection("Data Source=Demo.db"); + + var db = new QueryFactory(connection, new SqliteCompiler()); + + // db.Statement("create table accounts(id integer primary key,name text,currency_id text);"); db.Logger = q => Console.WriteLine(q.ToString()); - var accounts = db.Query("Accounts") - .ForPage(2, 10) - .WhereRaw("[CurrencyId] in (?)", new object[] { 11 }) - .WhereRaw("[CurrencyId] in (?)", new[] { 1, 2, 3 }) - .WhereRaw("[CurrencyId] in (?)", new[] { "100", "200" }) - .Get(); + var accounts = db.Query("Accounts").OrderByDesc("Id").Offset(10).Get(); Console.WriteLine(JsonConvert.SerializeObject(accounts)); diff --git a/Program/Program.csproj b/Program/Program.csproj index 48aa1e8f..357d6785 100644 --- a/Program/Program.csproj +++ b/Program/Program.csproj @@ -9,6 +9,7 @@ + diff --git a/QueryBuilder.Tests/SqliteLimitTest.cs b/QueryBuilder.Tests/SqliteLimitTest.cs new file mode 100644 index 00000000..ded0fff5 --- /dev/null +++ b/QueryBuilder.Tests/SqliteLimitTest.cs @@ -0,0 +1,53 @@ +using Sqlkata.Compilers; +using SqlKata; +using Xunit; + +namespace SqlKata.Tests +{ + public class SqliteLimitTest + { + private SqliteCompiler compiler = new SqliteCompiler(); + + [Fact] + public void WithNoLimitNorOffset() + { + var query = new Query("Table"); + var ctx = new SqlResult { Query = query }; + + Assert.Null(compiler.CompileLimit(ctx)); + } + + [Fact] + public void WithNoOffset() + { + var query = new Query("Table").Limit(10); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); + Assert.Equal(10, ctx.Bindings[0]); + } + + [Fact] + public void WithNoLimit() + { + var query = new Query("Table").Offset(20); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("LIMIT -1 OFFSET ?", compiler.CompileLimit(ctx)); + Assert.Equal(20, ctx.Bindings[0]); + Assert.Single(ctx.Bindings); + } + + [Fact] + public void WithLimitAndOffset() + { + var query = new Query("Table").Limit(5).Offset(20); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); + Assert.Equal(5, ctx.Bindings[0]); + Assert.Equal(20, ctx.Bindings[1]); + Assert.Equal(2, ctx.Bindings.Count); + } + } +} \ No newline at end of file diff --git a/QueryBuilder/Compilers/Compiler.cs b/QueryBuilder/Compilers/Compiler.cs index 1f1502f4..41dccd32 100644 --- a/QueryBuilder/Compilers/Compiler.cs +++ b/QueryBuilder/Compilers/Compiler.cs @@ -8,8 +8,13 @@ namespace SqlKata.Compilers public abstract partial class Compiler { private readonly ConditionsCompilerProvider _compileConditionMethodsProvider; - protected string parameterPlaceholder = "?"; - protected string parameterPlaceholderPrefix = "@p"; + protected virtual string parameterPlaceholder { get; set; } = "?"; + protected virtual string parameterPlaceholderPrefix { get; set; } = "@p"; + protected virtual string OpeningIdentifier { get; set; } = "\""; + protected virtual string ClosingIdentifier { get; set; } = "\""; + protected virtual string ColumnAsKeyword { get; set; } = "AS "; + protected virtual string TableAsKeyword { get; set; } = "AS "; + protected virtual string LastId { get; set; } = ""; protected Compiler() { @@ -17,11 +22,7 @@ protected Compiler() } public abstract string EngineCode { get; } - protected string OpeningIdentifier = "\""; - protected string ClosingIdentifier = "\""; - protected string ColumnAsKeyword = "AS "; - protected string TableAsKeyword = "AS "; - protected string LastId = ""; + /// /// A list of white-listed operators @@ -588,7 +589,7 @@ public virtual string CompileOrders(SqlResult ctx) return "ORDER BY " + string.Join(", ", columns); } - public string CompileHaving(SqlResult ctx) + public virtual string CompileHaving(SqlResult ctx) { if (!ctx.Query.HasComponent("having", EngineCode)) { diff --git a/QueryBuilder/Compilers/SqlServerCompiler.cs b/QueryBuilder/Compilers/SqlServerCompiler.cs index 51e08e30..be24ce19 100644 --- a/QueryBuilder/Compilers/SqlServerCompiler.cs +++ b/QueryBuilder/Compilers/SqlServerCompiler.cs @@ -37,7 +37,9 @@ protected override SqlResult CompileSelectQuery(Query query) { query.Select("*"); } + var order = CompileOrders(ctx) ?? "ORDER BY (SELECT 0)"; + query.SelectRaw($"ROW_NUMBER() OVER ({order}) AS [row_num]", ctx.Bindings.ToArray()); query.ClearComponent("order"); diff --git a/QueryBuilder/Compilers/SqliteCompiler.cs b/QueryBuilder/Compilers/SqliteCompiler.cs new file mode 100644 index 00000000..a878b3c7 --- /dev/null +++ b/QueryBuilder/Compilers/SqliteCompiler.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using SqlKata; +using SqlKata.Compilers; + +namespace Sqlkata.Compilers +{ + public class SqliteCompiler : Compiler + { + public override string EngineCode => "sqlite"; + protected override string parameterPlaceholder { get; set; } = "?"; + protected override string parameterPlaceholderPrefix { get; set; } = "@p"; + protected override string OpeningIdentifier { get; set; } = "\""; + protected override string ClosingIdentifier { get; set; } = "\""; + protected override string LastId { get; set; } = "last_insert_rowid()"; + + public override string CompileTrue() + { + return "1"; + } + + public override string CompileFalse() + { + return "0"; + } + + public override string CompileLimit(SqlResult ctx) + { + var limit = ctx.Query.GetLimit(EngineCode); + var offset = ctx.Query.GetOffset(EngineCode); + + if (limit == 0 && offset > 0) + { + ctx.Bindings.Add(offset); + return "LIMIT -1 OFFSET ?"; + } + + return base.CompileLimit(ctx); + } + + protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) + { + var column = Wrap(condition.Column); + var value = Parameter(ctx, condition.Value); + + var formatMap = new Dictionary { + {"date", "%Y-%m-%d"}, + {"time", "%H:%M:%S"}, + {"year", "%Y"}, + {"month", "%m"}, + {"day", "%d"}, + {"hour", "%H"}, + {"minute", "%M"}, + }; + + if (!formatMap.ContainsKey(condition.Part)) + { + return $"{column} {condition.Operator} {value}"; + } + + var sql = $"strftime('{formatMap[condition.Part]}', {column}) {condition.Operator} cast({value} as text)"; + + if (condition.IsNot) + { + return $"NOT ({sql})"; + } + + return sql; + } + + } + public static class SqliteCompilerExtensions + { + public static string ENGINE_CODE = "sqlite"; + public static Query ForSqlite(this Query src, Func fn) + { + return src.For(SqliteCompilerExtensions.ENGINE_CODE, fn); + } + } +} \ No newline at end of file