diff --git a/README.md b/README.md index a310bb9..a194528 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,18 @@ In Xcode, add `libsqlite3.tbd` to your project's `Build Phases` ➜ `Link Binary #### Windows 1. Open the solution in `Visual Studio` for your Windows apps. - Right click your in the Explorer and click `Add` > `Existing Project...`. - - Navigate to `.//windows/RNSqlite2/` and add `RNSqlite2.csproj`. + - [UWP] Navigate to `.//windows/RNSqlite2/` and add `RNSqlite2.csproj`. + + [WPF] Navigate to `.//windows/RNSqlite2.Net46/` and add `RNSqlite2.Net46.csproj`. - Right click on your React Native Windows app under your solutions directory and click `Add` > `Reference...`. - - Check the `RNSqlite2` you just added and press `Ok`. + - [UWP] Check the `RNSqlite2` you just added and press `Ok`. + + [WPF] Check the `RNSqlite2.Net46` you just added and press `Ok`. 2. Open `MainPage.cs` in your app - Edit it like below: ``` -using RNFileSystem; +using RNSqlite2; get { diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..33d3fde --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,89 @@ +*AppPackages* +*BundleArtifacts* +*ReactAssets* + +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.opensdf +*.opendb +*.unsuccessfulbuild +ipch/ +[Oo]bj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +#MonoDevelop +*.pidb +*.userprefs + +#Tooling +_ReSharper*/ +*.resharper +[Tt]est[Rr]esult* +*.sass-cache + +#Project files +[Bb]uild/ + +#Subversion files +.svn + +# Office Temp Files +~$* + +# vim Temp Files +*~ + +#NuGet +packages/ +*.nupkg + +#ncrunch +*ncrunch* +*crunch*.local.xml + +# visual studio database projects +*.dbmdl + +#Test files +*.testsettings + +#Other files +*.DotSettings +.vs/ +*project.lock.json diff --git a/windows/RNSqlite2.Net46/Properties/AssemblyInfo.cs b/windows/RNSqlite2.Net46/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..11d9425 --- /dev/null +++ b/windows/RNSqlite2.Net46/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RNSqlite2.Net46")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RNSqlite2.Net46")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b2dced9a-bde1-4eae-8c69-c562448aca2c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/windows/RNSqlite2.Net46/RNSqlite2.Net46.csproj b/windows/RNSqlite2.Net46/RNSqlite2.Net46.csproj new file mode 100644 index 0000000..51fa892 --- /dev/null +++ b/windows/RNSqlite2.Net46/RNSqlite2.Net46.csproj @@ -0,0 +1,160 @@ + + + + + + Debug + AnyCPU + {B2DCED9A-BDE1-4EAE-8C69-C562448ACA2C} + Library + Properties + RNSqlite2.Net46 + RNSqlite2.Net46 + v4.6 + 512 + + + + + true + full + false + bin\Debug\ + TRACE;DEBUG + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)\packages\Microsoft.Data.Sqlite.1.1.1\lib\net451\Microsoft.Data.Sqlite.dll + True + + + $(SolutionDir)\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + True + + + $(SolutionDir)\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + $(SolutionDir)\packages\PCLStorage.1.0.2\lib\net45\PCLStorage.dll + True + + + $(SolutionDir)\packages\PCLStorage.1.0.2\lib\net45\PCLStorage.Abstractions.dll + True + + + + $(SolutionDir)\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + True + + + + $(SolutionDir)\packages\System.Console.4.3.0\lib\net46\System.Console.dll + True + + + + $(SolutionDir)\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + True + + + $(SolutionDir)\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll + True + + + $(SolutionDir)\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + + + + $(SolutionDir)\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + True + + + $(SolutionDir)\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + True + + + $(SolutionDir)\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + + + $(SolutionDir)\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll + True + + + $(SolutionDir)\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + True + + + + $(SolutionDir)\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + + + $(SolutionDir)\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll + True + + + $(SolutionDir)\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + $(SolutionDir)\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + $(SolutionDir)\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll + True + + + + + + + + $(SolutionDir)\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + True + + + + + + + + + + {22cbff9c-fe36-43e8-a246-266c7635e662} + ReactNative.Net46 + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/windows/RNSqlite2.Net46/app.config b/windows/RNSqlite2.Net46/app.config new file mode 100644 index 0000000..41eeea4 --- /dev/null +++ b/windows/RNSqlite2.Net46/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/RNSqlite2.Net46/packages.config b/windows/RNSqlite2.Net46/packages.config new file mode 100644 index 0000000..5a63eeb --- /dev/null +++ b/windows/RNSqlite2.Net46/packages.config @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/RNSqlite2/RNSqlite2.csproj b/windows/RNSqlite2/RNSqlite2.csproj index c7ccbdf..165f397 100644 --- a/windows/RNSqlite2/RNSqlite2.csproj +++ b/windows/RNSqlite2/RNSqlite2.csproj @@ -108,8 +108,8 @@ - - + + diff --git a/windows/RNSqlite2/RNSqlite2Module.cs b/windows/RNSqlite2Module.cs similarity index 97% rename from windows/RNSqlite2/RNSqlite2Module.cs rename to windows/RNSqlite2Module.cs index eccdac5..d49660d 100644 --- a/windows/RNSqlite2/RNSqlite2Module.cs +++ b/windows/RNSqlite2Module.cs @@ -1,442 +1,446 @@ -using ReactNative.Bridge; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Data.Sqlite; -using System.IO; -using Newtonsoft.Json.Linq; -using System.Data; -using System.Text.RegularExpressions; - -namespace RNSqlite2 -{ - class RNSqlite2Module : ReactContextNativeModuleBase - { - public RNSqlite2Module(ReactContext reactContext) - : base(reactContext) - { - this.reactContext = reactContext; - } - - public override string Name - { - get - { - return "RNSqlite2"; - } - } - private readonly ReactContext reactContext; - - private static readonly bool DEBUG_MODE = false; - - private static readonly string TAG = typeof(RNSqlite2Module).Name; - - private static readonly List EMPTY_ROWS = new List(); - private static readonly List EMPTY_COLUMNS = new List(); - - //protected Context context = null; - private static readonly SQLitePLuginResult EMPTY_RESULT = new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, null); - - private static readonly Dictionary DATABASES = new Dictionary(); - private static readonly Dictionary TRANSACTIONS = new Dictionary(); - - [ReactMethod] - public void test(string message, IPromise promise) - { - debug("example", "test called: " + message); - promise.Resolve(message + "\u0000" + "hoge"); - } - - private SqliteConnection getDatabase(string name) - { - if (!DATABASES.ContainsKey(name)) - { - string path = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, name); - SqliteConnection conn = new SqliteConnection("Filename=" + name); - DATABASES[name] = conn; - TRANSACTIONS[name] = null; - } - return DATABASES[name]; - } - - // TODO: Need improve . This function : command.Parameters.AddWithValue(item.Key, item.Value); don't support bind string by index or i missing something - private static void parseJsonArrayToParams(ref string sql, List jsonArray, ref SqliteCommand statement) - { - MatchCollection matches = Regex.Matches(sql, @"\?"); - Dictionary array = new Dictionary(); - int len = matches.Count; - if (len > 0) - { - if (matches.Count > jsonArray.Count) - { - throw new Exception("parameter not correct"); - } - - var lastLen = 0; - for (int i = 0; i < len; i++) - { - var stringBuilder = new StringBuilder(sql); - stringBuilder.Remove(lastLen + matches[i].Index, 1); - stringBuilder.Insert(lastLen + matches[i].Index, "@" + i); - statement.Parameters.AddWithValue("@" + i, jsonArray[i].ToString()); - lastLen += i.ToString().Length; - sql = stringBuilder.ToString(); - } - } - else - { - // This is old code handle txn.executeSql('INSERT INTO Users (name) VALUES (:name)', ['narumiya']) - matches = Regex.Matches(sql, @"\:([A-Za-z0-9]*)"); - len = matches.Count; - - if (matches.Count > jsonArray.Count) - { - throw new Exception("parameter not correct"); - } - - for (int i = 0; i < len; i++) - { - statement.Parameters.AddWithValue(matches[i].Value, jsonArray[i].ToString()); - } - } - } - - private static bool isSelect(String str) - { - return str.TrimStart().StartsWith("select", StringComparison.OrdinalIgnoreCase); - } - - private static bool isInsert(String str) - { - return str.TrimStart().StartsWith("insert", StringComparison.OrdinalIgnoreCase); - } - - private static bool isDelete(String str) - { - return str.TrimStart().StartsWith("delete", StringComparison.OrdinalIgnoreCase); - } - - private static bool isUpdate(String str) - { - return str.TrimStart().StartsWith("update", StringComparison.OrdinalIgnoreCase); - } - - private static bool isBegin(String str) - { - return str.TrimStart().StartsWith("begin", StringComparison.OrdinalIgnoreCase); - } - - private static bool isEnd(String str) - { - return str.TrimStart().StartsWith("end", StringComparison.OrdinalIgnoreCase); - } - - [ReactMethod] - public void exec(string dbName, JArray queries, bool readOnly, IPromise promise) - { - debug("test called: " + dbName); - SqliteConnection db = getDatabase(dbName); - try - { - int numQueries = queries.Count; - SQLitePLuginResult[] results = new SQLitePLuginResult[numQueries]; - if (TRANSACTIONS[dbName] == null) - { - db.Open(); - } - for (int i = 0; i < numQueries; i++) - { - var sqlQuery = queries[i]; - string sql = sqlQuery[0].ToString(); - try - { - if (isSelect(sql)) - { - results[i] = doSelectInBackgroundAndPossiblyThrow(sql, sqlQuery[1].ToList(), db, TRANSACTIONS[dbName]); - } - else if (isBegin(sql)) - { - //Handle begin without end - if (TRANSACTIONS[dbName] != null) - { - TRANSACTIONS[dbName].Rollback(); - TRANSACTIONS[dbName].Dispose(); - TRANSACTIONS[dbName] = null; - } - TRANSACTIONS[dbName] = db.BeginTransaction(); - results[i] = EMPTY_RESULT; - } - else if (isEnd(sql)) - { - if (TRANSACTIONS[dbName] != null) - { - TRANSACTIONS[dbName].Commit(); - TRANSACTIONS[dbName].Dispose(); - TRANSACTIONS[dbName] = null; - } - results[i] = EMPTY_RESULT; - } - else - { // update/insert/delete - if (readOnly) - { - results[i] = new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, new ReadOnlyException()); - } - else - { - results[i] = doUpdateInBackgroundAndPossiblyThrow(sql, sqlQuery[1].ToList(), db, TRANSACTIONS[dbName]); - } - } - } - catch (Exception e) - { - if (RNSqlite2Module.DEBUG_MODE) - { - Console.WriteLine(e.ToString()); - } - if (TRANSACTIONS[dbName] != null) - { - TRANSACTIONS[dbName].Rollback(); - TRANSACTIONS[dbName].Dispose(); - TRANSACTIONS[dbName] = null; - } - results[i] = new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, e); - } - } - - List data = pluginResultsToPrimitiveData(results); - promise.Resolve(data); - } - catch (Exception e) - { - promise.Reject("SQLiteError", e); - if (TRANSACTIONS[dbName] != null) - { - TRANSACTIONS[dbName].Rollback(); - TRANSACTIONS[dbName].Dispose(); - TRANSACTIONS[dbName] = null; - } - } - finally - { - if (TRANSACTIONS[dbName] == null) - { - db.Close(); - } - } - } - - private Object getValueFromCursor(SqliteDataReader reader, int index, Type dataType) - { - if (dataType == typeof(Int16)) - { - return reader.GetInt16(index); - } - else if (dataType == typeof(Int32)) - { - return reader.GetInt32(index); - } - else if (dataType == typeof(Int64)) - { - return reader.GetInt64(index); - } - else if (dataType == typeof(double)) - { - return reader.GetDouble(index); - } - else if (dataType == typeof(decimal)) - { - return reader.GetDecimal(index); - } - else if (dataType == typeof(string)) - { - return reader.GetString(index); - } - else - { - return ""; - } - } - - private static void debug(String line, object format = null) - { - if (DEBUG_MODE) - { - Console.WriteLine(TAG, string.Format(line, format)); - } - } - - // do a select operation - private SQLitePLuginResult doSelectInBackgroundAndPossiblyThrow(string sql, List queryParams, - SqliteConnection db, SqliteTransaction transaction = null) - { - debug("\"all\" query: %s", sql); - SqliteDataReader query = null; - try - { - SqliteCommand command = new SqliteCommand(); - command.Connection = db; - command.Transaction = transaction; - - if (queryParams != null && queryParams.Count > 0) - { - parseJsonArrayToParams(ref sql, queryParams, ref command); - } - command.CommandText = sql; - - query = command.ExecuteReader(); - if (!query.HasRows) - { - return EMPTY_RESULT; - } - List entries = new List(); - - while (query.Read()) - { - List row = new List(); - for (int i = 0; i < query.FieldCount; i++) - { - var type = query.GetValue(i).GetType(); - row.Add(getValueFromCursor(query, i, type)); - } - entries.Add(row); - } - List columns = Enumerable.Range(0, query.FieldCount).Select(query.GetName).ToList(); - debug("returning %d rows", entries.Count()); - return new SQLitePLuginResult(entries, columns, 0, 0, null); - } - catch (Exception e) - { - debug("Query error", e.Message); - return new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, null); - } - finally - { - if (query != null) - { - query.Dispose(); - } - } - } - - private static List pluginResultsToPrimitiveData(SQLitePLuginResult[] results) - { - List list = new List(); - for (int i = 0; i < results.Count(); i++) - { - SQLitePLuginResult result = results[i]; - List arr = convertPluginResultToArray(result); - list.Add(arr); - } - return list; - } - - private static List convertPluginResultToArray(SQLitePLuginResult result) - { - List data = new List(); - if (result.error != null) - { - data.Add(result.error.Message); - } - else - { - data.Add(null); - } - data.Add((long)result.insertId); - data.Add((int)result.rowsAffected); - - // column names - data.Add(result.columns); - data.Add(result.rows); - - return data; - } - - // do a update/delete/insert operation - private SQLitePLuginResult doUpdateInBackgroundAndPossiblyThrow(string sql, List queryParams, - SqliteConnection db, SqliteTransaction transaction = null) - { - debug("\"run\" query: %s", sql); - SqliteCommand statement = null; - try - { - statement = new SqliteCommand(); - statement.Connection = db; - statement.Transaction = transaction; - statement.CommandType = CommandType.Text; - debug("compiled statement"); - - if (queryParams != null && queryParams.Count > 0) - { - parseJsonArrayToParams(ref sql, queryParams, ref statement); - } - statement.CommandText = sql; - debug("bound args"); - if (isInsert(sql)) - { - debug("type: insert"); - int rowsAffected = statement.ExecuteNonQuery(); - long insertId = 0; - if (rowsAffected > 0) - { - statement.CommandText = "SELECT last_insert_rowid()"; - insertId = (long)statement.ExecuteScalar(); - } - return new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, rowsAffected, insertId, null); - } - else if (isDelete(sql) || isUpdate(sql)) - { - debug("type: update/delete"); - int rowsAffected = statement.ExecuteNonQuery(); - return new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, rowsAffected, 0, null); - } - else - { - // in this case, we don't need rowsAffected or insertId, so we can have a slight - // perf boost by just executing the query - debug("type: drop/create/etc."); - statement.ExecuteScalar(); - return EMPTY_RESULT; - } - } - catch (Exception e) - { - debug("insert or update or delete error", e.Message); - return EMPTY_RESULT; - } - finally - { - if (statement != null) - { - statement.Dispose(); - } - } - } - - private class SQLitePLuginResult - { - public readonly List rows; - public readonly List columns; - public readonly int rowsAffected; - public readonly long insertId; - public readonly Exception error; - - public SQLitePLuginResult(List rows, List columns, - int rowsAffected, long insertId, Exception error) - { - this.rows = rows; - this.columns = columns; - this.rowsAffected = rowsAffected; - this.insertId = insertId; - this.error = error; - } - } - private class ReadOnlyException : Exception - { - public ReadOnlyException() : base("could not prepare statement (23 not authorized)") - { - } - } - } -} +using ReactNative.Bridge; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Data.Sqlite; +using System.IO; +using Newtonsoft.Json.Linq; +using System.Data; +using System.Text.RegularExpressions; + +namespace RNSqlite2 +{ + class RNSqlite2Module : ReactContextNativeModuleBase + { + public RNSqlite2Module(ReactContext reactContext) + : base(reactContext) + { + this.reactContext = reactContext; + } + + public override string Name + { + get + { + return "RNSqlite2"; + } + } + private readonly ReactContext reactContext; + + private static readonly bool DEBUG_MODE = false; + + private static readonly string TAG = typeof(RNSqlite2Module).Name; + + private static readonly List EMPTY_ROWS = new List(); + private static readonly List EMPTY_COLUMNS = new List(); + + //protected Context context = null; + private static readonly SQLitePLuginResult EMPTY_RESULT = new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, null); + + private static readonly Dictionary DATABASES = new Dictionary(); + private static readonly Dictionary TRANSACTIONS = new Dictionary(); + + [ReactMethod] + public void test(string message, IPromise promise) + { + debug("example", "test called: " + message); + promise.Resolve(message + "\u0000" + "hoge"); + } + + private SqliteConnection getDatabase(string name) + { + if (!DATABASES.ContainsKey(name)) + { +#if WINDOWS_UWP + string path = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, name); +#else + string path = Path.Combine(PCLStorage.FileSystem.Current.LocalStorage.Path, name); +#endif + SqliteConnection conn = new SqliteConnection("Filename=" + path); + DATABASES[name] = conn; + TRANSACTIONS[name] = null; + } + return DATABASES[name]; + } + + // TODO: Need improve . This function : command.Parameters.AddWithValue(item.Key, item.Value); don't support bind string by index or i missing something + private static void parseJsonArrayToParams(ref string sql, List jsonArray, ref SqliteCommand statement) + { + MatchCollection matches = Regex.Matches(sql, @"\?"); + Dictionary array = new Dictionary(); + int len = matches.Count; + if (len > 0) + { + if (matches.Count > jsonArray.Count) + { + throw new Exception("parameter not correct"); + } + + var lastLen = 0; + for (int i = 0; i < len; i++) + { + var stringBuilder = new StringBuilder(sql); + stringBuilder.Remove(lastLen + matches[i].Index, 1); + stringBuilder.Insert(lastLen + matches[i].Index, "@" + i); + statement.Parameters.AddWithValue("@" + i, jsonArray[i].ToString()); + lastLen += i.ToString().Length; + sql = stringBuilder.ToString(); + } + } + else + { + // This is old code handle txn.executeSql('INSERT INTO Users (name) VALUES (:name)', ['narumiya']) + matches = Regex.Matches(sql, @"\:([A-Za-z0-9]*)"); + len = matches.Count; + + if (matches.Count > jsonArray.Count) + { + throw new Exception("parameter not correct"); + } + + for (int i = 0; i < len; i++) + { + statement.Parameters.AddWithValue(matches[i].Value, jsonArray[i].ToString()); + } + } + } + + private static bool isSelect(String str) + { + return str.TrimStart().StartsWith("select", StringComparison.OrdinalIgnoreCase); + } + + private static bool isInsert(String str) + { + return str.TrimStart().StartsWith("insert", StringComparison.OrdinalIgnoreCase); + } + + private static bool isDelete(String str) + { + return str.TrimStart().StartsWith("delete", StringComparison.OrdinalIgnoreCase); + } + + private static bool isUpdate(String str) + { + return str.TrimStart().StartsWith("update", StringComparison.OrdinalIgnoreCase); + } + + private static bool isBegin(String str) + { + return str.TrimStart().StartsWith("begin", StringComparison.OrdinalIgnoreCase); + } + + private static bool isEnd(String str) + { + return str.TrimStart().StartsWith("end", StringComparison.OrdinalIgnoreCase); + } + + [ReactMethod] + public void exec(string dbName, JArray queries, bool readOnly, IPromise promise) + { + debug("test called: " + dbName); + SqliteConnection db = getDatabase(dbName); + try + { + int numQueries = queries.Count; + SQLitePLuginResult[] results = new SQLitePLuginResult[numQueries]; + if (TRANSACTIONS[dbName] == null) + { + db.Open(); + } + for (int i = 0; i < numQueries; i++) + { + var sqlQuery = queries[i]; + string sql = sqlQuery[0].ToString(); + try + { + if (isSelect(sql)) + { + results[i] = doSelectInBackgroundAndPossiblyThrow(sql, sqlQuery[1].ToList(), db, TRANSACTIONS[dbName]); + } + else if (isBegin(sql)) + { + //Handle begin without end + if (TRANSACTIONS[dbName] != null) + { + TRANSACTIONS[dbName].Rollback(); + TRANSACTIONS[dbName].Dispose(); + TRANSACTIONS[dbName] = null; + } + TRANSACTIONS[dbName] = db.BeginTransaction(); + results[i] = EMPTY_RESULT; + } + else if (isEnd(sql)) + { + if (TRANSACTIONS[dbName] != null) + { + TRANSACTIONS[dbName].Commit(); + TRANSACTIONS[dbName].Dispose(); + TRANSACTIONS[dbName] = null; + } + results[i] = EMPTY_RESULT; + } + else + { // update/insert/delete + if (readOnly) + { + results[i] = new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, new ReadOnlyException()); + } + else + { + results[i] = doUpdateInBackgroundAndPossiblyThrow(sql, sqlQuery[1].ToList(), db, TRANSACTIONS[dbName]); + } + } + } + catch (Exception e) + { + if (RNSqlite2Module.DEBUG_MODE) + { + Console.WriteLine(e.ToString()); + } + if (TRANSACTIONS[dbName] != null) + { + TRANSACTIONS[dbName].Rollback(); + TRANSACTIONS[dbName].Dispose(); + TRANSACTIONS[dbName] = null; + } + results[i] = new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, e); + } + } + + List data = pluginResultsToPrimitiveData(results); + promise.Resolve(data); + } + catch (Exception e) + { + promise.Reject("SQLiteError", e); + if (TRANSACTIONS[dbName] != null) + { + TRANSACTIONS[dbName].Rollback(); + TRANSACTIONS[dbName].Dispose(); + TRANSACTIONS[dbName] = null; + } + } + finally + { + if (TRANSACTIONS[dbName] == null) + { + db.Close(); + } + } + } + + private Object getValueFromCursor(SqliteDataReader reader, int index, Type dataType) + { + if (dataType == typeof(Int16)) + { + return reader.GetInt16(index); + } + else if (dataType == typeof(Int32)) + { + return reader.GetInt32(index); + } + else if (dataType == typeof(Int64)) + { + return reader.GetInt64(index); + } + else if (dataType == typeof(double)) + { + return reader.GetDouble(index); + } + else if (dataType == typeof(decimal)) + { + return reader.GetDecimal(index); + } + else if (dataType == typeof(string)) + { + return reader.GetString(index); + } + else + { + return ""; + } + } + + private static void debug(String line, object format = null) + { + if (DEBUG_MODE) + { + Console.WriteLine(TAG, string.Format(line, format)); + } + } + + // do a select operation + private SQLitePLuginResult doSelectInBackgroundAndPossiblyThrow(string sql, List queryParams, + SqliteConnection db, SqliteTransaction transaction = null) + { + debug("\"all\" query: %s", sql); + SqliteDataReader query = null; + try + { + SqliteCommand command = new SqliteCommand(); + command.Connection = db; + command.Transaction = transaction; + + if (queryParams != null && queryParams.Count > 0) + { + parseJsonArrayToParams(ref sql, queryParams, ref command); + } + command.CommandText = sql; + + query = command.ExecuteReader(); + if (!query.HasRows) + { + return EMPTY_RESULT; + } + List entries = new List(); + + while (query.Read()) + { + List row = new List(); + for (int i = 0; i < query.FieldCount; i++) + { + var type = query.GetValue(i).GetType(); + row.Add(getValueFromCursor(query, i, type)); + } + entries.Add(row); + } + List columns = Enumerable.Range(0, query.FieldCount).Select(query.GetName).ToList(); + debug("returning %d rows", entries.Count()); + return new SQLitePLuginResult(entries, columns, 0, 0, null); + } + catch (Exception e) + { + debug("Query error", e.Message); + return new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, null); + } + finally + { + if (query != null) + { + query.Dispose(); + } + } + } + + private static List pluginResultsToPrimitiveData(SQLitePLuginResult[] results) + { + List list = new List(); + for (int i = 0; i < results.Count(); i++) + { + SQLitePLuginResult result = results[i]; + List arr = convertPluginResultToArray(result); + list.Add(arr); + } + return list; + } + + private static List convertPluginResultToArray(SQLitePLuginResult result) + { + List data = new List(); + if (result.error != null) + { + data.Add(result.error.Message); + } + else + { + data.Add(null); + } + data.Add((long)result.insertId); + data.Add((int)result.rowsAffected); + + // column names + data.Add(result.columns); + data.Add(result.rows); + + return data; + } + + // do a update/delete/insert operation + private SQLitePLuginResult doUpdateInBackgroundAndPossiblyThrow(string sql, List queryParams, + SqliteConnection db, SqliteTransaction transaction = null) + { + debug("\"run\" query: %s", sql); + SqliteCommand statement = null; + try + { + statement = new SqliteCommand(); + statement.Connection = db; + statement.Transaction = transaction; + statement.CommandType = CommandType.Text; + debug("compiled statement"); + + if (queryParams != null && queryParams.Count > 0) + { + parseJsonArrayToParams(ref sql, queryParams, ref statement); + } + statement.CommandText = sql; + debug("bound args"); + if (isInsert(sql)) + { + debug("type: insert"); + int rowsAffected = statement.ExecuteNonQuery(); + long insertId = 0; + if (rowsAffected > 0) + { + statement.CommandText = "SELECT last_insert_rowid()"; + insertId = (long)statement.ExecuteScalar(); + } + return new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, rowsAffected, insertId, null); + } + else if (isDelete(sql) || isUpdate(sql)) + { + debug("type: update/delete"); + int rowsAffected = statement.ExecuteNonQuery(); + return new SQLitePLuginResult(EMPTY_ROWS, EMPTY_COLUMNS, rowsAffected, 0, null); + } + else + { + // in this case, we don't need rowsAffected or insertId, so we can have a slight + // perf boost by just executing the query + debug("type: drop/create/etc."); + statement.ExecuteScalar(); + return EMPTY_RESULT; + } + } + catch (Exception e) + { + debug("insert or update or delete error", e.Message); + return EMPTY_RESULT; + } + finally + { + if (statement != null) + { + statement.Dispose(); + } + } + } + + private class SQLitePLuginResult + { + public readonly List rows; + public readonly List columns; + public readonly int rowsAffected; + public readonly long insertId; + public readonly Exception error; + + public SQLitePLuginResult(List rows, List columns, + int rowsAffected, long insertId, Exception error) + { + this.rows = rows; + this.columns = columns; + this.rowsAffected = rowsAffected; + this.insertId = insertId; + this.error = error; + } + } + private class ReadOnlyException : Exception + { + public ReadOnlyException() : base("could not prepare statement (23 not authorized)") + { + } + } + } +} diff --git a/windows/RNSqlite2/RNSqlite2Package.cs b/windows/RNSqlite2Package.cs similarity index 96% rename from windows/RNSqlite2/RNSqlite2Package.cs rename to windows/RNSqlite2Package.cs index b6a539a..9040ccc 100644 --- a/windows/RNSqlite2/RNSqlite2Package.cs +++ b/windows/RNSqlite2Package.cs @@ -1,32 +1,32 @@ -using ReactNative.Bridge; -using ReactNative.Modules.Core; -using ReactNative.UIManager; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace RNSqlite2 -{ - public class RNSqlite2Package : IReactPackage - { - public IReadOnlyList CreateNativeModules(ReactContext reactContext) - { - return new List - { - new RNSqlite2Module(reactContext) - }; - } - - public IReadOnlyList CreateJavaScriptModulesConfig() - { - return new List(0); - } - - public IReadOnlyList CreateViewManagers(ReactContext reactContext) - { - return new List(0); - } - } -} +using ReactNative.Bridge; +using ReactNative.Modules.Core; +using ReactNative.UIManager; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RNSqlite2 +{ + public class RNSqlite2Package : IReactPackage + { + public IReadOnlyList CreateNativeModules(ReactContext reactContext) + { + return new List + { + new RNSqlite2Module(reactContext) + }; + } + + public IReadOnlyList CreateJavaScriptModulesConfig() + { + return new List(0); + } + + public IReadOnlyList CreateViewManagers(ReactContext reactContext) + { + return new List(0); + } + } +}