From acd1480cb1bc1fcb017c24a76c31dddb8c4f0a98 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 24 Aug 2016 19:04:24 +0200 Subject: [PATCH] a LOT More work --- .gitignore | 3 +- src/NadekoBot/Classes/FlowersHandler.cs | 53 - src/NadekoBot/Classes/IncidentsHandler.cs | 25 - src/NadekoBot/Classes/NadekoStats.cs | 284 -- src/NadekoBot/Classes/SQLite.cs | 3278 ----------------- .../Administration/AdministrationModule.cs | 103 +- .../Modules/Utility/Commands/QuoteCommands.cs | 4 +- .../Services/Database/IUnitOfWork.cs | 2 + .../Services/Database/Models/Config.cs | 14 + .../Services/Database/Models/Donator.cs | 15 + .../Services/Database/NadekoContext.cs | 32 +- .../Repositories/IConfigRepository.cs | 15 + .../Repositories/IDonatorsRepository.cs | 15 + .../Database/Repositories/IRepository.cs | 2 +- .../Repositories/Impl/ConfigRepository.cs | 35 + .../Repositories/Impl/DonatorsRepository.cs | 41 + src/NadekoBot/Services/Database/UnitOfWork.cs | 5 + src/NadekoBot/Services/Impl/BotCredentials.cs | 5 +- .../{Classes => _Classes}/DBHandler.cs | 0 .../ObservableConcurrentDictionary.cs | 0 .../{Classes => _Classes}/SearchHelper.cs | 0 .../ServerSpecificConfig.cs | 0 src/NadekoBot/project.json | 3 +- src/NadekoBot/project.lock.json | 53 +- 24 files changed, 264 insertions(+), 3723 deletions(-) delete mode 100644 src/NadekoBot/Classes/FlowersHandler.cs delete mode 100644 src/NadekoBot/Classes/IncidentsHandler.cs delete mode 100644 src/NadekoBot/Classes/NadekoStats.cs delete mode 100644 src/NadekoBot/Classes/SQLite.cs create mode 100644 src/NadekoBot/Services/Database/Models/Config.cs create mode 100644 src/NadekoBot/Services/Database/Models/Donator.cs create mode 100644 src/NadekoBot/Services/Database/Repositories/IConfigRepository.cs create mode 100644 src/NadekoBot/Services/Database/Repositories/IDonatorsRepository.cs create mode 100644 src/NadekoBot/Services/Database/Repositories/Impl/ConfigRepository.cs create mode 100644 src/NadekoBot/Services/Database/Repositories/Impl/DonatorsRepository.cs rename src/NadekoBot/{Classes => _Classes}/DBHandler.cs (100%) rename src/NadekoBot/{Classes => _Classes}/ObservableConcurrentDictionary.cs (100%) rename src/NadekoBot/{Classes => _Classes}/SearchHelper.cs (100%) rename src/NadekoBot/{Classes => _Classes}/ServerSpecificConfig.cs (100%) diff --git a/.gitignore b/.gitignore index d7c829c3..e5c7adbc 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ NadekoBot.sln.iml .idea/workspace.xml .idea/vcs.xml .idea/modules.xml -NadekoBot/bin/Debug/data/config_xnaas.json \ No newline at end of file +NadekoBot/bin/Debug/data/config_xnaas.json +src/NadekoBot/project.lock.json \ No newline at end of file diff --git a/src/NadekoBot/Classes/FlowersHandler.cs b/src/NadekoBot/Classes/FlowersHandler.cs deleted file mode 100644 index 46463c2f..00000000 --- a/src/NadekoBot/Classes/FlowersHandler.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Threading.Tasks; - -namespace NadekoBot.Classes -{ - internal static class FlowersHandler - { - public static async Task AddFlowersAsync(Discord.User u, string reason, int amount, bool silent = false) - { - if (amount <= 0) - return; - await Task.Run(() => - { - DbHandler.Instance.Connection.Insert(new DataModels.CurrencyTransaction - { - Reason = reason, - UserId = (long)u.Id, - Value = amount, - }); - }).ConfigureAwait(false); - - if (silent) - return; - - var flows = amount + " " + NadekoBot.Config.CurrencySign; - - await u.SendMessageAsync("👑Congratulations!👑\nYou received: " + flows).ConfigureAwait(false); - } - - public static async Task RemoveFlowers(Discord.User u, string reason, int amount, bool silent=false, string message="👎`Bot owner has taken {0}{1} from you.`") - { - if (amount <= 0) - return false; - var uid = (long)u.Id; - var state = DbHandler.Instance.FindOne(cs => cs.UserId == uid); - - if (state.Value < amount) - return false; - - DbHandler.Instance.Connection.Insert(new DataModels.CurrencyTransaction - { - Reason = reason, - UserId = (long)u.Id, - Value = -amount, - }); - - if (silent) - return true; - - await u.SendMessageAsync(string.Format(message,amount,NadekoBot.Config.CurrencySign)).ConfigureAwait(false); - return true; - } - } -} diff --git a/src/NadekoBot/Classes/IncidentsHandler.cs b/src/NadekoBot/Classes/IncidentsHandler.cs deleted file mode 100644 index 3042f824..00000000 --- a/src/NadekoBot/Classes/IncidentsHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -using NadekoBot.DataModels; -using System; - -namespace NadekoBot.Classes -{ - internal static class IncidentsHandler - { - public static void Add(ulong serverId, ulong channelId, string text) - { - var def = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"INCIDENT: {text}"); - Console.ForegroundColor = def; - var incident = new Incident - { - ChannelId = (long)channelId, - ServerId = (long)serverId, - Text = text, - Read = false - }; - - DbHandler.Instance.Connection.Insert(incident, typeof(Incident)); - } - } -} diff --git a/src/NadekoBot/Classes/NadekoStats.cs b/src/NadekoBot/Classes/NadekoStats.cs deleted file mode 100644 index 49038e98..00000000 --- a/src/NadekoBot/Classes/NadekoStats.cs +++ /dev/null @@ -1,284 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules.Administration.Commands; -using NadekoBot.Modules.Music; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Reflection; -using System.Threading.Tasks; -using System.Timers; - -namespace NadekoBot -{ - public class NadekoStats - { - public static NadekoStats Instance { get; } = new NadekoStats(); - - public string BotVersion => $"{Assembly.GetExecutingAssembly().GetName().Name} v{Assembly.GetExecutingAssembly().GetName().Version}"; - - private int commandsRan = 0; - private string statsCache = ""; - private readonly Stopwatch statsStopwatch = new Stopwatch(); - - public int ServerCount { get; private set; } = 0; - public int TextChannelsCount { get; private set; } = 0; - public int VoiceChannelsCount { get; private set; } = 0; - - private readonly Timer commandLogTimer = new Timer() { Interval = 10000 }; - private readonly Timer carbonStatusTimer = new Timer() { Interval = 3600000 }; - - private static ulong messageCounter = 0; - public static ulong MessageCounter => messageCounter; - - static NadekoStats() { } - - private NadekoStats() - { - var commandService = NadekoBot.Client.GetService(); - - statsStopwatch.Start(); - - commandService.CommandExecuted += StatsCollector_RanCommand; - commandService.CommandFinished += CommandService_CommandFinished; - commandService.CommandErrored += CommandService_CommandFinished; - - Task.Run(StartCollecting); - - commandLogTimer.Start(); - - ServerCount = NadekoBot.Client.Servers.Count(); - var channels = NadekoBot.Client.Servers.SelectMany(s => s.AllChannels); - var channelsArray = channels as Channel[] ?? channels.ToArray(); - TextChannelsCount = channelsArray.Count(c => c.Type == ChannelType.Text); - VoiceChannelsCount = channelsArray.Count() - TextChannelsCount; - - NadekoBot.Client.MessageReceived += (s, e) => messageCounter++; - - NadekoBot.Client.JoinedServer += (s, e) => - { - try - { - ServerCount++; - TextChannelsCount += e.Server.TextChannels.Count(); - VoiceChannelsCount += e.Server.VoiceChannels.Count(); - //await SendUpdateToCarbon().ConfigureAwait(false); - } - catch { } - }; - NadekoBot.Client.LeftServer += (s, e) => - { - try - { - ServerCount--; - TextChannelsCount -= e.Server.TextChannels.Count(); - VoiceChannelsCount -= e.Server.VoiceChannels.Count(); - //await SendUpdateToCarbon().ConfigureAwait(false); - } - catch { } - }; - NadekoBot.Client.ChannelCreated += (s, e) => - { - try - { - if (e.Channel.IsPrivate) - return; - if (e.Channel.Type == ChannelType.Text) - TextChannelsCount++; - else if (e.Channel.Type == ChannelType.Voice) - VoiceChannelsCount++; - } - catch { } - }; - NadekoBot.Client.ChannelDestroyed += (s, e) => - { - try - { - if (e.Channel.IsPrivate) - return; - if (e.Channel.Type == ChannelType.Text) - TextChannelsCount--; - else if (e.Channel.Type == ChannelType.Voice) - VoiceChannelsCount--; - } - catch { } - }; - carbonStatusTimer.Elapsed += async (s, e) => await SendUpdateToCarbon().ConfigureAwait(false); - carbonStatusTimer.Start(); - } - - HttpClient carbonClient = new HttpClient(); - private async Task SendUpdateToCarbon() - { - if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.CarbonKey)) - return; - try - { - using (var content = new FormUrlEncodedContent(new Dictionary { - { "servercount", NadekoBot.Client.Servers.Count().ToString() }, - { "key", NadekoBot.Credentials.CarbonKey } - })) - { - content.Headers.Clear(); - content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); - - var res = await carbonClient.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false); - }; - } - catch (Exception ex) - { - Console.WriteLine("Failed sending status update to carbon."); - Console.WriteLine(ex); - } - } - - public TimeSpan GetUptime() => - DateTime.Now - Process.GetCurrentProcess().StartTime; - - public string GetUptimeString() - { - var time = GetUptime(); - return time.Days + " days, " + time.Hours + " hours, and " + time.Minutes + " minutes."; - } - - public Task LoadStats() => - Task.Run(() => - { - var songs = MusicModule.MusicPlayers.Count(mp => mp.Value.CurrentSong != null); - var sb = new System.Text.StringBuilder(); - sb.AppendLine("`Author: Kwoth` `Library: Discord.Net`"); - sb.AppendLine($"`Bot Version: {BotVersion}`"); - sb.AppendLine($"`Bot id: {NadekoBot.Client.CurrentUser.Id}`"); - sb.Append("`Owners' Ids:` "); - sb.AppendLine("`" + String.Join(", ", NadekoBot.Credentials.OwnerIds) + "`"); - sb.AppendLine($"`Uptime: {GetUptimeString()}`"); - sb.Append($"`Servers: {ServerCount}"); - sb.Append($" | TextChannels: {TextChannelsCount}"); - sb.AppendLine($" | VoiceChannels: {VoiceChannelsCount}`"); - sb.AppendLine($"`Commands Ran this session: {commandsRan}`"); - sb.AppendLine($"`Message queue size: {NadekoBot.Client.MessageQueue.Count}`"); - sb.Append($"`Greeted {ServerGreetCommand.Greeted} times.`"); - sb.AppendLine($" `| Playing {songs} songs, ".SnPl(songs) + - $"{MusicModule.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count)} queued.`"); - sb.AppendLine($"`Messages: {messageCounter} ({messageCounter / (double)GetUptime().TotalSeconds:F2}/sec)` `Heap: {Heap(false)}`"); - statsCache = sb.ToString(); - }); - - public string Heap(bool pass = true) => Math.Round((double)GC.GetTotalMemory(pass) / 1.MiB(), 2).ToString(); - - public async Task GetStats() - { - if (statsStopwatch.Elapsed.Seconds < 4 && - !string.IsNullOrWhiteSpace(statsCache)) return statsCache; - await LoadStats().ConfigureAwait(false); - statsStopwatch.Restart(); - return statsCache; - } - - private async Task StartCollecting() - { - var statsSw = new Stopwatch(); - while (true) - { - await Task.Delay(new TimeSpan(0, 30, 0)).ConfigureAwait(false); - statsSw.Start(); - try - { - var onlineUsers = await Task.Run(() => NadekoBot.Client.Servers.Sum(x => x.Users.Count())).ConfigureAwait(false); - var realOnlineUsers = await Task.Run(() => NadekoBot.Client.Servers - .Sum(x => x.Users.Count(u => u.Status == UserStatus.Online))) - .ConfigureAwait(false); - var connectedServers = NadekoBot.Client.Servers.Count(); - - Classes.DbHandler.Instance.Connection.Insert(new DataModels.Stats - { - OnlineUsers = onlineUsers, - RealOnlineUsers = realOnlineUsers, - Uptime = GetUptime(), - ConnectedServers = connectedServers, - DateAdded = DateTime.Now - }); - - statsSw.Stop(); - var clr = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Blue; - Console.WriteLine($"--------------\nCollecting stats finished in {statsSw.Elapsed.TotalSeconds}s\n-------------"); - Console.ForegroundColor = clr; - statsSw.Reset(); - } - catch - { - Console.WriteLine("DB Exception in stats collecting."); - break; - } - } - } - - private static ConcurrentDictionary commandTracker = new ConcurrentDictionary(); - - private void CommandService_CommandFinished(object sender, CommandEventArgs e) - { - - DateTime dt; - if (!commandTracker.TryGetValue(e.Message.Id, out dt)) - return; - try - { - if (e is CommandErrorEventArgs) - { - var er = e as CommandErrorEventArgs; - if (er.ErrorType == CommandErrorType.Exception) - { - File.AppendAllText("errors.txt", $@"Command: {er.Command} -{er.Exception} -------------------------------------- -"); - Console.WriteLine($">>COMMAND ERRORED after *{(DateTime.UtcNow - dt).TotalSeconds}s*\nCmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {imsg.Author.Username} [{imsg.Author.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----"); - } - - } - else - { - Console.WriteLine($">>COMMAND ENDED after *{(DateTime.UtcNow - dt).TotalSeconds}s*\nCmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {imsg.Author.Username} [{imsg.Author.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----"); - } - } - catch { } - } - - private async void StatsCollector_RanCommand(object sender, CommandEventArgs e) - { - commandTracker.TryAdd(e.Message.Id, DateTime.UtcNow); - Console.WriteLine($">>COMMAND STARTED\nCmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {imsg.Author.Username} [{imsg.Author.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----"); -#if !NADEKO_RELEASE - await Task.Run(() => - { - try - { - commandsRan++; - Classes.DbHandler.Instance.Connection.Insert(new DataModels.Command - { - ServerId = (long)(e.Server?.Id ?? 0), - ServerName = e.Server?.Name ?? "--Direct Message--", - ChannelId = (long)e.Channel.Id, - ChannelName = e.Channel.IsPrivate ? "--Direct Message" : e.Channel.Name, - UserId = (long)imsg.Author.Id, - UserName = imsg.Author.Username, - CommandName = e.Command.Text, - DateAdded = DateTime.Now - }); - } - catch (Exception ex) - { - Console.WriteLine("Probably unimportant error in ran command DB write."); - Console.WriteLine(ex); - } - }).ConfigureAwait(false); -#endif - } - } -} diff --git a/src/NadekoBot/Classes/SQLite.cs b/src/NadekoBot/Classes/SQLite.cs deleted file mode 100644 index e74447f6..00000000 --- a/src/NadekoBot/Classes/SQLite.cs +++ /dev/null @@ -1,3278 +0,0 @@ -// -// Copyright (c) 2009-2012 Krueger Systems, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -#if WINDOWS_PHONE && !USE_WP8_NATIVE_SQLITE -#define USE_CSHARP_SQLITE -#endif - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Collections.Generic; -using System.Reflection; -using System.Linq; -using System.Linq.Expressions; -using System.Threading; - -#if USE_CSHARP_SQLITE -using Sqlite3 = Community.CsharpSqlite.Sqlite3; -using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3; -using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe; -#elif USE_WP8_NATIVE_SQLITE -using Sqlite3 = Sqlite.Sqlite3; -using Sqlite3DatabaseHandle = Sqlite.Database; -using Sqlite3Statement = Sqlite.Statement; -#else -using Sqlite3DatabaseHandle = System.IntPtr; -using Sqlite3Statement = System.IntPtr; -#endif - -namespace SQLite -{ - public class SQLiteException : Exception - { - public SQLite3.Result Result { get; private set; } - - protected SQLiteException (SQLite3.Result r,string message) : base(message) - { - Result = r; - } - - public static SQLiteException New (SQLite3.Result r, string message) - { - return new SQLiteException (r, message); - } - } - - public class NotNullConstraintViolationException : SQLiteException - { - public IEnumerable Columns { get; protected set; } - - protected NotNullConstraintViolationException (SQLite3.Result r, string message) - : this (r, message, null, null) - { - - } - - protected NotNullConstraintViolationException (SQLite3.Result r, string message, TableMapping mapping, object obj) - : base (r, message) - { - if (mapping != null && obj != null) { - this.Columns = from c in mapping.Columns - where c.IsNullable == false && c.GetValue (obj) == null - select c; - } - } - - public static new NotNullConstraintViolationException New (SQLite3.Result r, string message) - { - return new NotNullConstraintViolationException (r, message); - } - - public static NotNullConstraintViolationException New (SQLite3.Result r, string message, TableMapping mapping, object obj) - { - return new NotNullConstraintViolationException (r, message, mapping, obj); - } - - public static NotNullConstraintViolationException New (SQLiteException exception, TableMapping mapping, object obj) - { - return new NotNullConstraintViolationException (exception.Result, exception.Message, mapping, obj); - } - } - - [Flags] - public enum SQLiteOpenFlags { - ReadOnly = 1, ReadWrite = 2, Create = 4, - NoMutex = 0x8000, FullMutex = 0x10000, - SharedCache = 0x20000, PrivateCache = 0x40000, - ProtectionComplete = 0x00100000, - ProtectionCompleteUnlessOpen = 0x00200000, - ProtectionCompleteUntilFirstUserAuthentication = 0x00300000, - ProtectionNone = 0x00400000 - } - - [Flags] - public enum CreateFlags - { - None = 0, - ImplicitPK = 1, // create a primary key for field called 'Id' (Orm.ImplicitPkName) - ImplicitIndex = 2, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) - AllImplicit = 3, // do both above - - AutoIncPK = 4 // force PK field to be auto inc - } - - /// - /// Represents an open connection to a SQLite database. - /// - public partial class SQLiteConnection : IDisposable - { - private bool _open; - private TimeSpan _busyTimeout; - private Dictionary _mappings = null; - private Dictionary _tables = null; - private System.Diagnostics.Stopwatch _sw; - private long _elapsedMilliseconds = 0; - - private int _transactionDepth = 0; - private Random _rand = new Random (); - - public Sqlite3DatabaseHandle Handle { get; private set; } - internal static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); - - public string DatabasePath { get; private set; } - - public bool TimeExecution { get; set; } - - public bool Trace { get; set; } - - public bool StoreDateTimeAsTicks { get; private set; } - - /// - /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. - /// - /// - /// Specifies the path to the database file. - /// - /// - /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You - /// absolutely do want to store them as Ticks in all new projects. The default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection (string databasePath, bool storeDateTimeAsTicks = false) - : this (databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) - { - } - - /// - /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. - /// - /// - /// Specifies the path to the database file. - /// - /// - /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You - /// absolutely do want to store them as Ticks in all new projects. The default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) - { - if (string.IsNullOrEmpty (databasePath)) - throw new ArgumentException ("Must be specified", "databasePath"); - - DatabasePath = databasePath; - -#if NETFX_CORE - SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); -#endif - - Sqlite3DatabaseHandle handle; - -#if SILVERLIGHT || USE_CSHARP_SQLITE - var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); -#else - // open using the byte[] - // in the case where the path may include Unicode - // force open to using UTF-8 using sqlite3_open_v2 - var databasePathAsBytes = GetNullTerminatedUtf8 (DatabasePath); - var r = SQLite3.Open (databasePathAsBytes, out handle, (int) openFlags, IntPtr.Zero); -#endif - - Handle = handle; - if (r != SQLite3.Result.OK) { - throw SQLiteException.New (r, String.Format ("Could not open database file: {0} ({1})", DatabasePath, r)); - } - _open = true; - - StoreDateTimeAsTicks = storeDateTimeAsTicks; - - BusyTimeout = TimeSpan.FromSeconds (0.1); - } - - static SQLiteConnection () - { - if (_preserveDuringLinkMagic) { - var ti = new ColumnInfo (); - ti.Name = "magic"; - } - } - - public void EnableLoadExtension(int onoff) - { - SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff); - if (r != SQLite3.Result.OK) { - string msg = SQLite3.GetErrmsg (Handle); - throw SQLiteException.New (r, msg); - } - } - - static byte[] GetNullTerminatedUtf8 (string s) - { - var utf8Length = System.Text.Encoding.UTF8.GetByteCount (s); - var bytes = new byte [utf8Length + 1]; - utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); - return bytes; - } - - /// - /// Used to list some code that we want the MonoTouch linker - /// to see, but that we never want to actually execute. - /// - static bool _preserveDuringLinkMagic; - - /// - /// Sets a busy handler to sleep the specified amount of time when a table is locked. - /// The handler will sleep multiple times until a total time of has accumulated. - /// - public TimeSpan BusyTimeout { - get { return _busyTimeout; } - set { - _busyTimeout = value; - if (Handle != NullHandle) { - SQLite3.BusyTimeout (Handle, (int)_busyTimeout.TotalMilliseconds); - } - } - } - - /// - /// Returns the mappings from types to tables that the connection - /// currently understands. - /// - public IEnumerable TableMappings { - get { - return _tables != null ? _tables.Values : Enumerable.Empty (); - } - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The type whose mapping to the database is returned. - /// - /// - /// Optional flags allowing implicit PK and indexes based on naming conventions - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) - { - if (_mappings == null) { - _mappings = new Dictionary (); - } - TableMapping map; - if (!_mappings.TryGetValue (type.FullName, out map)) { - map = new TableMapping (type, createFlags); - _mappings [type.FullName] = map; - } - return map; - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping () - { - return GetMapping (typeof (T)); - } - - private struct IndexedColumn - { - public int Order; - public string ColumnName; - } - - private struct IndexInfo - { - public string IndexName; - public string TableName; - public bool Unique; - public List Columns; - } - - /// - /// Executes a "drop table" on the database. This is non-recoverable. - /// - public int DropTable() - { - var map = GetMapping (typeof (T)); - - var query = string.Format("drop table if exists \"{0}\"", map.TableName); - - return Execute (query); - } - - /// - /// Executes a "create table if not exists" on the database. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(CreateFlags createFlags = CreateFlags.None) - { - return CreateTable(typeof (T), createFlags); - } - - /// - /// Executes a "create table if not exists" on the database. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// Type to reflect to a database table. - /// Optional flags allowing implicit PK and indexes based on naming conventions. - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) - { - if (_tables == null) { - _tables = new Dictionary (); - } - TableMapping map; - if (!_tables.TryGetValue (ty.FullName, out map)) { - map = GetMapping (ty, createFlags); - _tables.Add (ty.FullName, map); - } - var query = "create table if not exists \"" + map.TableName + "\"(\n"; - - var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks)); - var decl = string.Join (",\n", decls.ToArray ()); - query += decl; - query += ")"; - - var count = Execute (query); - - if (count == 0) { //Possible bug: This always seems to return 0? - // Table already exists, migrate it - MigrateTable (map); - } - - var indexes = new Dictionary (); - foreach (var c in map.Columns) { - foreach (var i in c.Indices) { - var iname = i.Name ?? map.TableName + "_" + c.Name; - IndexInfo iinfo; - if (!indexes.TryGetValue (iname, out iinfo)) { - iinfo = new IndexInfo { - IndexName = iname, - TableName = map.TableName, - Unique = i.Unique, - Columns = new List () - }; - indexes.Add (iname, iinfo); - } - - if (i.Unique != iinfo.Unique) - throw new Exception ("All the columns in an index must have the same value for their Unique property"); - - iinfo.Columns.Add (new IndexedColumn { - Order = i.Order, - ColumnName = c.Name - }); - } - } - - foreach (var indexName in indexes.Keys) { - var index = indexes[indexName]; - var columns = index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray(); - count += CreateIndex(indexName, index.TableName, columns, index.Unique); - } - - return count; - } - - /// - /// Creates an index for the specified table and columns. - /// - /// Name of the index to create - /// Name of the database table - /// An array of column names to index - /// Whether the index should be unique - public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) - { - const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")"; - var sql = String.Format(sqlFormat, tableName, string.Join ("\", \"", columnNames), unique ? "unique" : "", indexName); - return Execute(sql); - } - - /// - /// Creates an index for the specified table and column. - /// - /// Name of the index to create - /// Name of the database table - /// Name of the column to index - /// Whether the index should be unique - public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) - { - return CreateIndex(indexName, tableName, new string[] { columnName }, unique); - } - - /// - /// Creates an index for the specified table and column. - /// - /// Name of the database table - /// Name of the column to index - /// Whether the index should be unique - public int CreateIndex(string tableName, string columnName, bool unique = false) - { - return CreateIndex(tableName + "_" + columnName, tableName, columnName, unique); - } - - /// - /// Creates an index for the specified table and columns. - /// - /// Name of the database table - /// An array of column names to index - /// Whether the index should be unique - public int CreateIndex(string tableName, string[] columnNames, bool unique = false) - { - return CreateIndex(tableName + "_" + string.Join ("_", columnNames), tableName, columnNames, unique); - } - - /// - /// Creates an index for the specified object property. - /// e.g. CreateIndex(c => c.Name); - /// - /// Type to reflect to a database table. - /// Property to index - /// Whether the index should be unique - public void CreateIndex(Expression> property, bool unique = false) - { - MemberExpression mx; - if (property.Body.NodeType == ExpressionType.Convert) - { - mx = ((UnaryExpression)property.Body).Operand as MemberExpression; - } - else - { - mx= (property.Body as MemberExpression); - } - var propertyInfo = mx.Member as PropertyInfo; - if (propertyInfo == null) - { - throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); - } - - var propName = propertyInfo.Name; - - var map = GetMapping(); - var colName = map.FindColumnWithPropertyName(propName).Name; - - CreateIndex(map.TableName, colName, unique); - } - - public class ColumnInfo - { -// public int cid { get; set; } - - [Column ("name")] - public string Name { get; set; } - -// [Column ("type")] -// public string ColumnType { get; set; } - - public int notnull { get; set; } - -// public string dflt_value { get; set; } - -// public int pk { get; set; } - - public override string ToString () - { - return Name; - } - } - - public List GetTableInfo (string tableName) - { - var query = "pragma table_info(\"" + tableName + "\")"; - return Query (query); - } - - void MigrateTable (TableMapping map) - { - var existingCols = GetTableInfo (map.TableName); - - var toBeAdded = new List (); - - foreach (var p in map.Columns) { - var found = false; - foreach (var c in existingCols) { - found = (string.Compare (p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0); - if (found) - break; - } - if (!found) { - toBeAdded.Add (p); - } - } - - foreach (var p in toBeAdded) { - var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks); - Execute (addCol); - } - } - - /// - /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class. - /// - /// - protected virtual SQLiteCommand NewCommand () - { - return new SQLiteCommand (this); - } - - /// - /// Creates a new SQLiteCommand given the command text with arguments. Place a '?' - /// in the command text for each of the arguments. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the command text. - /// - /// - /// A - /// - public SQLiteCommand CreateCommand (string cmdText, params object[] ps) - { - if (!_open) - throw SQLiteException.New (SQLite3.Result.Error, "Cannot create commands from unopened database"); - - var cmd = NewCommand (); - cmd.CommandText = cmdText; - foreach (var o in ps) { - cmd.Bind (o); - } - return cmd; - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// Use this method instead of Query when you don't expect rows back. Such cases include - /// INSERTs, UPDATEs, and DELETEs. - /// You can set the Trace or TimeExecution properties of the connection - /// to profile execution. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// The number of rows modified in the database as a result of this execution. - /// - public int Execute (string query, params object[] args) - { - var cmd = CreateCommand (query, args); - - if (TimeExecution) { - if (_sw == null) { - _sw = new Stopwatch (); - } - _sw.Reset (); - _sw.Start (); - } - - var r = cmd.ExecuteNonQuery (); - - if (TimeExecution) { - _sw.Stop (); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - public T ExecuteScalar (string query, params object[] args) - { - var cmd = CreateCommand (query, args); - - if (TimeExecution) { - if (_sw == null) { - _sw = new Stopwatch (); - } - _sw.Reset (); - _sw.Start (); - } - - var r = cmd.ExecuteScalar (); - - if (TimeExecution) { - _sw.Stop (); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the mapping automatically generated for - /// the given type. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// - public List Query (string query, params object[] args) where T : new() - { - var cmd = CreateCommand (query, args); - return cmd.ExecuteQuery (); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the mapping automatically generated for - /// the given type. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// The enumerator will call sqlite3_step on each call to MoveNext, so the database - /// connection must remain open for the lifetime of the enumerator. - /// - public IEnumerable DeferredQuery(string query, params object[] args) where T : new() - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A to use to convert the resulting rows - /// into objects. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// - public List Query (TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand (query, args); - return cmd.ExecuteQuery (map); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A to use to convert the resulting rows - /// into objects. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// The enumerator will call sqlite3_step on each call to MoveNext, so the database - /// connection must remain open for the lifetime of the enumerator. - /// - public IEnumerable DeferredQuery(TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(map); - } - - /// - /// Returns a queryable interface to the table represented by the given type. - /// - /// - /// A queryable object that is able to translate Where, OrderBy, and Take - /// queries into native SQL. - /// - public TableQuery Table () where T : new() - { - return new TableQuery (this); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The object with the given primary key. Throws a not found exception - /// if the object is not found. - /// - public T Get (object pk) where T : new() - { - var map = GetMapping (typeof(T)); - return Query (map.GetByPrimaryKeySql, pk).First (); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate. Throws a not found exception - /// if the object is not found. - /// - public T Get (Expression> predicate) where T : new() - { - return Table ().Where (predicate).First (); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public T Find (object pk) where T : new () - { - var map = GetMapping (typeof (T)); - return Query (map.GetByPrimaryKeySql, pk).FirstOrDefault (); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The TableMapping used to identify the object type. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public object Find (object pk, TableMapping map) - { - return Query (map, map.GetByPrimaryKeySql, pk).FirstOrDefault (); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate or null - /// if the object is not found. - /// - public T Find (Expression> predicate) where T : new() - { - return Table ().Where (predicate).FirstOrDefault (); - } - - /// - /// Whether has been called and the database is waiting for a . - /// - public bool IsInTransaction { - get { return _transactionDepth > 0; } - } - - /// - /// Begins a new transaction. Call to end the transaction. - /// - /// Throws if a transaction has already begun. - public void BeginTransaction () - { - // The BEGIN command only works if the transaction stack is empty, - // or in other words if there are no pending transactions. - // If the transaction stack is not empty when the BEGIN command is invoked, - // then the command fails with an error. - // Rather than crash with an error, we will just ignore calls to BeginTransaction - // that would result in an error. - if (Interlocked.CompareExchange (ref _transactionDepth, 1, 0) == 0) { - try { - Execute ("begin transaction"); - } catch (Exception ex) { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo (null, true); - break; - } - } else { - // Call decrement and not VolatileWrite in case we've already - // created a transaction point in SaveTransactionPoint since the catch. - Interlocked.Decrement (ref _transactionDepth); - } - - throw; - } - } else { - // Calling BeginTransaction on an already open transaction is invalid - throw new InvalidOperationException ("Cannot begin a transaction while already in a transaction."); - } - } - - /// - /// Creates a savepoint in the database at the current point in the transaction timeline. - /// Begins a new transaction if one is not in progress. - /// - /// Call to undo transactions since the returned savepoint. - /// Call to commit transactions after the savepoint returned here. - /// Call to end the transaction, committing all changes. - /// - /// A string naming the savepoint. - public string SaveTransactionPoint () - { - int depth = Interlocked.Increment (ref _transactionDepth) - 1; - string retVal = "S" + _rand.Next (short.MaxValue) + "D" + depth; - - try { - Execute ("savepoint " + retVal); - } catch (Exception ex) { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo (null, true); - break; - } - } else { - Interlocked.Decrement (ref _transactionDepth); - } - - throw; - } - - return retVal; - } - - /// - /// Rolls back the transaction that was begun by or . - /// - public void Rollback () - { - RollbackTo (null, false); - } - - /// - /// Rolls back the savepoint created by or SaveTransactionPoint. - /// - /// The name of the savepoint to roll back to, as returned by . If savepoint is null or empty, this method is equivalent to a call to - public void RollbackTo (string savepoint) - { - RollbackTo (savepoint, false); - } - - /// - /// Rolls back the transaction that was begun by . - /// - /// true to avoid throwing exceptions, false otherwise - void RollbackTo (string savepoint, bool noThrow) - { - // Rolling back without a TO clause rolls backs all transactions - // and leaves the transaction stack empty. - try { - if (String.IsNullOrEmpty (savepoint)) { - if (Interlocked.Exchange (ref _transactionDepth, 0) > 0) { - Execute ("rollback"); - } - } else { - DoSavePointExecute (savepoint, "rollback to "); - } - } catch (SQLiteException) { - if (!noThrow) - throw; - - } - // No need to rollback if there are no transactions open. - } - - /// - /// Releases a savepoint returned from . Releasing a savepoint - /// makes changes since that savepoint permanent if the savepoint began the transaction, - /// or otherwise the changes are permanent pending a call to . - /// - /// The RELEASE command is like a COMMIT for a SAVEPOINT. - /// - /// The name of the savepoint to release. The string should be the result of a call to - public void Release (string savepoint) - { - DoSavePointExecute (savepoint, "release "); - } - - void DoSavePointExecute (string savepoint, string cmd) - { - // Validate the savepoint - int firstLen = savepoint.IndexOf ('D'); - if (firstLen >= 2 && savepoint.Length > firstLen + 1) { - int depth; - if (Int32.TryParse (savepoint.Substring (firstLen + 1), out depth)) { - // TODO: Mild race here, but inescapable without locking almost everywhere. - if (0 <= depth && depth < _transactionDepth) { -#if NETFX_CORE - Volatile.Write (ref _transactionDepth, depth); -#elif SILVERLIGHT - _transactionDepth = depth; -#else - Thread.VolatileWrite (ref _transactionDepth, depth); -#endif - Execute (cmd + savepoint); - return; - } - } - } - - throw new ArgumentException ("savePoint is not valid, and should be the result of a call to SaveTransactionPoint.", "savePoint"); - } - - /// - /// Commits the transaction that was begun by . - /// - public void Commit () - { - if (Interlocked.Exchange (ref _transactionDepth, 0) != 0) { - Execute ("commit"); - } - // Do nothing on a commit with no open transaction - } - - /// - /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an - /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception - /// is rethrown. - /// - /// - /// The to perform within a transaction. can contain any number - /// of operations on the connection but should never call or - /// . - /// - public void RunInTransaction (Action action) - { - try { - var savePoint = SaveTransactionPoint (); - action (); - Release (savePoint); - } catch (Exception) { - Rollback (); - throw; - } - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) { - c += Insert (r); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects, string extra) - { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Insert (r, extra); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects, Type objType) - { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Insert (r, objType); - } - }); - return c; - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj) - { - if (obj == null) { - return 0; - } - return Insert (obj, "", obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// If a UNIQUE constraint violation occurs with - /// some pre-existing object, this function deletes - /// the old object. - /// - /// - /// The object to insert. - /// - /// - /// The number of rows modified. - /// - public int InsertOrReplace (object obj) - { - if (obj == null) { - return 0; - } - return Insert (obj, "OR REPLACE", obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, Type objType) - { - return Insert (obj, "", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// If a UNIQUE constraint violation occurs with - /// some pre-existing object, this function deletes - /// the old object. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows modified. - /// - public int InsertOrReplace (object obj, Type objType) - { - return Insert (obj, "OR REPLACE", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, string extra) - { - if (obj == null) { - return 0; - } - return Insert (obj, extra, obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, string extra, Type objType) - { - if (obj == null || objType == null) { - return 0; - } - - - var map = GetMapping (objType); - -#if NETFX_CORE - if (map.PK != null && map.PK.IsAutoGuid) - { - // no GetProperty so search our way up the inheritance chain till we find it - PropertyInfo prop; - while (objType != null) - { - var info = objType.GetTypeInfo(); - prop = info.GetDeclaredProperty(map.PK.PropertyName); - if (prop != null) - { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) - { - prop.SetValue(obj, Guid.NewGuid(), null); - } - break; - } - - objType = info.BaseType; - } - } -#else - if (map.PK != null && map.PK.IsAutoGuid) { - var prop = objType.GetProperty(map.PK.PropertyName); - if (prop != null) { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) { - prop.SetValue(obj, Guid.NewGuid(), null); - } - } - } -#endif - - - var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; - var vals = new object[cols.Length]; - for (var i = 0; i < vals.Length; i++) { - vals [i] = cols [i].GetValue (obj); - } - - var insertCmd = map.GetInsertCommand (this, extra); - int count; - - try { - count = insertCmd.ExecuteNonQuery (vals); - } - catch (SQLiteException ex) { - - if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (ex.Result, ex.Message, map, obj); - } - throw; - } - - if (map.HasAutoIncPK) - { - var id = SQLite3.LastInsertRowid (Handle); - map.SetAutoIncPK (obj, id); - } - - return count; - } - - /// - /// Updates all of the columns of a table using the specified object - /// except for its primary key. - /// The object is required to have a primary key. - /// - /// - /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The number of rows updated. - /// - public int Update (object obj) - { - if (obj == null) { - return 0; - } - return Update (obj, obj.GetType ()); - } - - /// - /// Updates all of the columns of a table using the specified object - /// except for its primary key. - /// The object is required to have a primary key. - /// - /// - /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows updated. - /// - public int Update (object obj, Type objType) - { - int rowsAffected = 0; - if (obj == null || objType == null) { - return 0; - } - - var map = GetMapping (objType); - - var pk = map.PK; - - if (pk == null) { - throw new NotSupportedException ("Cannot update " + map.TableName + ": it has no PK"); - } - - var cols = from p in map.Columns - where p != pk - select p; - var vals = from c in cols - select c.GetValue (obj); - var ps = new List (vals); - ps.Add (pk.GetValue (obj)); - var q = string.Format ("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join (",", (from c in cols - select "\"" + c.Name + "\" = ? ").ToArray ()), pk.Name); - - try { - rowsAffected = Execute (q, ps.ToArray ()); - } - catch (SQLiteException ex) { - - if (ex.Result == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (ex, map, obj); - } - - throw ex; - } - - return rowsAffected; - } - - /// - /// Updates all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows modified. - /// - public int UpdateAll (System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Update (r); - } - }); - return c; - } - - /// - /// Deletes the given object from the database using its primary key. - /// - /// - /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The number of rows deleted. - /// - public int Delete (object objectToDelete) - { - var map = GetMapping (objectToDelete.GetType ()); - var pk = map.PK; - if (pk == null) { - throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute (q, pk.GetValue (objectToDelete)); - } - - /// - /// Deletes the object with the specified primary key. - /// - /// - /// The primary key of the object to delete. - /// - /// - /// The number of objects deleted. - /// - /// - /// The type of object. - /// - public int Delete (object primaryKey) - { - var map = GetMapping (typeof (T)); - var pk = map.PK; - if (pk == null) { - throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute (q, primaryKey); - } - - /// - /// Deletes all the objects from the specified table. - /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the - /// specified table. Do you really want to do that? - /// - /// - /// The number of objects deleted. - /// - /// - /// The type of objects to delete. - /// - public int DeleteAll () - { - var map = GetMapping (typeof (T)); - var query = string.Format("delete from \"{0}\"", map.TableName); - return Execute (query); - } - - ~SQLiteConnection () - { - Dispose (false); - } - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - Close (); - } - - public void Close () - { - if (_open && Handle != NullHandle) { - try { - if (_mappings != null) { - foreach (var sqlInsertCommand in _mappings.Values) { - sqlInsertCommand.Dispose(); - } - } - var r = SQLite3.Close (Handle); - if (r != SQLite3.Result.OK) { - string msg = SQLite3.GetErrmsg (Handle); - throw SQLiteException.New (r, msg); - } - } - finally { - Handle = NullHandle; - _open = false; - } - } - } - } - - /// - /// Represents a parsed connection string. - /// - class SQLiteConnectionString - { - public string ConnectionString { get; private set; } - public string DatabasePath { get; private set; } - public bool StoreDateTimeAsTicks { get; private set; } - -#if NETFX_CORE - static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; -#endif - - public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks) - { - ConnectionString = databasePath; - StoreDateTimeAsTicks = storeDateTimeAsTicks; - -#if NETFX_CORE - DatabasePath = System.IO.Path.Combine (MetroStyleDataPath, databasePath); -#else - DatabasePath = databasePath; -#endif - } - } - - [AttributeUsage (AttributeTargets.Class)] - public class TableAttribute : Attribute - { - public string Name { get; set; } - - public TableAttribute (string name) - { - Name = name; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - public string Name { get; set; } - - public ColumnAttribute (string name) - { - Name = name; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class PrimaryKeyAttribute : Attribute - { - } - - [AttributeUsage (AttributeTargets.Property)] - public class AutoIncrementAttribute : Attribute - { - } - - [AttributeUsage (AttributeTargets.Property)] - public class IndexedAttribute : Attribute - { - public string Name { get; set; } - public int Order { get; set; } - public virtual bool Unique { get; set; } - - public IndexedAttribute() - { - } - - public IndexedAttribute(string name, int order) - { - Name = name; - Order = order; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - [AttributeUsage (AttributeTargets.Property)] - public class UniqueAttribute : IndexedAttribute - { - public override bool Unique { - get { return true; } - set { /* throw? */ } - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class MaxLengthAttribute : Attribute - { - public int Value { get; private set; } - - public MaxLengthAttribute (int length) - { - Value = length; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class CollationAttribute: Attribute - { - public string Value { get; private set; } - - public CollationAttribute (string collation) - { - Value = collation; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class NotNullAttribute : Attribute - { - } - - public class TableMapping - { - public Type MappedType { get; private set; } - - public string TableName { get; private set; } - - public Column[] Columns { get; private set; } - - public Column PK { get; private set; } - - public string GetByPrimaryKeySql { get; private set; } - - Column _autoPk; - Column[] _insertColumns; - Column[] _insertOrReplaceColumns; - - public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) - { - MappedType = type; - -#if NETFX_CORE - var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions - .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); -#else - var tableAttr = (TableAttribute)type.GetCustomAttributes (typeof (TableAttribute), true).FirstOrDefault (); -#endif - - TableName = tableAttr != null ? tableAttr.Name : MappedType.Name; - -#if !NETFX_CORE - var props = MappedType.GetProperties (BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); -#else - var props = from p in MappedType.GetRuntimeProperties() - where ((p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic) || (p.GetMethod != null && p.GetMethod.IsStatic) || (p.SetMethod != null && p.SetMethod.IsStatic)) - select p; -#endif - var cols = new List (); - foreach (var p in props) { -#if !NETFX_CORE - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Length > 0; -#else - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Count() > 0; -#endif - if (p.CanWrite && !ignore) { - cols.Add (new Column (p, createFlags)); - } - } - Columns = cols.ToArray (); - foreach (var c in Columns) { - if (c.IsAutoInc && c.IsPK) { - _autoPk = c; - } - if (c.IsPK) { - PK = c; - } - } - - HasAutoIncPK = _autoPk != null; - - if (PK != null) { - GetByPrimaryKeySql = string.Format ("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name); - } - else { - // People should not be calling Get/Find without a PK - GetByPrimaryKeySql = string.Format ("select * from \"{0}\" limit 1", TableName); - } - } - - public bool HasAutoIncPK { get; private set; } - - public void SetAutoIncPK (object obj, long id) - { - if (_autoPk != null) { - _autoPk.SetValue (obj, Convert.ChangeType (id, _autoPk.ColumnType, null)); - } - } - - public Column[] InsertColumns { - get { - if (_insertColumns == null) { - _insertColumns = Columns.Where (c => !c.IsAutoInc).ToArray (); - } - return _insertColumns; - } - } - - public Column[] InsertOrReplaceColumns { - get { - if (_insertOrReplaceColumns == null) { - _insertOrReplaceColumns = Columns.ToArray (); - } - return _insertOrReplaceColumns; - } - } - - public Column FindColumnWithPropertyName (string propertyName) - { - var exact = Columns.FirstOrDefault (c => c.PropertyName == propertyName); - return exact; - } - - public Column FindColumn (string columnName) - { - var exact = Columns.FirstOrDefault (c => c.Name == columnName); - return exact; - } - - PreparedSqlLiteInsertCommand _insertCommand; - string _insertCommandExtra; - - public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra) - { - if (_insertCommand == null) { - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - else if (_insertCommandExtra != extra) { - _insertCommand.Dispose(); - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - return _insertCommand; - } - - PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) - { - var cols = InsertColumns; - string insertSql; - if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) - { - insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); - } - else - { - var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - if (replacing) { - cols = InsertOrReplaceColumns; - } - - insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, - string.Join(",", (from c in cols - select "\"" + c.Name + "\"").ToArray()), - string.Join(",", (from c in cols - select "?").ToArray()), extra); - - } - - var insertCommand = new PreparedSqlLiteInsertCommand(conn); - insertCommand.CommandText = insertSql; - return insertCommand; - } - - protected internal void Dispose() - { - if (_insertCommand != null) { - _insertCommand.Dispose(); - _insertCommand = null; - } - } - - public class Column - { - PropertyInfo _prop; - - public string Name { get; private set; } - - public string PropertyName { get { return _prop.Name; } } - - public Type ColumnType { get; private set; } - - public string Collation { get; private set; } - - public bool IsAutoInc { get; private set; } - public bool IsAutoGuid { get; private set; } - - public bool IsPK { get; private set; } - - public IEnumerable Indices { get; set; } - - public bool IsNullable { get; private set; } - - public int? MaxStringLength { get; private set; } - - public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) - { - var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); - - _prop = prop; - Name = colAttr == null ? prop.Name : colAttr.Name; - //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead - ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; - Collation = Orm.Collation(prop); - - IsPK = Orm.IsPK(prop) || - (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) && - string.Compare (prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0); - - var isAuto = Orm.IsAutoInc(prop) || (IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)); - IsAutoGuid = isAuto && ColumnType == typeof(Guid); - IsAutoInc = isAuto && !IsAutoGuid; - - Indices = Orm.GetIndices(prop); - if (!Indices.Any() - && !IsPK - && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex) - && Name.EndsWith (Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) - ) - { - Indices = new IndexedAttribute[] { new IndexedAttribute() }; - } - IsNullable = !(IsPK || Orm.IsMarkedNotNull(prop)); - MaxStringLength = Orm.MaxStringLength(prop); - } - - public void SetValue (object obj, object val) - { - _prop.SetValue (obj, val, null); - } - - public object GetValue (object obj) - { - return _prop.GetValue (obj, null); - } - } - } - - public static class Orm - { - public const int DefaultMaxStringLength = 140; - public const string ImplicitPkName = "Id"; - public const string ImplicitIndexSuffix = "Id"; - - public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) - { - string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " "; - - if (p.IsPK) { - decl += "primary key "; - } - if (p.IsAutoInc) { - decl += "autoincrement "; - } - if (!p.IsNullable) { - decl += "not null "; - } - if (!string.IsNullOrEmpty (p.Collation)) { - decl += "collate " + p.Collation + " "; - } - - return decl; - } - - public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) - { - var clrType = p.ColumnType; - if (clrType == typeof(Boolean) || clrType == typeof(Byte) || clrType == typeof(UInt16) || clrType == typeof(SByte) || clrType == typeof(Int16) || clrType == typeof(Int32)) { - return "integer"; - } else if (clrType == typeof(UInt32) || clrType == typeof(Int64)) { - return "bigint"; - } else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal)) { - return "float"; - } else if (clrType == typeof(String)) { - int? len = p.MaxStringLength; - - if (len.HasValue) - return "varchar(" + len.Value + ")"; - - return "varchar"; - } else if (clrType == typeof(TimeSpan)) { - return "bigint"; - } else if (clrType == typeof(DateTime)) { - return storeDateTimeAsTicks ? "bigint" : "datetime"; - } else if (clrType == typeof(DateTimeOffset)) { - return "bigint"; -#if !NETFX_CORE - } else if (clrType.IsEnum) { -#else - } else if (clrType.GetTypeInfo().IsEnum) { -#endif - return "integer"; - } else if (clrType == typeof(byte[])) { - return "blob"; - } else if (clrType == typeof(Guid)) { - return "varchar(36)"; - } else { - throw new NotSupportedException ("Don't know about " + clrType); - } - } - - public static bool IsPK (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(PrimaryKeyAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - - public static string Collation (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(CollationAttribute), true); -#if !NETFX_CORE - if (attrs.Length > 0) { - return ((CollationAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) { - return ((CollationAttribute)attrs.First()).Value; -#endif - } else { - return string.Empty; - } - } - - public static bool IsAutoInc (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(AutoIncrementAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - - public static IEnumerable GetIndices(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true); - return attrs.Cast(); - } - - public static int? MaxStringLength(PropertyInfo p) - { - var attrs = p.GetCustomAttributes (typeof(MaxLengthAttribute), true); -#if !NETFX_CORE - if (attrs.Length > 0) - return ((MaxLengthAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) - return ((MaxLengthAttribute)attrs.First()).Value; -#endif - - return null; - } - - public static bool IsMarkedNotNull(MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof (NotNullAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - } - - public partial class SQLiteCommand - { - SQLiteConnection _conn; - private List _bindings; - - public string CommandText { get; set; } - - internal SQLiteCommand (SQLiteConnection conn) - { - _conn = conn; - _bindings = new List (); - CommandText = ""; - } - - public int ExecuteNonQuery () - { - if (_conn.Trace) { - Debug.WriteLine ("Executing: " + this); - } - - var r = SQLite3.Result.OK; - var stmt = Prepare (); - r = SQLite3.Step (stmt); - Finalize (stmt); - if (r == SQLite3.Result.Done) { - int rowsAffected = SQLite3.Changes (_conn.Handle); - return rowsAffected; - } else if (r == SQLite3.Result.Error) { - string msg = SQLite3.GetErrmsg (_conn.Handle); - throw SQLiteException.New (r, msg); - } - else if (r == SQLite3.Result.Constraint) { - if (SQLite3.ExtendedErrCode (_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - } - } - - throw SQLiteException.New(r, r.ToString()); - } - - public IEnumerable ExecuteDeferredQuery () - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))); - } - - public List ExecuteQuery () - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))).ToList(); - } - - public List ExecuteQuery (TableMapping map) - { - return ExecuteDeferredQuery(map).ToList(); - } - - /// - /// Invoked every time an instance is loaded from the database. - /// - /// - /// The newly created object. - /// - /// - /// This can be overridden in combination with the - /// method to hook into the life-cycle of objects. - /// - /// Type safety is not possible because MonoTouch does not support virtual generic methods. - /// - protected virtual void OnInstanceCreated (object obj) - { - // Can be overridden. - } - - public IEnumerable ExecuteDeferredQuery (TableMapping map) - { - if (_conn.Trace) { - Debug.WriteLine ("Executing Query: " + this); - } - - var stmt = Prepare (); - try - { - var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; - - for (int i = 0; i < cols.Length; i++) { - var name = SQLite3.ColumnName16 (stmt, i); - cols [i] = map.FindColumn (name); - } - - while (SQLite3.Step (stmt) == SQLite3.Result.Row) { - var obj = Activator.CreateInstance(map.MappedType); - for (int i = 0; i < cols.Length; i++) { - if (cols [i] == null) - continue; - var colType = SQLite3.ColumnType (stmt, i); - var val = ReadCol (stmt, i, colType, cols [i].ColumnType); - cols [i].SetValue (obj, val); - } - OnInstanceCreated (obj); - yield return (T)obj; - } - } - finally - { - SQLite3.Finalize(stmt); - } - } - - public T ExecuteScalar () - { - if (_conn.Trace) { - Debug.WriteLine ("Executing Query: " + this); - } - - T val = default(T); - - var stmt = Prepare (); - - try - { - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Row) { - var colType = SQLite3.ColumnType (stmt, 0); - val = (T)ReadCol (stmt, 0, colType, typeof(T)); - } - else if (r == SQLite3.Result.Done) { - } - else - { - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - } - } - finally - { - Finalize (stmt); - } - - return val; - } - - public void Bind (string name, object val) - { - _bindings.Add (new Binding { - Name = name, - Value = val - }); - } - - public void Bind (object val) - { - Bind (null, val); - } - - public override string ToString () - { - var parts = new string[1 + _bindings.Count]; - parts [0] = CommandText; - var i = 1; - foreach (var b in _bindings) { - parts [i] = string.Format (" {0}: {1}", i - 1, b.Value); - i++; - } - return string.Join (Environment.NewLine, parts); - } - - Sqlite3Statement Prepare() - { - var stmt = SQLite3.Prepare2 (_conn.Handle, CommandText); - BindAll (stmt); - return stmt; - } - - void Finalize (Sqlite3Statement stmt) - { - SQLite3.Finalize (stmt); - } - - void BindAll (Sqlite3Statement stmt) - { - int nextIdx = 1; - foreach (var b in _bindings) { - if (b.Name != null) { - b.Index = SQLite3.BindParameterIndex (stmt, b.Name); - } else { - b.Index = nextIdx++; - } - - BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks); - } - } - - internal static IntPtr NegativePointer = new IntPtr (-1); - - internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) - { - if (value == null) { - SQLite3.BindNull (stmt, index); - } else { - if (value is Int32) { - SQLite3.BindInt (stmt, index, (int)value); - } else if (value is String) { - SQLite3.BindText (stmt, index, (string)value, -1, NegativePointer); - } else if (value is Byte || value is UInt16 || value is SByte || value is Int16) { - SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); - } else if (value is Boolean) { - SQLite3.BindInt (stmt, index, (bool)value ? 1 : 0); - } else if (value is UInt32 || value is Int64) { - SQLite3.BindInt64 (stmt, index, Convert.ToInt64 (value)); - } else if (value is Single || value is Double || value is Decimal) { - SQLite3.BindDouble (stmt, index, Convert.ToDouble (value)); - } else if (value is TimeSpan) { - SQLite3.BindInt64(stmt, index, ((TimeSpan)value).Ticks); - } else if (value is DateTime) { - if (storeDateTimeAsTicks) { - SQLite3.BindInt64 (stmt, index, ((DateTime)value).Ticks); - } - else { - SQLite3.BindText (stmt, index, ((DateTime)value).ToString ("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer); - } - } else if (value is DateTimeOffset) { - SQLite3.BindInt64 (stmt, index, ((DateTimeOffset)value).UtcTicks); -#if !NETFX_CORE - } else if (value.GetType().IsEnum) { -#else - } else if (value.GetType().GetTypeInfo().IsEnum) { -#endif - SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); - } else if (value is byte[]){ - SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); - } else if (value is Guid) { - SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); - } else { - throw new NotSupportedException("Cannot store type: " + value.GetType()); - } - } - } - - class Binding - { - public string Name { get; set; } - - public object Value { get; set; } - - public int Index { get; set; } - } - - object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) - { - if (type == SQLite3.ColType.Null) { - return null; - } else { - if (clrType == typeof(String)) { - return SQLite3.ColumnString (stmt, index); - } else if (clrType == typeof(Int32)) { - return (int)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Boolean)) { - return SQLite3.ColumnInt (stmt, index) == 1; - } else if (clrType == typeof(double)) { - return SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(float)) { - return (float)SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(TimeSpan)) { - return new TimeSpan(SQLite3.ColumnInt64(stmt, index)); - } else if (clrType == typeof(DateTime)) { - if (_conn.StoreDateTimeAsTicks) { - return new DateTime (SQLite3.ColumnInt64 (stmt, index)); - } - else { - var text = SQLite3.ColumnString (stmt, index); - return DateTime.Parse (text); - } - } else if (clrType == typeof(DateTimeOffset)) { - return new DateTimeOffset(SQLite3.ColumnInt64 (stmt, index),TimeSpan.Zero); -#if !NETFX_CORE - } else if (clrType.IsEnum) { -#else - } else if (clrType.GetTypeInfo().IsEnum) { -#endif - return SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Int64)) { - return SQLite3.ColumnInt64 (stmt, index); - } else if (clrType == typeof(UInt32)) { - return (uint)SQLite3.ColumnInt64 (stmt, index); - } else if (clrType == typeof(decimal)) { - return (decimal)SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(Byte)) { - return (byte)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(UInt16)) { - return (ushort)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Int16)) { - return (short)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(sbyte)) { - return (sbyte)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(byte[])) { - return SQLite3.ColumnByteArray (stmt, index); - } else if (clrType == typeof(Guid)) { - var text = SQLite3.ColumnString(stmt, index); - return new Guid(text); - } else{ - throw new NotSupportedException ("Don't know how to read " + clrType); - } - } - } - } - - /// - /// Since the insert never changed, we only need to prepare once. - /// - public class PreparedSqlLiteInsertCommand : IDisposable - { - public bool Initialized { get; set; } - - protected SQLiteConnection Connection { get; set; } - - public string CommandText { get; set; } - - protected Sqlite3Statement Statement { get; set; } - internal static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); - - internal PreparedSqlLiteInsertCommand (SQLiteConnection conn) - { - Connection = conn; - } - - public int ExecuteNonQuery (object[] source) - { - if (Connection.Trace) { - Debug.WriteLine ("Executing: " + CommandText); - } - - var r = SQLite3.Result.OK; - - if (!Initialized) { - Statement = Prepare (); - Initialized = true; - } - - //bind the values. - if (source != null) { - for (int i = 0; i < source.Length; i++) { - SQLiteCommand.BindParameter (Statement, i + 1, source [i], Connection.StoreDateTimeAsTicks); - } - } - r = SQLite3.Step (Statement); - - if (r == SQLite3.Result.Done) { - int rowsAffected = SQLite3.Changes (Connection.Handle); - SQLite3.Reset (Statement); - return rowsAffected; - } else if (r == SQLite3.Result.Error) { - string msg = SQLite3.GetErrmsg (Connection.Handle); - SQLite3.Reset (Statement); - throw SQLiteException.New (r, msg); - } else if (r == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (Connection.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - SQLite3.Reset (Statement); - throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (Connection.Handle)); - } else { - SQLite3.Reset (Statement); - throw SQLiteException.New (r, r.ToString ()); - } - } - - protected virtual Sqlite3Statement Prepare () - { - var stmt = SQLite3.Prepare2 (Connection.Handle, CommandText); - return stmt; - } - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - private void Dispose (bool disposing) - { - if (Statement != NullStatement) { - try { - SQLite3.Finalize (Statement); - } finally { - Statement = NullStatement; - Connection = null; - } - } - } - - ~PreparedSqlLiteInsertCommand () - { - Dispose (false); - } - } - - public abstract class BaseTableQuery - { - protected class Ordering - { - public string ColumnName { get; set; } - public bool Ascending { get; set; } - } - } - - public class TableQuery : BaseTableQuery, IEnumerable - { - public SQLiteConnection Connection { get; private set; } - - public TableMapping Table { get; private set; } - - Expression _where; - List _orderBys; - int? _limit; - int? _offset; - - BaseTableQuery _joinInner; - Expression _joinInnerKeySelector; - BaseTableQuery _joinOuter; - Expression _joinOuterKeySelector; - Expression _joinSelector; - - Expression _selector; - - TableQuery (SQLiteConnection conn, TableMapping table) - { - Connection = conn; - Table = table; - } - - public TableQuery (SQLiteConnection conn) - { - Connection = conn; - Table = Connection.GetMapping (typeof(T)); - } - - public TableQuery Clone () - { - var q = new TableQuery (Connection, Table); - q._where = _where; - q._deferred = _deferred; - if (_orderBys != null) { - q._orderBys = new List (_orderBys); - } - q._limit = _limit; - q._offset = _offset; - q._joinInner = _joinInner; - q._joinInnerKeySelector = _joinInnerKeySelector; - q._joinOuter = _joinOuter; - q._joinOuterKeySelector = _joinOuterKeySelector; - q._joinSelector = _joinSelector; - q._selector = _selector; - return q; - } - - public TableQuery Where (Expression> predExpr) - { - if (predExpr.NodeType == ExpressionType.Lambda) { - var lambda = (LambdaExpression)predExpr; - var pred = lambda.Body; - var q = Clone (); - q.AddWhere (pred); - return q; - } else { - throw new NotSupportedException ("Must be a predicate"); - } - } - - public TableQuery Take (int n) - { - var q = Clone (); - q._limit = n; - return q; - } - - public TableQuery Skip (int n) - { - var q = Clone (); - q._offset = n; - return q; - } - - public T ElementAt (int index) - { - return Skip (index).Take (1).First (); - } - - bool _deferred; - public TableQuery Deferred () - { - var q = Clone (); - q._deferred = true; - return q; - } - - public TableQuery OrderBy (Expression> orderExpr) - { - return AddOrderBy (orderExpr, true); - } - - public TableQuery OrderByDescending (Expression> orderExpr) - { - return AddOrderBy (orderExpr, false); - } - - public TableQuery ThenBy(Expression> orderExpr) - { - return AddOrderBy(orderExpr, true); - } - - public TableQuery ThenByDescending(Expression> orderExpr) - { - return AddOrderBy(orderExpr, false); - } - - private TableQuery AddOrderBy (Expression> orderExpr, bool asc) - { - if (orderExpr.NodeType == ExpressionType.Lambda) { - var lambda = (LambdaExpression)orderExpr; - - MemberExpression mem = null; - - var unary = lambda.Body as UnaryExpression; - if (unary != null && unary.NodeType == ExpressionType.Convert) { - mem = unary.Operand as MemberExpression; - } - else { - mem = lambda.Body as MemberExpression; - } - - if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) { - var q = Clone (); - if (q._orderBys == null) { - q._orderBys = new List (); - } - q._orderBys.Add (new Ordering { - ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name, - Ascending = asc - }); - return q; - } else { - throw new NotSupportedException ("Order By does not support: " + orderExpr); - } - } else { - throw new NotSupportedException ("Must be a predicate"); - } - } - - private void AddWhere (Expression pred) - { - if (_where == null) { - _where = pred; - } else { - _where = Expression.AndAlso (_where, pred); - } - } - - public TableQuery Join ( - TableQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - { - var q = new TableQuery (Connection, Connection.GetMapping (typeof (TResult))) { - _joinOuter = this, - _joinOuterKeySelector = outerKeySelector, - _joinInner = inner, - _joinInnerKeySelector = innerKeySelector, - _joinSelector = resultSelector, - }; - return q; - } - - public TableQuery Select (Expression> selector) - { - var q = Clone (); - q._selector = selector; - return q; - } - - private SQLiteCommand GenerateCommand (string selectionList) - { - if (_joinInner != null && _joinOuter != null) { - throw new NotSupportedException ("Joins are not supported."); - } - else { - var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\""; - var args = new List (); - if (_where != null) { - var w = CompileExpr (_where, args); - cmdText += " where " + w.CommandText; - } - if ((_orderBys != null) && (_orderBys.Count > 0)) { - var t = string.Join (", ", _orderBys.Select (o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray ()); - cmdText += " order by " + t; - } - if (_limit.HasValue) { - cmdText += " limit " + _limit.Value; - } - if (_offset.HasValue) { - if (!_limit.HasValue) { - cmdText += " limit -1 "; - } - cmdText += " offset " + _offset.Value; - } - return Connection.CreateCommand (cmdText, args.ToArray ()); - } - } - - class CompileResult - { - public string CommandText { get; set; } - - public object Value { get; set; } - } - - private CompileResult CompileExpr (Expression expr, List queryArgs) - { - if (expr == null) { - throw new NotSupportedException ("Expression is NULL"); - } else if (expr is BinaryExpression) { - var bin = (BinaryExpression)expr; - - var leftr = CompileExpr (bin.Left, queryArgs); - var rightr = CompileExpr (bin.Right, queryArgs); - - //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null") - string text; - if (leftr.CommandText == "?" && leftr.Value == null) - text = CompileNullBinaryExpression(bin, rightr); - else if (rightr.CommandText == "?" && rightr.Value == null) - text = CompileNullBinaryExpression(bin, leftr); - else - text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")"; - return new CompileResult { CommandText = text }; - } else if (expr.NodeType == ExpressionType.Call) { - - var call = (MethodCallExpression)expr; - var args = new CompileResult[call.Arguments.Count]; - var obj = call.Object != null ? CompileExpr (call.Object, queryArgs) : null; - - for (var i = 0; i < args.Length; i++) { - args [i] = CompileExpr (call.Arguments [i], queryArgs); - } - - var sqlCall = ""; - - if (call.Method.Name == "Like" && args.Length == 2) { - sqlCall = "(" + args [0].CommandText + " like " + args [1].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 2) { - sqlCall = "(" + args [1].CommandText + " in " + args [0].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 1) { - if (call.Object != null && call.Object.Type == typeof(string)) { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args [0].CommandText + " || '%'))"; - } - else { - sqlCall = "(" + args [0].CommandText + " in " + obj.CommandText + ")"; - } - } - else if (call.Method.Name == "StartsWith" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " like (" + args [0].CommandText + " || '%'))"; - } - else if (call.Method.Name == "EndsWith" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args [0].CommandText + "))"; - } - else if (call.Method.Name == "Equals" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; - } else if (call.Method.Name == "ToLower") { - sqlCall = "(lower(" + obj.CommandText + "))"; - } else if (call.Method.Name == "ToUpper") { - sqlCall = "(upper(" + obj.CommandText + "))"; - } else { - sqlCall = call.Method.Name.ToLower () + "(" + string.Join (",", args.Select (a => a.CommandText).ToArray ()) + ")"; - } - return new CompileResult { CommandText = sqlCall }; - - } else if (expr.NodeType == ExpressionType.Constant) { - var c = (ConstantExpression)expr; - queryArgs.Add (c.Value); - return new CompileResult { - CommandText = "?", - Value = c.Value - }; - } else if (expr.NodeType == ExpressionType.Convert) { - var u = (UnaryExpression)expr; - var ty = u.Type; - var valr = CompileExpr (u.Operand, queryArgs); - return new CompileResult { - CommandText = valr.CommandText, - Value = valr.Value != null ? ConvertTo (valr.Value, ty) : null - }; - } else if (expr.NodeType == ExpressionType.MemberAccess) { - var mem = (MemberExpression)expr; - - if (mem.Expression!=null && mem.Expression.NodeType == ExpressionType.Parameter) { - // - // This is a column of our table, output just the column name - // Need to translate it if that column name is mapped - // - var columnName = Table.FindColumnWithPropertyName (mem.Member.Name).Name; - return new CompileResult { CommandText = "\"" + columnName + "\"" }; - } else { - object obj = null; - if (mem.Expression != null) { - var r = CompileExpr (mem.Expression, queryArgs); - if (r.Value == null) { - throw new NotSupportedException ("Member access failed to compile expression"); - } - if (r.CommandText == "?") { - queryArgs.RemoveAt (queryArgs.Count - 1); - } - obj = r.Value; - } - - // - // Get the member value - // - object val = null; - -#if !NETFX_CORE - if (mem.Member.MemberType == MemberTypes.Property) { -#else - if (mem.Member is PropertyInfo) { -#endif - var m = (PropertyInfo)mem.Member; - val = m.GetValue (obj, null); -#if !NETFX_CORE - } else if (mem.Member.MemberType == MemberTypes.Field) { -#else - } else if (mem.Member is FieldInfo) { -#endif -#if SILVERLIGHT - val = Expression.Lambda (expr).Compile ().DynamicInvoke (); -#else - var m = (FieldInfo)mem.Member; - val = m.GetValue (obj); -#endif - } else { -#if !NETFX_CORE - throw new NotSupportedException ("MemberExpr: " + mem.Member.MemberType); -#else - throw new NotSupportedException ("MemberExpr: " + mem.Member.DeclaringType); -#endif - } - - // - // Work special magic for enumerables - // - if (val != null && val is System.Collections.IEnumerable && !(val is string) && !(val is System.Collections.Generic.IEnumerable)) { - var sb = new System.Text.StringBuilder(); - sb.Append("("); - var head = ""; - foreach (var a in (System.Collections.IEnumerable)val) { - queryArgs.Add(a); - sb.Append(head); - sb.Append("?"); - head = ","; - } - sb.Append(")"); - return new CompileResult { - CommandText = sb.ToString(), - Value = val - }; - } - else { - queryArgs.Add (val); - return new CompileResult { - CommandText = "?", - Value = val - }; - } - } - } - throw new NotSupportedException ("Cannot compile: " + expr.NodeType.ToString ()); - } - - static object ConvertTo (object obj, Type t) - { - Type nut = Nullable.GetUnderlyingType(t); - - if (nut != null) { - if (obj == null) return null; - return Convert.ChangeType (obj, nut); - } else { - return Convert.ChangeType (obj, t); - } - } - - /// - /// Compiles a BinaryExpression where one of the parameters is null. - /// - /// The non-null parameter - private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) - { - if (expression.NodeType == ExpressionType.Equal) - return "(" + parameter.CommandText + " is ?)"; - else if (expression.NodeType == ExpressionType.NotEqual) - return "(" + parameter.CommandText + " is not ?)"; - else - throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString()); - } - - string GetSqlName (Expression expr) - { - var n = expr.NodeType; - if (n == ExpressionType.GreaterThan) - return ">"; else if (n == ExpressionType.GreaterThanOrEqual) { - return ">="; - } else if (n == ExpressionType.LessThan) { - return "<"; - } else if (n == ExpressionType.LessThanOrEqual) { - return "<="; - } else if (n == ExpressionType.And) { - return "&"; - } else if (n == ExpressionType.AndAlso) { - return "and"; - } else if (n == ExpressionType.Or) { - return "|"; - } else if (n == ExpressionType.OrElse) { - return "or"; - } else if (n == ExpressionType.Equal) { - return "="; - } else if (n == ExpressionType.NotEqual) { - return "!="; - } else { - throw new NotSupportedException ("Cannot get SQL for: " + n); - } - } - - public int Count () - { - return GenerateCommand("count(*)").ExecuteScalar (); - } - - public int Count (Expression> predExpr) - { - return Where (predExpr).Count (); - } - - public IEnumerator GetEnumerator () - { - if (!_deferred) - return GenerateCommand("*").ExecuteQuery().GetEnumerator(); - - return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () - { - return GetEnumerator (); - } - - public T First () - { - var query = Take (1); - return query.ToList().First (); - } - - public T FirstOrDefault () - { - var query = Take (1); - return query.ToList().FirstOrDefault (); - } - } - - public static class SQLite3 - { - public enum Result : int - { - OK = 0, - Error = 1, - Internal = 2, - Perm = 3, - Abort = 4, - Busy = 5, - Locked = 6, - NoMem = 7, - ReadOnly = 8, - Interrupt = 9, - IOError = 10, - Corrupt = 11, - NotFound = 12, - Full = 13, - CannotOpen = 14, - LockErr = 15, - Empty = 16, - SchemaChngd = 17, - TooBig = 18, - Constraint = 19, - Mismatch = 20, - Misuse = 21, - NotImplementedLFS = 22, - AccessDenied = 23, - Format = 24, - Range = 25, - NonDBFile = 26, - Notice = 27, - Warning = 28, - Row = 100, - Done = 101 - } - - public enum ExtendedResult : int - { - IOErrorRead = (Result.IOError | (1 << 8)), - IOErrorShortRead = (Result.IOError | (2 << 8)), - IOErrorWrite = (Result.IOError | (3 << 8)), - IOErrorFsync = (Result.IOError | (4 << 8)), - IOErrorDirFSync = (Result.IOError | (5 << 8)), - IOErrorTruncate = (Result.IOError | (6 << 8)), - IOErrorFStat = (Result.IOError | (7 << 8)), - IOErrorUnlock = (Result.IOError | (8 << 8)), - IOErrorRdlock = (Result.IOError | (9 << 8)), - IOErrorDelete = (Result.IOError | (10 << 8)), - IOErrorBlocked = (Result.IOError | (11 << 8)), - IOErrorNoMem = (Result.IOError | (12 << 8)), - IOErrorAccess = (Result.IOError | (13 << 8)), - IOErrorCheckReservedLock = (Result.IOError | (14 << 8)), - IOErrorLock = (Result.IOError | (15 << 8)), - IOErrorClose = (Result.IOError | (16 << 8)), - IOErrorDirClose = (Result.IOError | (17 << 8)), - IOErrorSHMOpen = (Result.IOError | (18 << 8)), - IOErrorSHMSize = (Result.IOError | (19 << 8)), - IOErrorSHMLock = (Result.IOError | (20 << 8)), - IOErrorSHMMap = (Result.IOError | (21 << 8)), - IOErrorSeek = (Result.IOError | (22 << 8)), - IOErrorDeleteNoEnt = (Result.IOError | (23 << 8)), - IOErrorMMap = (Result.IOError | (24 << 8)), - LockedSharedcache = (Result.Locked | (1 << 8)), - BusyRecovery = (Result.Busy | (1 << 8)), - CannottOpenNoTempDir = (Result.CannotOpen | (1 << 8)), - CannotOpenIsDir = (Result.CannotOpen | (2 << 8)), - CannotOpenFullPath = (Result.CannotOpen | (3 << 8)), - CorruptVTab = (Result.Corrupt | (1 << 8)), - ReadonlyRecovery = (Result.ReadOnly | (1 << 8)), - ReadonlyCannotLock = (Result.ReadOnly | (2 << 8)), - ReadonlyRollback = (Result.ReadOnly | (3 << 8)), - AbortRollback = (Result.Abort | (2 << 8)), - ConstraintCheck = (Result.Constraint | (1 << 8)), - ConstraintCommitHook = (Result.Constraint | (2 << 8)), - ConstraintForeignKey = (Result.Constraint | (3 << 8)), - ConstraintFunction = (Result.Constraint | (4 << 8)), - ConstraintNotNull = (Result.Constraint | (5 << 8)), - ConstraintPrimaryKey = (Result.Constraint | (6 << 8)), - ConstraintTrigger = (Result.Constraint | (7 << 8)), - ConstraintUnique = (Result.Constraint | (8 << 8)), - ConstraintVTab = (Result.Constraint | (9 << 8)), - NoticeRecoverWAL = (Result.Notice | (1 << 8)), - NoticeRecoverRollback = (Result.Notice | (2 << 8)) - } - - - public enum ConfigOption : int - { - SingleThread = 1, - MultiThread = 2, - Serialized = 3 - } - -#if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE - [DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_enable_load_extension", CallingConvention=CallingConvention.Cdecl)] - public static extern Result EnableLoadExtension (IntPtr db, int onoff); - - [DllImport("sqlite3", EntryPoint = "sqlite3_close", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Close (IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_initialize", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Initialize(); - - [DllImport("sqlite3", EntryPoint = "sqlite3_shutdown", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Shutdown(); - - [DllImport("sqlite3", EntryPoint = "sqlite3_config", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Config (ConfigOption option); - - [DllImport("sqlite3", EntryPoint = "sqlite3_win32_set_directory", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Unicode)] - public static extern int SetDirectory (uint directoryType, string directoryPath); - - [DllImport("sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention=CallingConvention.Cdecl)] - public static extern Result BusyTimeout (IntPtr db, int milliseconds); - - [DllImport("sqlite3", EntryPoint = "sqlite3_changes", CallingConvention=CallingConvention.Cdecl)] - public static extern int Changes (IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Prepare2 (IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); - -#if NETFX_CORE - [DllImport ("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Prepare2 (IntPtr db, byte[] queryBytes, int numBytes, out IntPtr stmt, IntPtr pzTail); -#endif - - public static IntPtr Prepare2 (IntPtr db, string query) - { - IntPtr stmt; -#if NETFX_CORE - byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query); - var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero); -#else - var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero); -#endif - if (r != Result.OK) { - throw SQLiteException.New (r, GetErrmsg (db)); - } - return stmt; - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_step", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Step (IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_reset", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Reset (IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Finalize (IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention=CallingConvention.Cdecl)] - public static extern long LastInsertRowid (IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_errmsg16", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr Errmsg (IntPtr db); - - public static string GetErrmsg (IntPtr db) - { - return Marshal.PtrToStringUni (Errmsg (db)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindParameterIndex (IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindNull (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindInt (IntPtr stmt, int index, int val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindInt64 (IntPtr stmt, int index, long val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindDouble (IntPtr stmt, int index, double val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_text16", CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int BindText (IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindBlob (IntPtr stmt, int index, byte[] val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnCount (IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnName (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention=CallingConvention.Cdecl)] - static extern IntPtr ColumnName16Internal (IntPtr stmt, int index); - public static string ColumnName16(IntPtr stmt, int index) - { - return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention=CallingConvention.Cdecl)] - public static extern ColType ColumnType (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnInt (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention=CallingConvention.Cdecl)] - public static extern long ColumnInt64 (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention=CallingConvention.Cdecl)] - public static extern double ColumnDouble (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnText (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnText16 (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnBlob (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_bytes", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnBytes (IntPtr stmt, int index); - - public static string ColumnString (IntPtr stmt, int index) - { - return Marshal.PtrToStringUni (SQLite3.ColumnText16 (stmt, index)); - } - - public static byte[] ColumnByteArray (IntPtr stmt, int index) - { - int length = ColumnBytes (stmt, index); - var result = new byte[length]; - if (length > 0) - Marshal.Copy (ColumnBlob (stmt, index), result, 0, length); - return result; - } - - [DllImport ("sqlite3", EntryPoint = "sqlite3_extended_errcode", CallingConvention = CallingConvention.Cdecl)] - public static extern ExtendedResult ExtendedErrCode (IntPtr db); - - [DllImport ("sqlite3", EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)] - public static extern int LibVersionNumber (); -#else - public static Result Open(string filename, out Sqlite3DatabaseHandle db) - { - return (Result) Sqlite3.sqlite3_open(filename, out db); - } - - public static Result Open(string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) - { -#if USE_WP8_NATIVE_SQLITE - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, ""); -#else - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null); -#endif - } - - public static Result Close(Sqlite3DatabaseHandle db) - { - return (Result)Sqlite3.sqlite3_close(db); - } - - public static Result BusyTimeout(Sqlite3DatabaseHandle db, int milliseconds) - { - return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds); - } - - public static int Changes(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_changes(db); - } - - public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) - { - Sqlite3Statement stmt = default(Sqlite3Statement); -#if USE_WP8_NATIVE_SQLITE - var r = Sqlite3.sqlite3_prepare_v2(db, query, out stmt); -#else - stmt = new Sqlite3Statement(); - var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0); -#endif - if (r != 0) - { - throw SQLiteException.New((Result)r, GetErrmsg(db)); - } - return stmt; - } - - public static Result Step(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_step(stmt); - } - - public static Result Reset(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_reset(stmt); - } - - public static Result Finalize(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_finalize(stmt); - } - - public static long LastInsertRowid(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_last_insert_rowid(db); - } - - public static string GetErrmsg(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_errmsg(db); - } - - public static int BindParameterIndex(Sqlite3Statement stmt, string name) - { - return Sqlite3.sqlite3_bind_parameter_index(stmt, name); - } - - public static int BindNull(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_bind_null(stmt, index); - } - - public static int BindInt(Sqlite3Statement stmt, int index, int val) - { - return Sqlite3.sqlite3_bind_int(stmt, index, val); - } - - public static int BindInt64(Sqlite3Statement stmt, int index, long val) - { - return Sqlite3.sqlite3_bind_int64(stmt, index, val); - } - - public static int BindDouble(Sqlite3Statement stmt, int index, double val) - { - return Sqlite3.sqlite3_bind_double(stmt, index, val); - } - - public static int BindText(Sqlite3Statement stmt, int index, string val, int n, IntPtr free) - { -#if USE_WP8_NATIVE_SQLITE - return Sqlite3.sqlite3_bind_text(stmt, index, val, n); -#else - return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); -#endif - } - - public static int BindBlob(Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) - { -#if USE_WP8_NATIVE_SQLITE - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n); -#else - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); -#endif - } - - public static int ColumnCount(Sqlite3Statement stmt) - { - return Sqlite3.sqlite3_column_count(stmt); - } - - public static string ColumnName(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static string ColumnName16(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static ColType ColumnType(Sqlite3Statement stmt, int index) - { - return (ColType)Sqlite3.sqlite3_column_type(stmt, index); - } - - public static int ColumnInt(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_int(stmt, index); - } - - public static long ColumnInt64(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_int64(stmt, index); - } - - public static double ColumnDouble(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_double(stmt, index); - } - - public static string ColumnText(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static string ColumnText16(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnBlob(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_blob(stmt, index); - } - - public static int ColumnBytes(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_bytes(stmt, index); - } - - public static string ColumnString(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) - { - return ColumnBlob(stmt, index); - } - - public static Result EnableLoadExtension(Sqlite3DatabaseHandle db, int onoff) - { - return (Result)Sqlite3.sqlite3_enable_load_extension(db, onoff); - } - - public static ExtendedResult ExtendedErrCode(Sqlite3DatabaseHandle db) - { - return (ExtendedResult)Sqlite3.sqlite3_extended_errcode(db); - } -#endif - - public enum ColType : int - { - Integer = 1, - Float = 2, - Text = 3, - Blob = 4, - Null = 5 - } - } -} diff --git a/src/NadekoBot/Modules/Administration/AdministrationModule.cs b/src/NadekoBot/Modules/Administration/AdministrationModule.cs index a9cfa5e2..6ec0771a 100644 --- a/src/NadekoBot/Modules/Administration/AdministrationModule.cs +++ b/src/NadekoBot/Modules/Administration/AdministrationModule.cs @@ -11,6 +11,8 @@ using NadekoBot.Services; using NadekoBot.Attributes; using System.Text.RegularExpressions; using Discord.WebSocket; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; //todo fix delmsgoncmd //todo DB @@ -35,24 +37,27 @@ namespace NadekoBot.Modules.Administration // System.Diagnostics.Process.Start(System.Reflection.Assembly.GetEntryAssembly().Location); // Environment.Exit(0); //} - - ////todo DB - //[LocalizedCommand, LocalizedDescription, LocalizedSummary] - //[RequireContext(ContextType.Guild)] - //[RequirePermission(GuildPermission.ManageGuild)] - //public async Task Delmsgoncmd(IMessage imsg) - //{ - // var channel = (ITextChannel)imsg.Channel; - - // var conf = SpecificConfigurations.Default.Of(channel.Guild.Id); - // conf.AutoDeleteMessagesOnCommand = !conf.AutoDeleteMessagesOnCommand; - // await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); - // if (conf.AutoDeleteMessagesOnCommand) - // await channel.SendMessageAsync("❗`Now automatically deleting successfull command invokations.`"); - // else - // await channel.SendMessageAsync("❗`Stopped automatic deletion of successfull command invokations.`"); - //} + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + public async Task Delmsgoncmd(IMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + Config conf; + using (var uow = DbHandler.UnitOfWork()) + { + conf = uow.Configs.For(channel.Guild.Id); + conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand; + uow.Configs.Update(conf); + await uow.CompleteAsync(); + } + if (conf.DeleteMessageOnCommand) + await channel.SendMessageAsync("❗`Now automatically deleting successfull command invokations.`"); + else + await channel.SendMessageAsync("❗`Stopped automatic deletion of successfull command invokations.`"); + } + [LocalizedCommand, LocalizedDescription, LocalizedSummary] [RequireContext(ContextType.Guild)] [RequirePermission(GuildPermission.ManageRoles)] @@ -563,30 +568,6 @@ namespace NadekoBot.Modules.Administration // } //} - ////todo owner only - ////todo DB - //[LocalizedCommand, LocalizedDescription, LocalizedSummary] - //[RequireContext(ContextType.Guild)] - //public async Task Donadd(IMessage imsg, IUser donator, int amount) - //{ - // var channel = (ITextChannel)imsg.Channel; - // var donator = channel.Guild.FindUsers(donator).FirstOrDefault(); - // var amount = int.Parse(amount); - // if (donator == null) return; - // try - // { - // DbHandler.Instance.Connection.Insert(new Donator - // { - // Amount = amount, - // UserName = donator.Name, - // UserId = (long)donator.Id - // }); - // channel.SendMessageAsync("Successfuly added a new donator. 👑").ConfigureAwait(false); - // } - // catch { } - - //} - ////todo owner only //[LocalizedCommand, LocalizedDescription, LocalizedSummary] //[RequireContext(ContextType.Guild)] @@ -660,18 +641,36 @@ namespace NadekoBot.Modules.Administration await channel.SendMessageAsync(send).ConfigureAwait(false); } - //todo DB - //[LocalizedCommand, LocalizedDescription, LocalizedSummary] - //[RequireContext(ContextType.Guild)] - //public async Task Donators(IMessage imsg) - //{ - // var channel = (ITextChannel)imsg.Channel; + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Donators(IMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + IEnumerable donatorsOrdered; + using (var uow = DbHandler.UnitOfWork()) + { + donatorsOrdered = uow.Donators.GetDonatorsOrdered(); + } - // var rows = DbHandler.Instance.GetAllRows(); - // var donatorsOrdered = rows.OrderByDescending(d => d.Amount); - // string str = $"**Thanks to the people listed below for making this project happen!**\n"; + string str = $"**Thanks to the people listed below for making this project happen!**\n"; + await channel.SendMessageAsync(str + string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false); + } - // await channel.SendMessageAsync(str + string.Join("⭐", donatorsOrdered.Select(d => d.UserName))).ConfigureAwait(false); - //} + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Donadd(IMessage imsg, IUser donator, int amount) + { + var channel = (ITextChannel)imsg.Channel; + + Donator don; + using (var uow = DbHandler.UnitOfWork()) + { + don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount); + await uow.CompleteAsync(); + } + + await channel.SendMessageAsync($"Successfuly added a new donator. Total donated amount from this user: {don.Amount} 👑").ConfigureAwait(false); + } } } diff --git a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs index 2f059674..3dc1e71a 100644 --- a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace NadekoBot.Modules.Utility.Commands +namespace NadekoBot.Modules.Utility { public partial class Utility { @@ -103,7 +103,7 @@ namespace NadekoBot.Modules.Utility.Commands { var quotes = uow.Quotes.GetAllQuotesByKeyword(keyword); - uow.Quotes.RemoveRange(quotes.Select(q => q.Id).ToArray());//wtf?! + uow.Quotes.RemoveRange(quotes.ToArray());//wtf?! await uow.CompleteAsync(); } diff --git a/src/NadekoBot/Services/Database/IUnitOfWork.cs b/src/NadekoBot/Services/Database/IUnitOfWork.cs index 9f92656a..c2eafccf 100644 --- a/src/NadekoBot/Services/Database/IUnitOfWork.cs +++ b/src/NadekoBot/Services/Database/IUnitOfWork.cs @@ -10,6 +10,8 @@ namespace NadekoBot.Services.Database public interface IUnitOfWork : IDisposable { IQuoteRepository Quotes { get; } + IConfigRepository Configs { get; } + IDonatorsRepository Donators { get; } int Complete(); Task CompleteAsync(); } diff --git a/src/NadekoBot/Services/Database/Models/Config.cs b/src/NadekoBot/Services/Database/Models/Config.cs new file mode 100644 index 00000000..a1e62e22 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Config.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class Config : DbEntity + { + public ulong GuildId { get; set; } + public bool DeleteMessageOnCommand { get; set; } = false; + } +} diff --git a/src/NadekoBot/Services/Database/Models/Donator.cs b/src/NadekoBot/Services/Database/Models/Donator.cs new file mode 100644 index 00000000..5376e6f9 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Donator.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class Donator : DbEntity + { + public ulong UserId { get; set; } + public string Name { get; set; } + public int Amount { get; set; } = 0; + } +} diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index eac45f89..0273efa3 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -15,24 +15,28 @@ namespace NadekoBot.Services.Database protected override void OnModelCreating(ModelBuilder modelBuilder) { #region QUOTES - //// guildid and keyword are unique pair - var quoteEntity = modelBuilder.Entity(); - //quoteEntity - // .HasAlternateKey(q => q.GuildId) - // .HasName("AK_GuildId_Keyword"); - - //quoteEntity - // .HasAlternateKey(q => q.Keyword) - // .HasName("AK_GuildId_Keyword"); - - quoteEntity - .HasIndex(q => new { q.GuildId, q.Keyword }) - .IsUnique(); + var quoteEntity = modelBuilder.Entity(); #endregion - #region + + #region Donators + + var donatorEntity = modelBuilder.Entity(); + donatorEntity + .HasIndex(d => d.UserId) + .IsUnique(); + + #endregion + + #region Config + + var configEntity = modelBuilder.Entity(); + configEntity + .HasIndex(c => c.GuildId) + .IsUnique(); + #endregion } protected abstract override void OnConfiguring(DbContextOptionsBuilder optionsBuilder); diff --git a/src/NadekoBot/Services/Database/Repositories/IConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/IConfigRepository.cs new file mode 100644 index 00000000..7d6d56d4 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IConfigRepository.cs @@ -0,0 +1,15 @@ +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Database.Repositories.Impl; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IConfigRepository : IRepository + { + Config For(ulong guildId); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IDonatorsRepository.cs b/src/NadekoBot/Services/Database/Repositories/IDonatorsRepository.cs new file mode 100644 index 00000000..2fb86ea7 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IDonatorsRepository.cs @@ -0,0 +1,15 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IDonatorsRepository : IRepository + { + IEnumerable GetDonatorsOrdered(); + Donator AddOrUpdateDonator(ulong userId, string name, int amount); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IRepository.cs b/src/NadekoBot/Services/Database/Repositories/IRepository.cs index 2f616219..0beccfb5 100644 --- a/src/NadekoBot/Services/Database/Repositories/IRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IRepository.cs @@ -18,7 +18,7 @@ namespace NadekoBot.Services.Database.Repositories void Remove(int id); void Remove(T obj); - void RemoveRange(params int[] ids); + void RemoveRange(params T[] objs); void Update(T obj); void UpdateRange(params T[] objs); diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ConfigRepository.cs new file mode 100644 index 00000000..7a0e2a75 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ConfigRepository.cs @@ -0,0 +1,35 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class ConfigRepository : Repository, IConfigRepository + { + public ConfigRepository(DbContext context) : base(context) + { + } + /// + /// Gets and creates if it doesn't exist a config for a guild. + /// + /// + /// + public Config For(ulong guildId) + { + var config = _set.Where(c => c.GuildId == guildId).FirstOrDefault(); + + if (config == null) + { + _set.Add((config = new Config + { + GuildId = guildId + })); + } + return config; + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/DonatorsRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/DonatorsRepository.cs new file mode 100644 index 00000000..b0fc3cd1 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/DonatorsRepository.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class DonatorsRepository : Repository, IDonatorsRepository + { + public DonatorsRepository(DbContext context) : base(context) + { + } + + public Donator AddOrUpdateDonator(ulong userId, string name, int amount) + { + var donator = _set.Find(userId); + + if (donator == null) + { + _set.Add(donator = new Donator + { + Amount = amount, + UserId = userId + }); + } + else + { + donator.Amount += amount; + _set.Update(donator); + } + + return donator; + } + + public IEnumerable GetDonatorsOrdered() => + _set.OrderByDescending(d => d.Amount).ToList(); + } +} diff --git a/src/NadekoBot/Services/Database/UnitOfWork.cs b/src/NadekoBot/Services/Database/UnitOfWork.cs index a477d86c..3c2d60f4 100644 --- a/src/NadekoBot/Services/Database/UnitOfWork.cs +++ b/src/NadekoBot/Services/Database/UnitOfWork.cs @@ -15,6 +15,11 @@ namespace NadekoBot.Services.Database private IQuoteRepository _quotes; public IQuoteRepository Quotes => _quotes ?? (_quotes = new QuoteRepository(_context)); + private IConfigRepository _configs; + public IConfigRepository Configs => _configs ?? (_configs = new ConfigRepository(_context)); + + private IDonatorsRepository _donators; + public IDonatorsRepository Donators => _donators ?? (_donators = new DonatorsRepository(_context)); public UnitOfWork(NadekoContext context) { diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs index 3c19c0be..23e4fcb9 100644 --- a/src/NadekoBot/Services/Impl/BotCredentials.cs +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -41,7 +41,10 @@ namespace NadekoBot.Services.Impl MashapeKey = cm.MashapeKey; OsuApiKey = cm.OsuApiKey; SoundCloudClientId = cm.SoundCloudClientId; - Db = new DB(string.IsNullOrWhiteSpace(cm.Db.Type) ? cm.Db.Type : "sqlite", cm.Db.ConnectionString); + if (cm.Db == null) + Db = new DB("sqlite", ""); + else + Db = new DB(cm.Db.Type, cm.Db.ConnectionString); } else _log.Fatal("credentials.json is missing. Failed to start."); diff --git a/src/NadekoBot/Classes/DBHandler.cs b/src/NadekoBot/_Classes/DBHandler.cs similarity index 100% rename from src/NadekoBot/Classes/DBHandler.cs rename to src/NadekoBot/_Classes/DBHandler.cs diff --git a/src/NadekoBot/Classes/ObservableConcurrentDictionary.cs b/src/NadekoBot/_Classes/ObservableConcurrentDictionary.cs similarity index 100% rename from src/NadekoBot/Classes/ObservableConcurrentDictionary.cs rename to src/NadekoBot/_Classes/ObservableConcurrentDictionary.cs diff --git a/src/NadekoBot/Classes/SearchHelper.cs b/src/NadekoBot/_Classes/SearchHelper.cs similarity index 100% rename from src/NadekoBot/Classes/SearchHelper.cs rename to src/NadekoBot/_Classes/SearchHelper.cs diff --git a/src/NadekoBot/Classes/ServerSpecificConfig.cs b/src/NadekoBot/_Classes/ServerSpecificConfig.cs similarity index 100% rename from src/NadekoBot/Classes/ServerSpecificConfig.cs rename to src/NadekoBot/_Classes/ServerSpecificConfig.cs diff --git a/src/NadekoBot/project.json b/src/NadekoBot/project.json index 5d2ba911..1aecd709 100644 --- a/src/NadekoBot/project.json +++ b/src/NadekoBot/project.json @@ -7,7 +7,7 @@ "emitEntryPoint": true, "allowUnsafe": true, "compile": { - "exclude": [ "_Models", "Classes", "_Modules" ] + "exclude": [ "_Models", "_Classes", "_Modules" ] }, "define": [] }, @@ -24,6 +24,7 @@ "System.Resources.ResourceWriter": "4.0.0-beta-22816", "Google.Apis.YouTube.v3": "1.15.0.582", "Google.Apis.Urlshortener.v1": "1.15.0.138", + "Google.Apis.Customsearch.v1": "1.16.0.466", "System.Diagnostics.Contracts": "4.0.1", "NLog": "4.4.0-betaV15", "VideoLibrary": "1.3.4", diff --git a/src/NadekoBot/project.lock.json b/src/NadekoBot/project.lock.json index f5ea0aac..ebd101b1 100644 --- a/src/NadekoBot/project.lock.json +++ b/src/NadekoBot/project.lock.json @@ -23,10 +23,10 @@ "lib/netstandard1.3/NCalc.dll": {} } }, - "Google.Apis/1.15.0": { + "Google.Apis/1.16.0": { "type": "package", "dependencies": { - "Google.Apis.Core": "1.15.0", + "Google.Apis.Core": "1.16.0", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", @@ -81,7 +81,7 @@ "lib/netstandard1.3/Google.Apis.Auth.dll": {} } }, - "Google.Apis.Core/1.15.0": { + "Google.Apis.Core/1.16.0": { "type": "package", "dependencies": { "Newtonsoft.Json": "9.0.1", @@ -108,6 +108,18 @@ "lib/netstandard1.3/Google.Apis.Core.dll": {} } }, + "Google.Apis.Customsearch.v1/1.16.0.466": { + "type": "package", + "dependencies": { + "Google.Apis": "1.16.0" + }, + "compile": { + "lib/netstandard1.3/Google.Apis.Customsearch.v1.dll": {} + }, + "runtime": { + "lib/netstandard1.3/Google.Apis.Customsearch.v1.dll": {} + } + }, "Google.Apis.Urlshortener.v1/1.15.0.138": { "type": "package", "dependencies": { @@ -3203,12 +3215,12 @@ "lib/netstandard1.3/NCalc.dll" ] }, - "Google.Apis/1.15.0": { - "sha512": "B//vbZgUsR0jdJztCJ0ORmVcAzhoiisIsxwc1libVjoZzu+kxUKNJKUl5Wlkj7V28kauS56y3hJUj3FMsgaJZQ==", + "Google.Apis/1.16.0": { + "sha512": "/p657K7J7p9aRl6QxUjQfNK0AobxnLHwDWdSqAr/17lZeQR+XMuEbIy7No80+zDeS39kKrRVWFlu1vHiws7gRQ==", "type": "package", - "path": "Google.Apis/1.15.0", + "path": "Google.Apis/1.16.0", "files": [ - "Google.Apis.1.15.0.nupkg.sha512", + "Google.Apis.1.16.0.nupkg.sha512", "Google.Apis.nuspec", "License.txt", "lib/net45/Google.Apis.PlatformServices.dll", @@ -3286,12 +3298,12 @@ "lib/wpa81/Google.Apis.Auth.xml" ] }, - "Google.Apis.Core/1.15.0": { - "sha512": "izmsmat5RRL0bmJ2tr3SvlqEiGf40wDeOTHv0PJOVUvHoZBdAaVbbZFxtevOWOyo878mwpYGXfzMs5Zfoyfrfw==", + "Google.Apis.Core/1.16.0": { + "sha512": "uATqVAsPHlpOf+3VV51xFEIghAMSLJyh1hqoVpnPOlkXBx7fkOcs0qP3tKXtmsaeuyC/7BYM1Tt42lyIyhY2lQ==", "type": "package", - "path": "Google.Apis.Core/1.15.0", + "path": "Google.Apis.Core/1.16.0", "files": [ - "Google.Apis.Core.1.15.0.nupkg.sha512", + "Google.Apis.Core.1.16.0.nupkg.sha512", "Google.Apis.Core.nuspec", "License.txt", "lib/net45/Google.Apis.Core.dll", @@ -3305,6 +3317,24 @@ "lib/portable-net45+sl50+netcore45+wpa81+wp8/Google.Apis.Core.xml" ] }, + "Google.Apis.Customsearch.v1/1.16.0.466": { + "sha512": "uZ5VP/xKgTZCMJXgEfwHYEIR/7FsbtvL+bncm08a07RhWLFZTdRpCAkNL4b5wADHkblpkBe1VWKv43W77I4tJw==", + "type": "package", + "path": "Google.Apis.Customsearch.v1/1.16.0.466", + "files": [ + "Google.Apis.Customsearch.v1.1.16.0.466.nupkg.sha512", + "Google.Apis.Customsearch.v1.nuspec", + "lib/netstandard1.3/Google.Apis.Customsearch.v1.dll", + "lib/netstandard1.3/Google.Apis.Customsearch.v1.pdb", + "lib/netstandard1.3/Google.Apis.Customsearch.v1.xml", + "lib/portable-net40+sl50+netcore45+wpa81+wp8/Google.Apis.Customsearch.v1.dll", + "lib/portable-net40+sl50+netcore45+wpa81+wp8/Google.Apis.Customsearch.v1.pdb", + "lib/portable-net40+sl50+netcore45+wpa81+wp8/Google.Apis.Customsearch.v1.xml", + "lib/portable-net45+netcore45+wpa81+wp8/Google.Apis.Customsearch.v1.dll", + "lib/portable-net45+netcore45+wpa81+wp8/Google.Apis.Customsearch.v1.pdb", + "lib/portable-net45+netcore45+wpa81+wp8/Google.Apis.Customsearch.v1.xml" + ] + }, "Google.Apis.Urlshortener.v1/1.15.0.138": { "sha512": "67USnpqrk8tWO3LAgaK9qDQT6h8A7i7eUIOKm+OISThZoQuHiLCn6dbg46FVb597LUh57AxClSSbhnweYcYC3Q==", "type": "package", @@ -9004,6 +9034,7 @@ "": [ "CoreCLR-NCalc >= 2.1.0", "Discord.Net.Commands >= 1.0.0-dev", + "Google.Apis.Customsearch.v1 >= 1.16.0.466", "Google.Apis.Urlshortener.v1 >= 1.15.0.138", "Google.Apis.YouTube.v3 >= 1.15.0.582", "Microsoft.EntityFrameworkCore >= 1.0.0",