Advertisement
Inverness

SQLiteKeyValueStore.cs

May 9th, 2014
312
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 12.24 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Data.SQLite;
  6. using System.Diagnostics;
  7. using System.Text;
  8.  
  9. namespace Ramp.Data
  10. {
  11.     /// <summary>
  12.     ///     An SQLite-backed key value store.
  13.     /// </summary>
  14.     public sealed class SQLiteKeyValueStore : IKeyValueStore, IDisposable
  15.     {
  16.         /// <summary>
  17.         ///     The maximum length of a bucket string.
  18.         /// </summary>
  19.         public const uint MaxBucketLength = 64;
  20.  
  21.         /// <summary>
  22.         ///     The maximum length of a key string.
  23.         /// </summary>
  24.         public const uint MaxKeyLength = 192;
  25.  
  26.         public const string DefaultBucketName = "";
  27.  
  28.         public const string MetadataBucketName = "_M";
  29.  
  30.         private const long CurrentVersion = 1;
  31.  
  32.         private readonly SQLiteCommand _get;
  33.         private readonly SQLiteCommand _set;
  34.         private readonly SQLiteCommand _delete;
  35.         private readonly SQLiteCommand _exists;
  36.         private readonly SQLiteCommand _getItems;
  37.         private readonly SQLiteCommand _getKeys;
  38.  
  39.         private readonly Func<Type, object, string> _convertToStringFunc;
  40.         private readonly Func<Type, string, object> _convertFromStringFunc;
  41.  
  42.         private readonly Dictionary<Type, TypeConverter> _converters = new Dictionary<Type, TypeConverter>();
  43.  
  44.         public SQLiteKeyValueStore(string connectionString,
  45.                                    Func<Type, object, string> convertToStringFunc = null,
  46.                                    Func<Type, string, object> convertFromStringFunc = null)
  47.         {
  48.             if (connectionString == null)
  49.                 throw new ArgumentNullException("connectionString");
  50.  
  51.             _convertToStringFunc = convertToStringFunc ?? DefaultConvertToString;
  52.             _convertFromStringFunc = convertFromStringFunc ?? DefaultConvertFromString;
  53.  
  54.             // Create/open the database
  55.             Connection = new SQLiteConnection(connectionString);
  56.             Connection.Open();
  57.  
  58.             // Update the database schema to latest version
  59.             Update();
  60.  
  61.             // Prepare all needed commands
  62.             _get = new SQLiteCommand("SELECT Value FROM Store WHERE Bucket = ? AND Key = ?", Connection);
  63.             _get.Parameters.Add(null, DbType.String);
  64.             _get.Parameters.Add(null, DbType.String);
  65.             _get.Prepare();
  66.  
  67.             _set = new SQLiteCommand("INSERT OR REPLACE INTO Store (Bucket, Key, Value) VALUES (?, ?, ?)", Connection);
  68.             _set.Parameters.Add(null, DbType.String);
  69.             _set.Parameters.Add(null, DbType.String);
  70.             _set.Parameters.Add(null, DbType.Binary);
  71.             _set.Prepare();
  72.  
  73.             _delete = new SQLiteCommand("DELETE FROM Store WHERE Bucket = ? AND Key = ?", Connection);
  74.             _delete.Parameters.Add(null, DbType.String);
  75.             _delete.Parameters.Add(null, DbType.String);
  76.             _delete.Prepare();
  77.  
  78.             _exists = new SQLiteCommand("SELECT COUNT(Key) FROM Store WHERE Bucket = ? AND Key = ?", Connection);
  79.             _exists.Parameters.Add(null, DbType.String);
  80.             _exists.Parameters.Add(null, DbType.String);
  81.             _exists.Prepare();
  82.  
  83.             _getItems = new SQLiteCommand("SELECT Key, Value FROM Store WHERE Bucket = ?", Connection);
  84.             _getItems.Parameters.Add(null, DbType.String);
  85.             _getItems.Prepare();
  86.  
  87.             _getKeys = new SQLiteCommand("SELECT Key FROM Store WHERE Bucket = ?", Connection);
  88.             _getKeys.Parameters.Add(null, DbType.String);
  89.             _getKeys.Prepare();
  90.         }
  91.  
  92.         /// <summary>
  93.         ///     The SQLite connection.
  94.         /// </summary>
  95.         public SQLiteConnection Connection { get; private set; }
  96.  
  97.         public Encoding Encoding { get { return Encoding.UTF8; } }
  98.  
  99.         string IKeyValueStore.DefaultBucketName
  100.         {
  101.             get { return DefaultBucketName; }
  102.         }
  103.  
  104.         string IKeyValueStore.MetadataBucketName
  105.         {
  106.             get { return MetadataBucketName; }
  107.         }
  108.  
  109.         public void Dispose()
  110.         {
  111.             _getKeys.Dispose();
  112.             _getItems.Dispose();
  113.             _delete.Dispose();
  114.             _set.Dispose();
  115.             _get.Dispose();
  116.             Connection.Dispose();
  117.         }
  118.  
  119.         public bool TryGetBytes(string bucket, string key, out byte[] value)
  120.         {
  121.             ValidateArgs(bucket, key);
  122.  
  123.             _get.Parameters[0].Value = bucket ?? DefaultBucketName;
  124.             _get.Parameters[1].Value = key;
  125.             object resultBase = _get.ExecuteScalar();
  126.  
  127.             // Should only be DBNull if the entry doesn't exist in the database.
  128.             if (Convert.IsDBNull(resultBase))
  129.             {
  130.                 value = null;
  131.                 return false;
  132.             }
  133.  
  134.             value = (byte[]) resultBase;
  135.             return true;
  136.         }
  137.  
  138.         public bool TryGetString(string bucket, string key, out string value)
  139.         {
  140.             ValidateArgs(bucket, key);
  141.  
  142.             byte[] resultBytes;
  143.             if (!TryGetBytes(bucket, key, out resultBytes))
  144.             {
  145.                 value = null;
  146.                 return false;
  147.             }
  148.  
  149.             value = Encoding.GetString(resultBytes);
  150.             return true;
  151.         }
  152.  
  153.         public bool TryGetValue<T>(string bucket, string key, out T value)
  154.         {
  155.             ValidateArgs(bucket, key);
  156.  
  157.             string resultString;
  158.             if (!TryGetString(bucket, key, out resultString))
  159.             {
  160.                 value = default(T);
  161.                 return false;
  162.             }
  163.  
  164.             try
  165.             {
  166.                 value = (T) _convertFromStringFunc(typeof(T), resultString);
  167.                 return true;
  168.             }
  169.             catch (Exception)
  170.             {
  171.                 value = default(T);
  172.                 return false;
  173.             }
  174.         }
  175.  
  176.         public byte[] GetBytesOrDefault(string bucket, string key, byte[] defaultValue = null)
  177.         {
  178.             byte[] result;
  179.             return TryGetBytes(bucket, key, out result) ? result : defaultValue;
  180.         }
  181.  
  182.         public string GetStringOrDefault(string bucket, string key, string defaultValue = null)
  183.         {
  184.             string result;
  185.             return TryGetString(bucket, key, out result) ? result : defaultValue;
  186.         }
  187.  
  188.         public T GetValueOrDefault<T>(string bucket, string key, T defaultValue = default(T))
  189.         {
  190.             T result;
  191.             return TryGetValue(bucket, key, out result) ? result : defaultValue;
  192.         }
  193.  
  194.         public void Set(string bucket, string key, string value)
  195.         {
  196.             Set(bucket, key, value == null ? null : Encoding.GetBytes(value));
  197.         }
  198.  
  199.         public void Set(string bucket, string key, byte[] value)
  200.         {
  201.             ValidateArgs(bucket, key);
  202.  
  203.             _set.Parameters[0].Value = bucket ?? DefaultBucketName;
  204.             _set.Parameters[1].Value = key;
  205.             _set.Parameters[2].Value = value;
  206.             _set.ExecuteNonQuery();
  207.             _set.Parameters[2].Value = null; // value could be large, don't prevent a GC
  208.         }
  209.  
  210.         public void Set<T>(string bucket, string key, T value)
  211.         {
  212.             if (value is DBNull)
  213.                 throw new ArgumentOutOfRangeException("value", "value cannot be DBNull");
  214.  
  215.             string stringValue = _convertToStringFunc(typeof(T), value);
  216.  
  217.             Set(bucket, key, stringValue);
  218.         }
  219.  
  220.         public bool Delete(string bucket, string key)
  221.         {
  222.             ValidateArgs(bucket, key);
  223.  
  224.             _delete.Parameters[0].Value = bucket ?? DefaultBucketName;
  225.             _delete.Parameters[1].Value = key;
  226.             return _delete.ExecuteNonQuery() != 0;
  227.         }
  228.  
  229.         public bool Exists(string bucket, string key)
  230.         {
  231.             ValidateArgs(bucket, key);
  232.  
  233.             _exists.Parameters[0].Value = bucket ?? DefaultBucketName;
  234.             _exists.Parameters[1].Value = key;
  235.             return (long) _exists.ExecuteScalar() != 0;
  236.         }
  237.  
  238.         public IEnumerable<KeyValuePair<string, byte[]>> GetItemBytes(string bucket)
  239.         {
  240.             ValidateBucket(bucket);
  241.  
  242.             foreach (var item in GetItems(bucket))
  243.             {
  244.                 yield return new KeyValuePair<string, byte[]>(item.Key, (byte[]) item.Value);
  245.             }
  246.         }
  247.  
  248.         public IEnumerable<KeyValuePair<string, string>> GetItemStrings(string bucket)
  249.         {
  250.             ValidateBucket(bucket);
  251.  
  252.             foreach (KeyValuePair<string, object> item in GetItems(bucket))
  253.             {
  254.                 string stringValue = item.Value != null ? Encoding.GetString((byte[]) item.Value) : null;
  255.                 yield return new KeyValuePair<string, string>(item.Key, stringValue);
  256.             }
  257.         }
  258.  
  259.         public IEnumerable<string> GetKeys(string bucket)
  260.         {
  261.             ValidateBucket(bucket);
  262.  
  263.             _getItems.Parameters[0].Value = bucket ?? DefaultBucketName;
  264.             using (SQLiteDataReader reader = _getItems.ExecuteReader())
  265.                 while (reader.Read())
  266.                     yield return reader.GetString(0);
  267.         }
  268.  
  269.         public IDbTransaction BeginTransaction()
  270.         {
  271.             return Connection.BeginTransaction();
  272.         }
  273.  
  274.         public IDbTransaction BeginTransaction(IsolationLevel isolationLevel)
  275.         {
  276.             return Connection.BeginTransaction(isolationLevel);
  277.         }
  278.  
  279.         private string DefaultConvertToString(Type type, object value)
  280.         {
  281.             return GetConverter(type).ConvertToString(value);
  282.         }
  283.  
  284.         private object DefaultConvertFromString(Type type, string value)
  285.         {
  286.             return GetConverter(type).ConvertFromString(value);
  287.         }
  288.  
  289.         // Caches type converters to avoid entering the type descriptor machinery each time.
  290.         private TypeConverter GetConverter(Type type)
  291.         {
  292.             TypeConverter result;
  293.             if (!_converters.TryGetValue(type, out result))
  294.             {
  295.                 result = TypeDescriptor.GetConverter(type);
  296.                 _converters.Add(type, result);
  297.             }
  298.             return result;
  299.         }
  300.  
  301.         private IEnumerable<KeyValuePair<string, object>> GetItems(string bucket)
  302.         {
  303.             _getItems.Parameters[0].Value = bucket ?? DefaultBucketName;
  304.             using (SQLiteDataReader reader = _getItems.ExecuteReader())
  305.             {
  306.                 while (reader.Read())
  307.                 {
  308.                     string key = reader.GetString(0);
  309.                     object value = reader.GetValue(1);
  310.                     Debug.Assert(!Convert.IsDBNull(value));
  311.                     yield return new KeyValuePair<string, object>(key, value);
  312.                 }
  313.             }
  314.         }
  315.  
  316.         // Updates the store to the latest schema
  317.         private void Update()
  318.         {
  319.             using (SQLiteTransaction transaction = Connection.BeginTransaction())
  320.             using (SQLiteCommand cmd = Connection.CreateCommand())
  321.             {
  322.                 cmd.CommandText = "PRAGMA user_version";
  323.                 var version = (long) cmd.ExecuteScalar();
  324.  
  325.                 if (version < 1)
  326.                 {
  327.                     cmd.CommandText =
  328.                         "CREATE TABLE Store (Bucket TEXT NOT NULL, Key TEXT NOT NULL, Value BLOB, PRIMARY KEY (Bucket, Key))";
  329.                     cmd.ExecuteNonQuery();
  330.                 }
  331.  
  332.                 cmd.CommandText = "PRAGMA user_version = " + CurrentVersion;
  333.                 cmd.ExecuteNonQuery();
  334.  
  335.                 transaction.Commit();
  336.             }
  337.         }
  338.  
  339.         private void ValidateBucket(string bucket)
  340.         {
  341.             if (bucket != null && bucket.Length > MaxBucketLength)
  342.                 throw new ArgumentOutOfRangeException("bucket");
  343.         }
  344.  
  345.         private void ValidateArgs(string bucket, string key)
  346.         {
  347.             if (bucket != null && bucket.Length > MaxBucketLength)
  348.                 throw new ArgumentOutOfRangeException("bucket");
  349.             if (key == null)
  350.                 throw new ArgumentNullException("key");
  351.             if (key.Length > MaxKeyLength)
  352.                 throw new ArgumentOutOfRangeException("key");
  353.         }
  354.     }
  355. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement