diff --git a/.gitignore b/.gitignore
index 917391e..e53e0f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,10 @@
-**/bin/
-**/obj
+**\bin
+**\obj
*.suo
*.swp
+*.csproj.user
+*.nupkg
+*.VisualState.xml
+TestResult.xml
+**\packages
+*.zip
diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config
new file mode 100644
index 0000000..6a318ad
--- /dev/null
+++ b/.nuget/NuGet.Config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe
new file mode 100644
index 0000000..b5c8886
Binary files /dev/null and b/.nuget/NuGet.exe differ
diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets
new file mode 100644
index 0000000..e470f19
--- /dev/null
+++ b/.nuget/NuGet.targets
@@ -0,0 +1,71 @@
+
+
+
+ $(MSBuildProjectDirectory)\..\
+
+
+ $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
+ $([System.IO.Path]::Combine($(ProjectDir), "packages.config"))
+ $([System.IO.Path]::Combine($(SolutionDir), "packages"))
+
+
+ $(SolutionDir).nuget
+ packages.config
+ $(SolutionDir)packages
+
+
+ $(NuGetToolsPath)\nuget.exe
+ "$(NuGetExePath)"
+ mono --runtime=v4.0.30319 $(NuGetExePath)
+
+ $(TargetDir.Trim('\\'))
+
+
+ ""
+
+
+ false
+
+
+ false
+
+
+ $(NuGetCommand) install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)"
+ $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols
+
+
+
+ RestorePackages;
+ $(BuildDependsOn);
+
+
+
+
+ $(BuildDependsOn);
+ BuildPackage;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Lasy.nunit b/Lasy.nunit
new file mode 100644
index 0000000..702599f
--- /dev/null
+++ b/Lasy.nunit
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/Lasy.nuspec b/Lasy.nuspec
new file mode 100644
index 0000000..ff9f8be
--- /dev/null
+++ b/Lasy.nuspec
@@ -0,0 +1,26 @@
+
+
+
+ Lasy
+ 1.2.0.8
+ Lasy
+ Trinity Western University
+ Brian DeJong
+ false
+ A lightweight CRUD abstraction layer for data storage.
+
+ Copyright 2014
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Lasy.sln b/Lasy.sln
new file mode 100644
index 0000000..02b93af
--- /dev/null
+++ b/Lasy.sln
@@ -0,0 +1,40 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Express 2013 for Web
+VisualStudioVersion = 12.0.21005.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lasy", "src\Lasy\Lasy.csproj", "{99A415F9-8D5A-4977-AC8B-86EA82C891D3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lasy.Tests", "test\Lasy.Tests\Lasy.Tests.csproj", "{088A595B-4550-475E-B1D0-47CB70FBDE6E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{ECC3DC6F-4EBA-4E2A-BE09-D7EAC57D1101}"
+ ProjectSection(SolutionItems) = preProject
+ .nuget\NuGet.exe = .nuget\NuGet.exe
+ .nuget\NuGet.targets = .nuget\NuGet.targets
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lasy-net4", "src\Lasy-net4\Lasy-net4.csproj", "{1D26E9C4-24C7-4D6A-8C47-F608F6314F58}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {99A415F9-8D5A-4977-AC8B-86EA82C891D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {99A415F9-8D5A-4977-AC8B-86EA82C891D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {99A415F9-8D5A-4977-AC8B-86EA82C891D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {99A415F9-8D5A-4977-AC8B-86EA82C891D3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {088A595B-4550-475E-B1D0-47CB70FBDE6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {088A595B-4550-475E-B1D0-47CB70FBDE6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {088A595B-4550-475E-B1D0-47CB70FBDE6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {088A595B-4550-475E-B1D0-47CB70FBDE6E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1D26E9C4-24C7-4D6A-8C47-F608F6314F58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1D26E9C4-24C7-4D6A-8C47-F608F6314F58}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1D26E9C4-24C7-4D6A-8C47-F608F6314F58}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1D26E9C4-24C7-4D6A-8C47-F608F6314F58}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/lib/nunit.framework.dll b/lib/nunit.framework.dll
deleted file mode 100644
index 6856e51..0000000
Binary files a/lib/nunit.framework.dll and /dev/null differ
diff --git a/packages/repositories.config b/packages/repositories.config
new file mode 100644
index 0000000..ba63e9e
--- /dev/null
+++ b/packages/repositories.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/FakeDB.cs b/src/FakeDB.cs
deleted file mode 100644
index ecaa2be..0000000
--- a/src/FakeDB.cs
+++ /dev/null
@@ -1,115 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Lasy;
-using Nvelope;
-
-namespace Lasy
-{
- public class FakeDB : IReadable, IWriteable, IReadWrite
- {
- public Dictionary DataStore = new Dictionary();
-
- public FakeDBTable Table(string tableName)
- {
- if (DataStore.ContainsKey(tableName))
- return DataStore[tableName];
- else
- return new FakeDBTable();
- }
-
- public IEnumerable> RawRead(string tableName, Dictionary id, ITransaction transaction = null)
- {
- if (!DataStore.ContainsKey(tableName))
- return new List>();
-
- return DataStore[tableName].FindByFieldValues(id);
- }
-
- public IEnumerable> RawReadCustomFields(string tableName, IEnumerable fields, Dictionary id, ITransaction transaction = null)
- {
- return DataStore[tableName].FindByFieldValues(id).Select(row => row.WhereKeys(key => fields.Contains(key)));
- }
-
- public IEnumerable> RawReadAll(string tableName, ITransaction transaction = null)
- {
- if (!DataStore.ContainsKey(tableName))
- return new List>();
-
- return DataStore[tableName];
- }
-
- public IEnumerable> RawReadAllCustomFields(string tableName, IEnumerable fields, ITransaction transaction = null)
- {
- return DataStore[tableName].Select(row => row.WhereKeys(key => fields.Contains(key)));
- }
-
- private IDBAnalyzer _analyzer = new FakeDBAnalyzer();
-
- public IDBAnalyzer Analyzer
- {
- get { return _analyzer; }
- set { _analyzer = value; }
- }
-
- public Dictionary Insert(string tableName, Dictionary row, ITransaction transaction = null)
- {
- if (!DataStore.ContainsKey(tableName))
- DataStore.Add(tableName, new FakeDBTable());
-
- var table = DataStore[tableName];
-
- var dictToUse = row.Copy();
- //var id = DataStore[tableName].Count + 1;
- var primaryKeys = Analyzer.GetPrimaryKeys(tableName);
- var autoKey = Analyzer.GetAutoNumberKey(tableName);
-
- if (autoKey != null)
- {
- if (!dictToUse.ContainsKey(autoKey))
- dictToUse.Add(autoKey, table.NextAutoKey++);
- else
- dictToUse[autoKey] = table.NextAutoKey++;
- }
-
- var invalid = primaryKeys.Where(key => dictToUse[key] == null);
- if (invalid.Any())
- throw new KeyNotSetException(tableName, invalid);
-
- table.Add(dictToUse);
- return dictToUse.WhereKeys(key => primaryKeys.Contains(key));
- }
-
- public void Delete(string tableName, Dictionary fieldValues, ITransaction transaction = null)
- {
- if (DataStore.ContainsKey(tableName))
- {
- var victims = DataStore[tableName].FindByFieldValues(fieldValues).ToList();
- victims.ForEach(x => DataStore[tableName].Remove(x));
- }
- }
-
- public void Update(string tableName, Dictionary dataFields, Dictionary keyFields, ITransaction transaction = null)
- {
- if(!DataStore.ContainsKey(tableName))
- return;
-
- var victims = DataStore[tableName].Where(r => r.IsSameAs(keyFields, keyFields.Keys))
- .Where(r => r != dataFields && r != keyFields); // Don't update if we've passed in the object itself,
- // because at that point the change has already been made by a sneaky back-door reference change,
- // and if we try to modify it here, we'll modify the collection while iterating over it, causing an exception
- // The non-hacky fix would be to return copies of the rows from the Read methods. That way, the user couldn't
- // make sneaky back-door changes. However, this would be a substantial performance penalty. Grr, I want
- // Clojure's persistent collections here...
- foreach (var vic in victims)
- foreach (var key in dataFields.Keys)
- vic[key] = dataFields[key];
- }
-
- public ITransaction BeginTransaction()
- {
- return new FakeDBTransaction();
- }
- }
-}
diff --git a/src/FakeDBTable.cs b/src/FakeDBTable.cs
deleted file mode 100644
index 4081598..0000000
--- a/src/FakeDBTable.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Nvelope;
-
-namespace Lasy
-{
- public class FakeDBTable : List>
- {
- public IEnumerable> FindByFieldValues(Dictionary values)
- {
- return this.Where(x => filter(x, values));
- }
-
- private bool filter(Dictionary row, Dictionary values)
- {
- return values.Keys.All(field => row.ContainsKey(field) && row[field].Eq(values[field]));
- }
-
- public int NextAutoKey = 1;
- }
-}
diff --git a/src/FakeDBTransaction.cs b/src/FakeDBTransaction.cs
deleted file mode 100644
index d4aa592..0000000
--- a/src/FakeDBTransaction.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Nvelope;
-
-namespace Lasy
-{
- ///
- /// Doesn't actually work. TODO: Implement this
- ///
- public class FakeDBTransaction : ITransaction
- {
- public void Commit()
- {
- }
-
- public void Rollback()
- {
- }
- }
-}
diff --git a/src/IReadable.cs b/src/IReadable.cs
deleted file mode 100644
index 31ba4c4..0000000
--- a/src/IReadable.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Lasy
-{
- public interface IReadable
- {
- ///
- /// Read all that match on id from the table
- ///
- ///
- ///
- ///
- ///
- IEnumerable> RawRead(string tableName, Dictionary id, ITransaction transaction = null);
- ///
- /// Read just the specified fields from the table, filtering down to just the rows that match on id
- ///
- ///
- ///
- ///
- ///
- ///
- IEnumerable> RawReadCustomFields(string tableName, IEnumerable fields, Dictionary id, ITransaction transaction = null);
- ///
- /// Get the analyzer for the DB
- ///
- IDBAnalyzer Analyzer { get; }
- ///
- /// Read all the rows from a table
- ///
- ///
- ///
- ///
- IEnumerable> RawReadAll(string tableName, ITransaction transaction = null);
- ///
- /// Read just the specified fields from the table, but for all rows
- ///
- ///
- ///
- ///
- ///
- IEnumerable> RawReadAllCustomFields(string tableName, IEnumerable fields, ITransaction transaction = null);
- }
-}
diff --git a/src/IWriteable.cs b/src/IWriteable.cs
deleted file mode 100644
index 9655a43..0000000
--- a/src/IWriteable.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Lasy
-{
- public interface IWriteable
- {
- Dictionary Insert(string tableName, Dictionary row, ITransaction transaction = null);
-
- void Delete(string tableName, Dictionary row, ITransaction transaction = null);
-
- void Update(string tableName, Dictionary dataFields, Dictionary keyFields, ITransaction transaction = null);
-
- IDBAnalyzer Analyzer { get; }
-
- ITransaction BeginTransaction();
- }
-}
diff --git a/src/Lasy-net4/Lasy-net4.csproj b/src/Lasy-net4/Lasy-net4.csproj
new file mode 100644
index 0000000..5960d53
--- /dev/null
+++ b/src/Lasy-net4/Lasy-net4.csproj
@@ -0,0 +1,131 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {1D26E9C4-24C7-4D6A-8C47-F608F6314F58}
+ Library
+ Properties
+ Lasy
+ Lasy
+ v4.0
+ 512
+ ..\..\Lasy\
+ true
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+ bin\Release\Lasy.XML
+
+
+
+ False
+ ..\..\packages\MySql.Data.6.7.4\lib\net40\MySql.Data.dll
+
+
+ False
+ ..\..\packages\Newtonsoft.Json.6.0.1\lib\net40\Newtonsoft.Json.dll
+
+
+ False
+ ..\..\packages\Nvelope.1.1.0.2\lib\net40\Nvelope.dll
+
+
+
+
+ ..\..\packages\Rx-Core.2.2.2\lib\net40\System.Reactive.Core.dll
+
+
+ ..\..\packages\Rx-Interfaces.2.2.2\lib\net40\System.Reactive.Interfaces.dll
+
+
+ ..\..\packages\Rx-Linq.2.2.2\lib\net40\System.Reactive.Linq.dll
+
+
+ ..\..\packages\Rx-PlatformServices.2.2.3\lib\net40\System.Reactive.PlatformServices.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Lasy-net4/packages.config b/src/Lasy-net4/packages.config
new file mode 100644
index 0000000..09b8afe
--- /dev/null
+++ b/src/Lasy-net4/packages.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lasy.csproj b/src/Lasy.csproj
deleted file mode 100644
index 815e51e..0000000
--- a/src/Lasy.csproj
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
- Debug
- AnyCPU
- 8.0.30703
- 2.0
- {99A415F9-8D5A-4977-AC8B-86EA82C891D3}
- Library
- Properties
- Lasy
- Lasy
- v4.0
- 512
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {AC4A93B6-DDB6-4FE1-B528-665DE101052B}
- Nvelope
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Lasy/AbstractSqlReadWrite.cs b/src/Lasy/AbstractSqlReadWrite.cs
new file mode 100644
index 0000000..bb0b79e
--- /dev/null
+++ b/src/Lasy/AbstractSqlReadWrite.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Nvelope;
+
+namespace Lasy
+{
+ public abstract class AbstractSqlReadWrite : IReadWrite
+ {
+ public AbstractSqlReadWrite(string connectionString, SqlAnalyzer analyzer, bool strictTables = true)
+ {
+ SqlAnalyzer = analyzer;
+ ConnectionString = connectionString;
+ StrictTables = strictTables;
+ }
+
+ public string ConnectionString { get; protected set; }
+
+ protected abstract IEnumerable> sqlRead(string sql, Dictionary values);
+ protected abstract int? sqlInsert(string sql, Dictionary values);
+ protected abstract void sqlUpdate(string sql, Dictionary values);
+
+ public virtual string QualifiedTable(string tablename)
+ {
+ var schema = SqlAnalyzer.SchemaName(tablename);
+ var table = SqlAnalyzer.TableName(tablename);
+
+ if (schema.IsNullOrEmpty())
+ return "[" + tablename + "]";
+ else
+ return "[" + schema + "].[" + table + "]";
+ }
+
+ ///
+ /// Warning: You should greatly prefer using SQL parameters instead of using literals.
+ /// Literals are vulnerable to SQL injection attacks
+ ///
+ ///
+ ///
+ public static string SqlLiteral(object o)
+ {
+ if (o == null || o == DBNull.Value)
+ return "null";
+ if (o is string || o is DateTime)
+ return "'" + o.ToString().Replace("'", "''") + "'";
+ else
+ return o.ToString();
+ }
+
+ ///
+ /// If true, throw an exception when referencing tables that don't exist.
+ /// If false, do something intelligent instead - Reads return nothing, updates and
+ /// deletes do nothing, but inserts still throw exceptions
+ ///
+ public virtual bool StrictTables { get; set; }
+
+ public virtual string MakeWhereClause(Dictionary keyFields, string paramPrefix = "", bool useParameters = true)
+ {
+ keyFields = keyFields ?? new Dictionary();
+
+ // TODO: Figure out how to pass null values as parameters
+ // instead of hardcoding them in here
+ var nullFields = keyFields.WhereValues(v => v == DBNull.Value || v == null).Keys;
+ var nullFieldParts = nullFields.Select(x => x + " is null");
+
+ var nonNullFields = keyFields.Except(nullFields).Keys;
+ var nonNullFieldParts =
+ useParameters ?
+ nonNullFields.Select(x => x + " = @" + paramPrefix + x) :
+ nonNullFields.Select(x => x + " = " + SqlLiteral(keyFields[x]));
+
+ var whereClause = "";
+ if (keyFields.Any())
+ whereClause = " WHERE " + nullFieldParts.And(nonNullFieldParts).Join(" AND ");
+
+ return whereClause;
+ }
+
+ protected Dictionary _coerceToTableTypes(string tableName, Dictionary data)
+ {
+ var fieldTypes = SqlAnalyzer.GetFieldTypes(tableName);
+ return data.Select(kv => new KeyValuePair(
+ kv.Key,
+ data[kv.Key].ConvertTo(SqlTypeConversion.GetDotNetType(fieldTypes[kv.Key]))))
+ .ToDictionary();
+ }
+
+ public virtual string MakeReadSql(string tableName, Dictionary keyFields, IEnumerable fields = null, bool useParameters = true)
+ {
+ fields = fields ?? new string[]{};
+
+ var fieldClause = "*";
+ if (fields.Any())
+ fieldClause = fields.Join(", ");
+
+ var coercedKeys = _coerceToTableTypes(tableName, keyFields);
+ var whereClause = MakeWhereClause(coercedKeys, "", useParameters);
+
+ var sql = "SELECT " + fieldClause + " FROM " + QualifiedTable(tableName) + whereClause;
+
+ return sql;
+ }
+
+ public virtual string GetInsertedAutonumber()
+ {
+ return "SELECT SCOPE_IDENTITY()";
+ }
+
+ public virtual string MakeInsertSql(string tableName, Dictionary row, bool useParameters = true, bool selectIdentity = true)
+ {
+ //Retrieve the AutoNumbered key name if there is one
+ var autoNumberKeyName = Analyzer.GetAutoNumberKey(tableName);
+
+ // Keep in mind that GetFields might return an empty list, which means that it doesn't know
+ var dbFields = Analyzer.GetFields(tableName).Or(row.Keys);
+ // Take out the autonumber keys, and take out any supplied data fields
+ // that aren't actually fields in the DB
+ var fieldNames = row.Keys.Except(autoNumberKeyName)
+ .Intersect(dbFields);
+
+ var valList = useParameters ?
+ fieldNames.Select(x => "@" + x) :
+ fieldNames.Select(x => SqlLiteral(row[x]));
+
+ var sql = "INSERT INTO " + QualifiedTable(tableName) + " (" + fieldNames.Join(", ") + ") " +
+ "VALUES (" + valList.Join(", ") + ")\n";
+ if (selectIdentity)
+ sql += GetInsertedAutonumber();
+
+ return sql;
+ }
+
+ public virtual string MakeUpdateSql(string tableName, Dictionary dataFields, Dictionary keyFields, bool useParameters = true)
+ {
+ var autoKey = Analyzer.GetAutoNumberKey(tableName);
+
+ var setFields = dataFields.Keys.Except(autoKey);
+ var dbFields = Analyzer.GetFields(tableName);
+ // Don't try to set fields that don't exist in the database
+ if (dbFields.Any()) // If we don't get anything back, that means we don't know what the DB fields are
+ setFields = setFields.Intersect(dbFields);
+
+ var whereClause = MakeWhereClause(keyFields, "key", useParameters);
+
+ var valFields =
+ useParameters ?
+ setFields.Select(x => x + " = @data" + x) :
+ setFields.Select(x => x + " = " + SqlLiteral(dataFields[x]));
+
+ var sql = "UPDATE " + QualifiedTable(tableName) + " SET " + valFields.Join(", ") + "\n" + whereClause;
+ return sql;
+ }
+
+ public virtual string MakeDeleteSql(string tableName, Dictionary keyFields, bool useParameters = true)
+ {
+ var whereClause = MakeWhereClause(keyFields, "", useParameters);
+ return "DELETE FROM " + QualifiedTable(tableName) + whereClause;
+ }
+
+ public virtual IEnumerable> RawRead(string tableName, Dictionary keyFields, IEnumerable fields = null)
+ {
+ // If the table doesn't exist, we probably don't want to run any sql
+ if (!Analyzer.TableExists(tableName))
+ if (StrictTables)
+ throw new NotATableException(tableName);
+ else
+ return new List>();
+
+ var sql = MakeReadSql(tableName, keyFields, fields);
+ return sqlRead(sql, keyFields);
+ }
+
+ public IDBAnalyzer Analyzer { get { return SqlAnalyzer; } }
+ public SqlAnalyzer SqlAnalyzer { get; protected set; }
+
+ public virtual Dictionary Insert(string tableName, Dictionary row)
+ {
+ if (StrictTables && !Analyzer.TableExists(tableName))
+ throw new NotATableException(tableName);
+
+ // Make sure all the required keys are supplied
+ this.AssertInsertKeys(tableName, row);
+
+ var sql = MakeInsertSql(tableName, row);
+ var autoKey = sqlInsert(sql, row);
+
+ // If there's an autonumber, make sure we add it to the result
+ var autoNumberKeyName = Analyzer.GetAutoNumberKey(tableName);
+ //if (autoNumberKeyName != null && autoKey == null)
+ // throw new ThisSadlyHappenedException("The SQL ran beautifully, but you were expecting an autogenerated number and you did not get it");
+ if(autoNumberKeyName != null && autoKey.HasValue)
+ row = row.Assoc(autoNumberKeyName, autoKey.Value);
+
+ // Return the set of primary keys from the insert operation
+ var primaryKeys = Analyzer.GetPrimaryKeys(tableName);
+ return row.Only(primaryKeys);
+ }
+
+ public virtual void Delete(string tableName, Dictionary keyFields)
+ {
+ if (!Analyzer.TableExists(tableName))
+ if (StrictTables)
+ throw new NotATableException(tableName);
+ else
+ return;
+
+ sqlUpdate(MakeDeleteSql(tableName, keyFields), keyFields);
+ }
+
+ public virtual void Update(string tableName, Dictionary dataFields, Dictionary keyFields)
+ {
+ if (!Analyzer.TableExists(tableName))
+ if (StrictTables)
+ throw new NotATableException(tableName);
+ else
+ return;
+
+ var sql = MakeUpdateSql(tableName, dataFields, keyFields);
+
+ var data = dataFields.SelectKeys(key => "data" + key);
+ var keys = keyFields.SelectKeys(key => "key" + key);
+
+ sqlUpdate(sql, data.Union(keys));
+ }
+ }
+}
diff --git a/src/Lasy/ConnectTo.cs b/src/Lasy/ConnectTo.cs
new file mode 100644
index 0000000..a764e97
--- /dev/null
+++ b/src/Lasy/ConnectTo.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Lasy
+{
+ ///
+ /// A bunch of helper methods to instantiate various database types
+ ///
+ public static class ConnectTo
+ {
+ public static SqlDB Sql2000(string connString, bool strictTables = true)
+ {
+ return new SqlDB(connString, new Sql2000Analyzer(connString), strictTables);
+ }
+
+ public static SqlDB Sql2005(string connString, bool strictTables = true)
+ {
+ return new SqlDB(connString, new SqlAnalyzer(connString), strictTables);
+ }
+
+ public static SqlDB MySql(string connString, bool strictTables = true)
+ {
+ return new MySqlDB(connString, new MySqlAnalyzer(connString), strictTables);
+ }
+
+ public static ModifiableSqlDB ModifiableSql2000(string connString, ITypedDBAnalyzer taxonomy = null)
+ {
+ var analyzer = new Sql2000Analyzer(connString);
+ var modifier = new SqlModifier(connString, analyzer, taxonomy);
+ var db = new SqlDB(connString, analyzer, false);
+ return new ModifiableSqlDB(db, modifier);
+ }
+
+ public static ModifiableSqlDB ModifiableSql2005(string connString, ITypedDBAnalyzer taxonomy = null)
+ {
+ var analyzer = new SqlAnalyzer(connString);
+ var modifier = new SqlModifier(connString, analyzer, taxonomy);
+ var db = new SqlDB(connString, analyzer, false);
+ return new ModifiableSqlDB(db, modifier);
+ }
+
+ public static ModifiableSqlDB ModifiableMySql(string connString, ITypedDBAnalyzer taxonomy = null)
+ {
+ var analyzer = new MySqlAnalyzer(connString);
+ var modifier = new MySqlModifier(connString, analyzer, taxonomy);
+ var db = new MySqlDB(connString, analyzer, false);
+ return new ModifiableSqlDB(db, modifier);
+ }
+
+ public static FileDB File(string directory, string fileExtension = ".rpt")
+ {
+ return new FileDB(directory, fileExtension);
+ }
+
+ public static FakeDB Memory()
+ {
+ return new FakeDB();
+ }
+
+ public static UnreliableDb Unreliable()
+ {
+ return new UnreliableDb();
+ }
+ }
+}
diff --git a/src/Lasy/DictionaryExtensions.cs b/src/Lasy/DictionaryExtensions.cs
new file mode 100644
index 0000000..c81bd81
--- /dev/null
+++ b/src/Lasy/DictionaryExtensions.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Nvelope;
+
+namespace Lasy
+{
+ public static class DictionaryExtensions
+ {
+ ///
+ /// Make sure that DBNull.Value is converted to null, so that we treat DBNull and null the same
+ /// on the backend
+ ///
+ ///
+ ///
+ public static Dictionary ScrubNulls(this Dictionary values)
+ {
+ var fields = values.Where(kv => kv.Value == DBNull.Value).Select(kv => kv.Key);
+ if (!fields.Any())
+ return values;
+ var res = values.Copy();
+ fields.Each(f => res[f] = null);
+ return res;
+ }
+ }
+}
diff --git a/src/Lasy/FakeDB.cs b/src/Lasy/FakeDB.cs
new file mode 100644
index 0000000..bbcea57
--- /dev/null
+++ b/src/Lasy/FakeDB.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Lasy;
+using Nvelope;
+
+namespace Lasy
+{
+ public class FakeDB : ITransactable, IRWEvented
+ {
+ public FakeDB()
+ : this(new FakeDBMeta())
+ { }
+
+ public FakeDB(IDBAnalyzer analyzer)
+ {
+ Analyzer = analyzer;
+ }
+
+ public Dictionary DataStore = new Dictionary();
+
+ public FakeDBTable Table(string tableName)
+ {
+ if (DataStore.ContainsKey(tableName))
+ return DataStore[tableName];
+ else
+ return new FakeDBTable();
+ }
+
+ public virtual void Wipe()
+ {
+ DataStore = new Dictionary();
+ }
+
+ public virtual IEnumerable> RawRead(string tableName, Dictionary keyFields, IEnumerable fields = null)
+ {
+ FireOnRead(tableName, keyFields);
+
+ if (!DataStore.ContainsKey(tableName))
+ return new List>();
+
+ return DataStore[tableName].Read(keyFields, fields);
+ }
+
+ private IDBAnalyzer _analyzer = new FakeDBMeta();
+
+ public IDBAnalyzer Analyzer
+ {
+ get { return _analyzer; }
+ set { _analyzer = value; }
+ }
+
+ public Dictionary NewAutokey(string tableName)
+ {
+ if (!DataStore.ContainsKey(tableName))
+ DataStore.Add(tableName, new FakeDBTable());
+
+ var table = DataStore[tableName];
+
+ var autoKey = Analyzer.GetAutoNumberKey(tableName);
+ if (autoKey == null)
+ return new Dictionary();
+ else
+ return new Dictionary() { { autoKey, table.NextAutoKey++ } };
+ }
+
+ public bool CheckKeys(string tableName, Dictionary row)
+ {
+ try
+ {
+ var res = this.ExtractKeys(tableName, row);
+ return true;
+ }
+ catch (KeyNotSetException)
+ {
+ return false;
+ }
+ }
+
+ public virtual Dictionary Insert(string tableName, Dictionary row)
+ {
+ FireOnInsert(tableName, row);
+
+ if (!DataStore.ContainsKey(tableName))
+ DataStore.Add(tableName, new FakeDBTable());
+
+ var table = DataStore[tableName];
+
+ row = row.ScrubNulls();
+
+ var autoKeys = NewAutokey(tableName);
+ var dictToUse = row.Union(autoKeys);
+ CheckKeys(tableName, dictToUse);
+ table.Add(dictToUse);
+
+ return this.ExtractKeys(tableName, dictToUse);
+ }
+
+ public virtual void Delete(string tableName, Dictionary fieldValues)
+ {
+ FireOnDelete(tableName, fieldValues);
+
+ if (DataStore.ContainsKey(tableName))
+ {
+ fieldValues = fieldValues.ScrubNulls();
+ var victims = DataStore[tableName].FindByFieldValues(fieldValues).ToList();
+ victims.ForEach(x => DataStore[tableName].Remove(x));
+ }
+ }
+
+ public virtual void Update(string tableName, Dictionary dataFields, Dictionary keyFields)
+ {
+ FireOnUpdate(tableName, dataFields, keyFields);
+
+ if(!DataStore.ContainsKey(tableName))
+ return;
+
+ dataFields = dataFields.ScrubNulls();
+ keyFields = keyFields.ScrubNulls();
+
+ var victims = DataStore[tableName].Where(r => r.IsSameAs(keyFields, keyFields.Keys, null));
+ foreach (var vic in victims)
+ foreach (var key in dataFields.Keys)
+ vic[key] = dataFields[key];
+ }
+
+ public virtual ITransaction BeginTransaction()
+ {
+ return new FakeDBTransaction(this);
+ }
+
+ public void FireOnInsert(string tableName, Dictionary keyFields)
+ {
+ if (OnInsert != null)
+ OnInsert(tableName, keyFields);
+ if (OnWrite != null)
+ OnWrite(tableName, keyFields);
+ }
+
+ public void FireOnDelete(string tableName, Dictionary fieldValues)
+ {
+ if (OnDelete != null)
+ OnDelete(tableName, fieldValues);
+ if (OnWrite != null)
+ OnWrite(tableName, fieldValues);
+ }
+
+ public void FireOnUpdate(string tableName, Dictionary dataFields, Dictionary keyFields)
+ {
+ if (OnUpdate != null)
+ OnUpdate(tableName, dataFields, keyFields);
+ if (OnWrite != null)
+ OnWrite(tableName, dataFields.Union(keyFields));
+ }
+
+ public void FireOnRead(string tableName, Dictionary keyFields)
+ {
+ if (OnRead != null)
+ OnRead(tableName, keyFields);
+ }
+
+ public event Action> OnInsert;
+
+ public event Action> OnDelete;
+
+ public event Action, Dictionary> OnUpdate;
+
+ public event Action> OnWrite;
+
+ public event Action> OnRead;
+ }
+}
diff --git a/src/FakeDBAnalyzer.cs b/src/Lasy/FakeDBMeta.cs
similarity index 80%
rename from src/FakeDBAnalyzer.cs
rename to src/Lasy/FakeDBMeta.cs
index cd27d98..34e9d4a 100644
--- a/src/FakeDBAnalyzer.cs
+++ b/src/Lasy/FakeDBMeta.cs
@@ -6,20 +6,20 @@
namespace Lasy
{
- public class FakeDBAnalyzer : IDBAnalyzer
+ public class FakeDBMeta : IDBAnalyzer
{
public Dictionary> PrimaryKeys = new Dictionary>();
public Dictionary AutoNumberKeys = new Dictionary();
+ public Dictionary> Fields = new Dictionary>();
+
///
/// If true, the analyzer will assume that there's a single autonumber PK for every table,
/// that has the name [tableName]Id. Any additions to PrimaryKeys or AutoNumberKeys will override this
///
public bool AssumeStandardKeys = true;
- #region IDBAnalyzer Members
-
public ICollection GetPrimaryKeys(string tableName)
{
if (PrimaryKeys.ContainsKey(tableName))
@@ -38,7 +38,7 @@ public string GetAutoNumberKey(string tableName)
else if (AssumeStandardKeys)
return _unschemadTablename(tableName) + "Id";
else
- throw new NotImplementedException("Dont know what the autonumbers for " + tableName + " would be. Either add that table's autonumbers to the AutoNumberKeys collection, or set AssumeStandardKeys to true for the default autonumber behavior");
+ return null;
}
private string _unschemadTablename(string tablename)
@@ -49,11 +49,18 @@ private string _unschemadTablename(string tablename)
public ICollection GetFields(string tableName)
{
+ // If we've been explicitly told the structure, use that
+ if (Fields.ContainsKey(tableName))
+ return Fields[tableName];
+
// We don't know what the actual structure is, so send back an empty
// list to indicate that we don't actually know
return new ReadOnlyCollection(new List { });
}
- #endregion
+ public bool TableExists(string tableName)
+ {
+ return true;
+ }
}
}
diff --git a/src/Lasy/FakeDBTable.cs b/src/Lasy/FakeDBTable.cs
new file mode 100644
index 0000000..85038d0
--- /dev/null
+++ b/src/Lasy/FakeDBTable.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Nvelope;
+
+namespace Lasy
+{
+ public class FakeDBTable : List>
+ {
+ public FakeDBTable()
+ : base()
+ { }
+
+ public FakeDBTable(FakeDBTable coll)
+ : base(coll)
+ {
+ NextAutoKey = coll.NextAutoKey;
+ }
+
+ public FakeDBTable(IEnumerable> rows, int nextAutokey)
+ : base(rows.ToList()) // Break any lazy evaluation so we don't end up re-evaluating the sequence
+ {
+ NextAutoKey = nextAutokey;
+ }
+
+ public IEnumerable> FindByFieldValues(Dictionary values)
+ {
+ return this.Where(x => filter(x, values));
+ }
+
+ private bool filter(Dictionary row, Dictionary values)
+ {
+ return values.Keys.All(field => row.ContainsKey(field) && row[field].Eq(values[field]));
+ }
+
+ public IEnumerable> Read(Dictionary values = null, IEnumerable fields = null)
+ {
+ fields = (fields ?? new List()).ToList();
+ values = values ?? new Dictionary();
+
+ var rows = FindByFieldValues(values.ScrubNulls());
+ if (!fields.Any())
+ return rows.Select(r => r.Copy()).ToList();
+ else
+ return rows.Select(row => row.WhereKeys(col => fields.Contains(col))).ToList();
+ }
+
+ public int NextAutoKey = 1;
+ }
+}
diff --git a/src/Lasy/FakeDBTransaction.cs b/src/Lasy/FakeDBTransaction.cs
new file mode 100644
index 0000000..bb51b42
--- /dev/null
+++ b/src/Lasy/FakeDBTransaction.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Nvelope;
+
+namespace Lasy
+{
+ ///
+ /// Fakes a transaction on a FakeDB
+ ///
+ /// Oh, the lies upon lies....
+ public class FakeDBTransaction : ITransaction
+ {
+ public FakeDBTransaction(FakeDB db)
+ {
+ _db = db;
+ _operations = new List();
+ }
+
+ protected abstract class Op
+ {
+ public Op(string table) { Table = table; }
+ public string Table { get; set; }
+ public abstract FakeDBTable Apply(FakeDBTable table);
+ }
+
+ protected class InsertOp : Op
+ {
+ ///
+ /// Note: row should include any autokeys that the table defines as well,
+ /// since InsertOp cannot determine them internally
+ ///
+ ///
+ ///
+ public InsertOp(string table, Dictionary row)
+ : base(table)
+ {
+ Row = row.Copy();
+ }
+
+ public Dictionary Row;
+
+ public override FakeDBTable Apply(FakeDBTable table)
+ {
+ // We don't increment the NextAutoKey because that was assumed to have been done
+ // already when we created the InsertOp in the first place
+ return new FakeDBTable(table.And(Row), table.NextAutoKey);
+ }
+ }
+
+ protected class UpdateOp : Op
+ {
+ public UpdateOp(string table, Dictionary data, Dictionary keys)
+ : base(table)
+ {
+ Keys = keys.Copy();
+ NewValues = data.Copy();
+ }
+
+ public Dictionary Keys;
+ public Dictionary NewValues;
+
+ public override FakeDBTable Apply(FakeDBTable table)
+ {
+ var victims = table.Where(r => Keys.IsSameAs(r)).ToList();
+ var newVersions = victims.Select(r => r.Union(NewValues));
+ return new FakeDBTable(table.Except(victims).And(newVersions), table.NextAutoKey);
+ }
+ }
+
+ protected class DeleteOp : Op
+ {
+ public DeleteOp(string table, Dictionary keys)
+ : base(table)
+ {
+ Keys = keys.Copy();
+ }
+
+ public Dictionary Keys;
+
+ public override FakeDBTable Apply(FakeDBTable table)
+ {
+ var victims = table.Where(r => Keys.IsSameAs(r));
+ return new FakeDBTable(table.Except(victims), table.NextAutoKey);
+ }
+ }
+
+ protected List _operations;
+
+ protected FakeDB _db;
+
+ public void Commit()
+ {
+ // Apply every operation in the transaction against the base database
+ foreach (var op in _operations)
+ _db.DataStore.Ensure(op.Table, op.Apply(_db.DataStore[op.Table]));
+ }
+
+ public void Rollback()
+ {
+ // Don't need to do anything
+ }
+
+ ///
+ /// Gets a filtered version of the table, having applied all of the operations of the transaction to it
+ ///
+ ///
+ ///
+ protected FakeDBTable _getTable(string table)
+ {
+ var underlying = _db.Table(table);
+ var opsForTable = _operations.Where(o => o.Table == table);
+ // Apply each of the operations to the table in sequence to get the output
+ var res = opsForTable.Aggregate(underlying, (source, op) => op.Apply(source));
+
+ return res;
+ }
+
+ public IEnumerable> RawRead(string tableName, Dictionary keyFields, IEnumerable fields)
+ {
+ _db.FireOnRead(tableName, keyFields);
+ return _getTable(tableName).Read(keyFields, fields);
+ }
+
+ public IDBAnalyzer Analyzer
+ {
+ get { return _db.Analyzer; }
+ }
+
+ public Dictionary Insert(string tableName, Dictionary row)
+ {
+ _db.FireOnInsert(tableName, row);
+
+ var autoKeys = _db.NewAutokey(tableName);
+ var inserted = row.ScrubNulls().Union(autoKeys);
+
+ var pks = _db.ExtractKeys(tableName, inserted);
+ _operations.Add(new InsertOp(tableName, inserted));
+ return pks;
+ }
+
+ public void Delete(string tableName, Dictionary row)
+ {
+ _db.FireOnDelete(tableName, row);
+ _operations.Add(new DeleteOp(tableName, row.ScrubNulls()));
+ }
+
+ public void Update(string tableName, Dictionary dataFields, Dictionary keyFields)
+ {
+ _db.FireOnUpdate(tableName, dataFields, keyFields);
+ _operations.Add(new UpdateOp(tableName, dataFields.ScrubNulls(), keyFields.ScrubNulls()));
+ }
+
+ public void Dispose()
+ {
+ // Don't need to do anything
+ }
+ }
+}
diff --git a/src/FileDB.cs b/src/Lasy/FileDB.cs
similarity index 60%
rename from src/FileDB.cs
rename to src/Lasy/FileDB.cs
index 98ab304..ff732cb 100644
--- a/src/FileDB.cs
+++ b/src/Lasy/FileDB.cs
@@ -92,70 +92,28 @@ private IEnumerable> getTable(string tableName)
private Dictionary convertRow(Dictionary row)
{
- return row.SelectVals(val => Infervert(val));
+ return row.SelectVals(val => Nvelope.Reading.TypeConversion.Infervert(val));
}
- ///
- /// Used by Infervert, this is how we guess what type data is supposed to be
- ///
- private static Dictionary _infervertConversions = new Dictionary()
- {
- {new Regex("^[0-9]+$", RegexOptions.Compiled), typeof(int)},
- {new Regex("^[0-9]+\\.[0-9]+$", RegexOptions.Compiled), typeof(decimal)},
- {new Regex("^[tT]rue|[Ff]alse$", RegexOptions.Compiled), typeof(bool)},
- {new Regex("^[0-9]{4}\\-[0-9]{2}\\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}$",
- RegexOptions.Compiled), typeof(DateTime)},
- {new Regex(".*", RegexOptions.Compiled), typeof(string)}
- };
-
- ///
- /// Based on a string representation, try to convert the value to the "appropriate" type
- /// Largely guesswork
- ///
- ///
- ///
- public object Infervert(string value)
- {
- if (value == "NULL")
- return null;
-
- var outputType = _infervertConversions.First(kv => kv.Key.IsMatch(value)).Value;
- return value.ConvertTo(outputType);
- }
-
- #region IReadable Members
-
- public IEnumerable> RawRead(string tableName, Dictionary id, ITransaction transaction = null)
- {
- var table = RawReadAll(tableName, transaction);
- var results = table.Where(row => row.IsSameAs(id, id.Keys));
- return results;
- }
-
- public IEnumerable> RawReadCustomFields(string tableName, IEnumerable fields, Dictionary id, ITransaction transaction = null)
+ public IEnumerable> RawRead(string tableName, Dictionary keyFields, IEnumerable fields)
{
- var res = RawRead(tableName, id, transaction);
- return res.Select(r => r.WhereKeys(f => fields.Contains(f)));
+ fields = fields ?? new string[] { };
+ keyFields = keyFields ?? new Dictionary();
+
+ var table = getTable(tableName);
+ var rows = table.Select(d => convertRow(d));
+ if(keyFields.Any())
+ rows = rows.Where(r => keyFields.IsSameAs(r));
+
+ if (fields.Any())
+ return rows.Select(r => r.Only(fields)).ToList();
+ else
+ return rows.ToList(); ;
}
public IDBAnalyzer Analyzer
{
get { throw new NotImplementedException(); }
}
-
- public IEnumerable> RawReadAll(string tableName, ITransaction transaction = null)
- {
- var raw = getTable(tableName);
- var table = raw.Select(d => convertRow(d));
- return table;
- }
-
- public IEnumerable> RawReadAllCustomFields(string tableName, IEnumerable fields, ITransaction transaction = null)
- {
- var res = RawReadAll(tableName, transaction);
- return res.Select(r => r.WhereKeys(f => fields.Contains(f)));
- }
-
- #endregion
}
}
diff --git a/src/Lasy/FunctionExtensions.cs b/src/Lasy/FunctionExtensions.cs
new file mode 100644
index 0000000..d06f37b
--- /dev/null
+++ b/src/Lasy/FunctionExtensions.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Reactive;
+using Nvelope;
+
+namespace Lasy
+{
+ ///
+ /// Encapsulates extension methods for Reactive-programming function extensions
+ ///
+ public static class FunctionExtensions
+ {
+ ///
+ ///
+ ///
+ /// We may be stretching the tradtional definition of Memoize with this one, but
+ /// the basic functionality is similar, so I haven't tried to come up with a better name
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Func Memoize(this Func fn, TimeSpan cacheTimeout, IObservable cacheInvalidator)
+ {
+ // We'll stored things in a local cache so we don't hit the underlying fn every time
+ var cache = new Dictionary();
+ // We want to watch to see if any cache invalidation events happen - if they do, we want to remove
+ // that value from the cache.
+ var cacheWatcher = cacheInvalidator.Subscribe(t => cache.Remove(t));
+ // We also want to check to see if the cache has expired
+ // TODO: We can probably use the System.Reactive lib to wrap this up in a nicer
+ // way and combine it with the cachewatcher - either one should generate an event
+ // that clears the cache.
+ var inTime = Nvelope.FunctionExtensions.HasBeenCalledIn(cacheTimeout);
+ // Finally, return our function that has a built-in cache
+ return t =>
+ {
+ // We need to call inTime every time to reset its timer,
+ // otherwise we'd just include it in the following if statement
+ var expired = !inTime(t);
+ if (!cache.ContainsKey(t) || expired)
+ cache.Ensure(t, fn(t));
+ return cache[t];
+ };
+ }
+ }
+}
diff --git a/src/Lasy/IAnalyzable.cs b/src/Lasy/IAnalyzable.cs
new file mode 100644
index 0000000..90ec071
--- /dev/null
+++ b/src/Lasy/IAnalyzable.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Nvelope;
+using Nvelope.Reflection;
+
+namespace Lasy
+{
+ public interface IAnalyzable
+ {
+ ///
+ /// Get the analyzer for the DB
+ ///
+ IDBAnalyzer Analyzer { get; }
+ }
+
+ public static class IAnalyzableExtensions
+ {
+ ///
+ /// Gets all the primary keys for tablename from values. Throws an exception if any of the keys
+ /// are not supplied
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Dictionary ExtractKeys(this IAnalyzable writer, string tablename,
+ Dictionary values)
+ {
+ var keynames = writer.Analyzer.GetPrimaryKeys(tablename);
+ // Make sure they supplied all the keys
+ if (!values.Keys.ToSet().IsSupersetOf(keynames))
+ throw new KeyNotSetException(tablename, keynames.Except(values.Keys));
+
+ var keys = values.Only(keynames);
+ return keys;
+ }
+
+ ///
+ /// Make sure that values contains all the keys needed to insert into tablename. If not,
+ /// a KeyNotSetException will be thrown
+ ///
+ ///
+ ///
+ ///
+ public static void AssertInsertKeys(this IAnalyzable writer, string tablename, Dictionary values)
+ {
+ var keys = writer.Analyzer.GetPrimaryKeys(tablename).Except(writer.Analyzer.GetAutoNumberKey(tablename));
+ if (!values.Keys.ToSet().IsSupersetOf(keys))
+ throw new KeyNotSetException(tablename, keys.Except(values.Keys));
+ }
+ }
+}
diff --git a/src/IDBAnalyzer.cs b/src/Lasy/IDBAnalyzer.cs
similarity index 54%
rename from src/IDBAnalyzer.cs
rename to src/Lasy/IDBAnalyzer.cs
index 8a40409..797a2d4 100644
--- a/src/IDBAnalyzer.cs
+++ b/src/Lasy/IDBAnalyzer.cs
@@ -15,6 +15,16 @@ public interface IDBAnalyzer
///
///
ICollection GetFields(string tableName);
+ bool TableExists(string tableName);
+ }
+ public interface ITypedDBAnalyzer : IDBAnalyzer
+ {
+ ///
+ /// Gets the types of the fields for the given table
+ ///
+ ///
+ ///
+ Dictionary GetFieldTypes(string tablename, Dictionary example);
}
}
diff --git a/src/Lasy/IDBModifier.cs b/src/Lasy/IDBModifier.cs
new file mode 100644
index 0000000..2aebdba
--- /dev/null
+++ b/src/Lasy/IDBModifier.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Nvelope.Reflection;
+using Nvelope;
+
+namespace Lasy
+{
+ public interface IDBModifier : IAnalyzable
+ {
+ ///
+ /// This analyzer should be able to tell the DBModifier about the structure
+ /// of any table that needs to be created
+ ///
+ ITypedDBAnalyzer Taxonomy { get; set; }
+ void CreateTable(string tablename, Dictionary fields);
+ void DropTable(string tablename);
+ }
+
+ public static class IDBModifierExtensions
+ {
+ public static void CreateTable(this IDBModifier meta, string tablename, Dictionary instance)
+ {
+ var taxonomyTypes = meta.Taxonomy == null ?
+ new Dictionary() :
+ meta.Taxonomy.GetFieldTypes(tablename, instance);
+
+ taxonomyTypes = taxonomyTypes ?? new Dictionary();
+
+ var missingTypes = instance.Except(taxonomyTypes.Keys)
+ .SelectVals(v => SqlTypeConversion.GetSqlType(v));
+
+ var fieldTypes = missingTypes.Union(taxonomyTypes);
+
+ meta.CreateTable(tablename, fieldTypes);
+ }
+
+ public static void CreateTable(this IDBModifier meta, string tablename, object instance)
+ {
+ CreateTable(meta, tablename, instance._AsDictionary());
+ }
+
+ public static void EnsureTable(this IDBModifier meta, string tablename, Dictionary instance)
+ {
+ if (!meta.Analyzer.TableExists(tablename))
+ CreateTable(meta, tablename, instance);
+ }
+
+ public static void EnsureTable(this IDBModifier meta, string tablename, object instance)
+ {
+ EnsureTable(meta, tablename, instance._AsDictionary());
+ }
+
+ public static void EnsureTable(this IDBModifier meta, string tablename, Dictionary fields)
+ {
+ if (!meta.Analyzer.TableExists(tablename))
+ meta.CreateTable(tablename, fields);
+ }
+
+ ///
+ /// Drops the table if it exists, else does nothing
+ ///
+ ///
+ ///
+ public static void KillTable(this IDBModifier meta, string tablename)
+ {
+ if (meta.Analyzer.TableExists(tablename))
+ meta.DropTable(tablename);
+ }
+ }
+}
diff --git a/src/Lasy/IModifiable.cs b/src/Lasy/IModifiable.cs
new file mode 100644
index 0000000..48b1fbc
--- /dev/null
+++ b/src/Lasy/IModifiable.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Nvelope;
+using Nvelope.Reflection;
+
+namespace Lasy
+{
+ ///
+ /// Indicates that a database supports being modfiable - ie tables can be added at runtime
+ ///
+ public interface IModifiable : IAnalyzable
+ {
+ IDBModifier Modifier { get; }
+ }
+}
diff --git a/src/Lasy/INameQualifier.cs b/src/Lasy/INameQualifier.cs
new file mode 100644
index 0000000..36271c1
--- /dev/null
+++ b/src/Lasy/INameQualifier.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Lasy
+{
+ public interface INameQualifier
+ {
+ string TableName(string rawTablename);
+ string SchemaName(string rawTablename);
+ bool SupportsSchemas { get; }
+ }
+}
diff --git a/src/Lasy/IRWEvented.cs b/src/Lasy/IRWEvented.cs
new file mode 100644
index 0000000..b881a1a
--- /dev/null
+++ b/src/Lasy/IRWEvented.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Lasy
+{
+ public interface IRWEvented : IReadWrite, IWriteEvented, IReadEvented
+ { }
+
+ public interface IWriteEvented : IWriteable
+ {
+ ///
+ /// Fires before an insert
+ ///
+ event Action> OnInsert;
+ ///
+ /// Fires before a delete
+ ///
+ event Action> OnDelete;
+ ///
+ /// Fires before an update
+ ///
+ event Action, Dictionary> OnUpdate;
+ ///
+ /// Fires before every insert, update, or delete
+ ///
+ event Action> OnWrite;
+ }
+
+ public interface IReadEvented : IReadable
+ {
+ ///
+ /// Fires before a Read operation
+ ///
+ event Action> OnRead;
+ }
+}
diff --git a/src/IReadWriteExtensions.cs b/src/Lasy/IReadWrite.cs
similarity index 80%
rename from src/IReadWriteExtensions.cs
rename to src/Lasy/IReadWrite.cs
index 9af1e28..f67f951 100644
--- a/src/IReadWriteExtensions.cs
+++ b/src/Lasy/IReadWrite.cs
@@ -2,11 +2,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
-using Nvelope.Reflection;
using Nvelope;
+using Nvelope.Reflection;
namespace Lasy
{
+ public interface IReadWrite : IReadable, IWriteable
+ {
+ }
+
public static class IReadWriteExtensions
{
///
@@ -20,18 +24,17 @@ public static class IReadWriteExtensions
public static void Ensure(this IReadWrite readWrite,
string tablename,
Dictionary dataFields,
- Dictionary keyFields,
- ITransaction trans = null)
+ Dictionary keyFields)
{
// See if the keyFields exist
// If so, update them, otherwise insert them
- var existing = readWrite.Read(tablename, keyFields, trans);
+ var existing = readWrite.Read(tablename, keyFields);
if (existing.Any())
- readWrite.Update(tablename, dataFields, keyFields, trans);
+ readWrite.Update(tablename, dataFields, keyFields);
else
{
var newRow = dataFields.Union(keyFields);
- var newKeys = readWrite.Insert(tablename, newRow, trans);
+ var newKeys = readWrite.Insert(tablename, newRow);
}
}
@@ -46,14 +49,12 @@ public static void Ensure(this IReadWrite readWrite,
public static void Ensure(this IReadWrite readWrite,
string tablename,
object dataObj,
- object keyObj,
- ITransaction trans = null)
+ object keyObj)
{
Ensure(readWrite,
tablename,
dataObj._AsDictionary(),
- keyObj._AsDictionary(),
- trans);
+ keyObj._AsDictionary());
}
///
@@ -65,11 +66,10 @@ public static void Ensure(this IReadWrite readWrite,
///
public static void Ensure(this IReadWrite readWrite,
string tablename,
- Dictionary values,
- ITransaction trans = null)
+ Dictionary values)
{
var keyFields = readWrite.ExtractKeys(tablename, values);
- Ensure(readWrite, tablename, values, keyFields, trans);
+ Ensure(readWrite, tablename, values, keyFields);
}
///
@@ -81,10 +81,9 @@ public static void Ensure(this IReadWrite readWrite,
///
public static void Ensure(this IReadWrite readWrite,
string tablename,
- object valueObj,
- ITransaction trans = null)
+ object valueObj)
{
- Ensure(readWrite, tablename, valueObj._AsDictionary(), trans);
+ Ensure(readWrite, tablename, valueObj._AsDictionary());
}
}
}
diff --git a/src/IReadableExtensions.cs b/src/Lasy/IReadable.cs
similarity index 51%
rename from src/IReadableExtensions.cs
rename to src/Lasy/IReadable.cs
index a614304..86eb570 100644
--- a/src/IReadableExtensions.cs
+++ b/src/Lasy/IReadable.cs
@@ -2,24 +2,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
-using Nvelope;
using Nvelope.Reflection;
+using Nvelope;
namespace Lasy
{
- public static class IReadableExtensions
+ public interface IReadable : IAnalyzable
{
- public static IEnumerable> RawReadCustomFields(this IReadable reader, string tableName, IEnumerable fields, object id, ITransaction transaction = null)
- {
- return reader.RawReadCustomFields(tableName, fields, id as Dictionary ?? id._AsDictionary(), transaction);
- }
-
- public static IEnumerable> RawRead(this IReadable reader, string tableName, Dictionary id, ITransaction transaction = null)
- {
- return reader.RawRead(tableName, id as Dictionary ?? id._AsDictionary(), transaction);
- }
+ ///
+ /// Read all that match on id from the table
+ ///
+ ///
+ ///
+ /// If supplied, read just these fields
+ ///
+ IEnumerable> RawRead(string tableName, Dictionary keyFields, IEnumerable fields = null);
+ }
- public static Dictionary ReadPK(this IReadable reader, string tablename, int primaryKey, ITransaction transaction = null)
+ public static class IReadableExtensions
+ {
+ public static Dictionary ReadPK(this IReadable reader, string tablename, int primaryKey)
{
var keys = new Dictionary();
var keynames = reader.Analyzer.GetPrimaryKeys(tablename);
@@ -27,38 +29,43 @@ public static Dictionary ReadPK(this IReadable reader, string ta
if (keynames.Count > 1)
throw new Exception("This table " + tablename + " has too many Primary Keys");
- var paras = new Dictionary(){{ keynames.First(), primaryKey}};
+ var paras = new Dictionary() { { keynames.First(), primaryKey } };
- return ReadPK(reader, tablename, paras, transaction);
+ return ReadPK(reader, tablename, paras);
}
- public static Dictionary ReadPK(this IReadable reader, string tablename, Dictionary primaryKeys, ITransaction transaction = null)
+ public static Dictionary ReadPK(this IReadable reader, string tablename, Dictionary primaryKeys)
{
- var results = reader.RawRead(tablename, primaryKeys, transaction);
+ var results = reader.RawRead(tablename, primaryKeys);
if (results.Count() > 1)
throw new Exception("This table " + tablename + " has more than one row with the primary key " + primaryKeys.Print());
else if (results.Count() == 0)
return null;
else
- return results.First();
+ return results.First();
}
- public static T ReadPK(this IReadable reader, string tablename, int primaryKey, ITransaction transaction = null) where T:class, new()
+ public static T ReadPK(this IReadable reader, string tablename, int primaryKey) where T : class, new()
{
- var results = reader.ReadPK(tablename, primaryKey, transaction);
+ var results = reader.ReadPK(tablename, primaryKey);
var output = new T();
return output._SetFrom(results);
}
//Whatever we use for T needs to have a zero-parameter constructor
- public static IEnumerable ReadAll(this IReadable reader, string tableName = null, ITransaction transaction = null) where T:class, new()
+ public static IEnumerable ReadAll(this IReadable reader, string tableName = null) where T : class, new()
{
- if(tableName.IsNullOrEmpty())
+ if (tableName.IsNullOrEmpty())
tableName = typeof(T).Name;
- var results = reader.RawReadAll(tableName, transaction);
+ var results = reader.RawRead(tableName, new Dictionary());
+
+ return results.Select(x => new T()._SetFrom(x));
+ }
- return results.Select(x => new T()._SetFrom(x) );
+ public static IEnumerable> ReadAll(this IReadable reader, string tableName, IEnumerable fields = null)
+ {
+ return reader.RawRead(tableName, new Dictionary(), fields);
}
///
@@ -66,24 +73,23 @@ public static Dictionary ReadPK(this IReadable reader, string ta
///
///
/// The name of the table
- /// An object with values that are the "where" clause in sql. Example: new {col1 = 6, col2 = 'myString'}
+ /// An object with values that are the "where" clause in sql. Example: new {col1 = 6, col2 = 'myString'}. You can also pass a dictionarys
/// A SQL transaction that can be passed in if this Read is to be part of a greater transaction
///
- public static IEnumerable> Read(this IReadable reader, string tableName, object values, ITransaction trans = null)
+ public static IEnumerable> Read(this IReadable reader, string tableName, object values, IEnumerable fields = null)
{
- return reader.RawRead(tableName, values as Dictionary ?? values._AsDictionary(), trans);
+ return reader.RawRead(tableName, values as Dictionary ?? values._AsDictionary(), fields);
}
- public static IEnumerable Read(this IReadable reader, string tableName, object values, ITransaction trans = null) where T: class, new()
+ public static IEnumerable Read(this IReadable reader, string tableName, object values) where T : class, new()
{
- return Read(reader, tableName, values as Dictionary ?? values._AsDictionary(), trans);
+ return Read(reader, tableName, values as Dictionary ?? values._AsDictionary());
}
- public static IEnumerable Read(this IReadable reader, string tableName, Dictionary values, ITransaction trans = null) where T:class, new()
+ public static IEnumerable Read(this IReadable reader, string tableName, Dictionary values) where T : class, new()
{
- var res = Read(reader, tableName, values, trans);
- ObjectReader converter = new ObjectReader();
- return converter.ReadAll(res);
+ var results = Read(reader, tableName, values);
+ return results.Select(x => new T()._SetFrom(x));
}
}
}
diff --git a/src/ITransaction.cs b/src/Lasy/ITransactable.cs
similarity index 54%
rename from src/ITransaction.cs
rename to src/Lasy/ITransactable.cs
index b11b1dd..152c342 100644
--- a/src/ITransaction.cs
+++ b/src/Lasy/ITransactable.cs
@@ -5,10 +5,8 @@
namespace Lasy
{
- public interface ITransaction
+ public interface ITransactable : IReadWrite
{
- void Commit();
-
- void Rollback();
+ ITransaction BeginTransaction();
}
}
diff --git a/src/Lasy/ITransaction.cs b/src/Lasy/ITransaction.cs
new file mode 100644
index 0000000..fa79603
--- /dev/null
+++ b/src/Lasy/ITransaction.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Lasy
+{
+ ///
+ /// Indicates that something is a transaction.
+ ///
+ public interface ITransaction : IReadWrite, IDisposable
+ {
+ void Commit();
+
+ void Rollback();
+ }
+}
diff --git a/src/IWritableExtensions.cs b/src/Lasy/IWriteable.cs
similarity index 54%
rename from src/IWritableExtensions.cs
rename to src/Lasy/IWriteable.cs
index d381659..cf66e7a 100644
--- a/src/IWritableExtensions.cs
+++ b/src/Lasy/IWriteable.cs
@@ -1,40 +1,31 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
-using Nvelope;
+using System.Text;
using Nvelope.Reflection;
+using Nvelope;
namespace Lasy
{
- public static class IWritableExtensions
+ public interface IWriteable : IAnalyzable
{
- public static Dictionary Insert(this IWriteable writer, string tablename, object obj, ITransaction trans = null)
- {
- return writer.Insert(tablename, obj as Dictionary ?? obj._AsDictionary(), trans);
- }
+ Dictionary Insert(string tableName, Dictionary row);
+
+ void Delete(string tableName, Dictionary keyFields);
+
+ void Update(string tableName, Dictionary dataFields, Dictionary keyFields);
+ }
- public static int InsertAutoKey(this IWriteable writer, string tablename, object obj, ITransaction trans = null)
+ public static class IWriteableExtensions
+ {
+ public static Dictionary Insert(this IWriteable writer, string tablename, object obj)
{
- return writer.Insert(tablename, obj as Dictionary ?? obj._AsDictionary(), trans).Single().Value.ConvertTo();
+ return writer.Insert(tablename, obj as Dictionary ?? obj._AsDictionary());
}
- ///
- /// Gets all the primary keys for tablename from values. Throws an exception if any of the keys
- /// are not supplied
- ///
- ///
- ///
- ///
- ///
- public static Dictionary ExtractKeys(this IWriteable writer, string tablename,
- Dictionary values)
+ public static int InsertAutoKey(this IWriteable writer, string tablename, object obj)
{
- var keynames = writer.Analyzer.GetPrimaryKeys(tablename);
- // Make sure they supplied all the keys
- if (!values.Keys.ToSet().IsSupersetOf(keynames))
- throw new KeyNotSetException(tablename, keynames.Except(values.Keys));
-
- var keys = values.Only(keynames);
- return keys;
+ return writer.Insert(tablename, obj as Dictionary ?? obj._AsDictionary()).Single().Value.ConvertTo();
}
///
@@ -45,10 +36,10 @@ public static Dictionary ExtractKeys(this IWriteable writer, str
///
///
///
- public static void Update(this IWriteable writer, string tablename, Dictionary values, ITransaction trans = null)
+ public static void Update(this IWriteable writer, string tablename, Dictionary values)
{
var keys = writer.ExtractKeys(tablename, values);
- writer.Update(tablename, values, keys, trans);
+ writer.Update(tablename, values, keys);
}
///
@@ -59,11 +50,11 @@ public static void Update(this IWriteable writer, string tablename, Dictionary
///
///
- public static void Update(this IWriteable writer, string tablename, object obj, ITransaction trans = null)
+ public static void Update(this IWriteable writer, string tablename, object obj)
{
var values = obj as Dictionary ?? obj._AsDictionary();
- Update(writer, tablename, values, trans);
+ Update(writer, tablename, values);
}
///
@@ -74,17 +65,17 @@ public static void Update(this IWriteable writer, string tablename, object obj,
/// If Dict[string,object] it will be passed through, otherwise converted
/// If Dict[string,object] it will be passed through, otherwise converted
///
- public static void Update(this IWriteable writer, string tablename, object dataObj, object keysObj, ITransaction trans = null)
+ public static void Update(this IWriteable writer, string tablename, object dataObj, object keysObj)
{
- Dictionary data = dataObj as Dictionary ?? dataObj._AsDictionary();
- Dictionary keys = keysObj as Dictionary ?? keysObj._AsDictionary();
- writer.Update(tablename, data, keys, trans);
+ Dictionary data = dataObj as Dictionary