diff --git a/Directory.Build.props b/Directory.Build.props
index 7cf793c5..de217239 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -14,6 +14,7 @@
true
true
+ 3.0.1
diff --git a/Makefile b/Makefile
index 42cd34dd..622b9cde 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ PACKAGES_OUT=$(abspath PackagesOut)
all: nuget
-nuget: pclnuget basenuget sqlciphernuget staticnuget
+nuget: pclnuget basenuget staticnuget
pclnuget: nuget/SQLite-net-std/SQLite-net-std.csproj $(SRC)
dotnet pack -c Release -o $(PACKAGES_OUT) $<
@@ -13,9 +13,6 @@ pclnuget: nuget/SQLite-net-std/SQLite-net-std.csproj $(SRC)
basenuget: nuget/SQLite-net-base/SQLite-net-base.csproj $(SRC)
dotnet pack -c Release -o $(PACKAGES_OUT) $<
-sqlciphernuget: nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj $(SRC)
- dotnet pack -c Release -o $(PACKAGES_OUT) $<
-
staticnuget: nuget/SQLite-net-static/SQLite-net-static.csproj $(SRC)
dotnet pack -c Release -o $(PACKAGES_OUT) $<
diff --git a/README.md b/README.md
index 04e9ad9f..9cb10898 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,6 @@ Use one of these packages:
| Version | Package | Description |
| ------- | ------- | ----------- |
| [](https://www.nuget.org/packages/sqlite-net-pcl) | [sqlite-net-pcl](https://www.nuget.org/packages/sqlite-net-pcl) | .NET Standard Library |
-| [](https://www.nuget.org/packages/sqlite-net-sqlcipher) | [sqlite-net-sqlcipher](https://www.nuget.org/packages/sqlite-net-sqlcipher) | With Encryption Support |
| [](https://www.nuget.org/packages/sqlite-net-static) | [sqlite-net-static](https://www.nuget.org/packages/sqlite-net-static) | Special version that uses P/Invokes to platform-provided sqlite3 |
| [](https://www.nuget.org/packages/sqlite-net-base) | [sqlite-net-base](https://www.nuget.org/packages/sqlite-net-base) | without a SQLitePCLRaw bundle so you can choose your own provider |
@@ -204,9 +203,9 @@ db.Execute ("insert into Stock(Symbol) values (?)", "MSFT");
var stocks = db.Query ("select * from Stock");
```
-## Using SQLCipher
+## Using encryption
-You can use an encrypted database by using the [sqlite-net-sqlcipher NuGet package](https://www.nuget.org/packages/sqlite-net-sqlcipher).
+If you are using a native SQLite instance which supports encryption:
The database key is set in the `SqliteConnectionString` passed to the connection constructor:
diff --git a/SQLite.sln b/SQLite.sln
index a6329bc1..fa0b05d6 100644
--- a/SQLite.sln
+++ b/SQLite.sln
@@ -15,8 +15,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiDiff", "tests\ApiDiff\Ap
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-base", "nuget\SQLite-net-base\SQLite-net-base.csproj", "{53D1953C-3641-47D0-BE08-14DB853CC576}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-sqlcipher", "nuget\SQLite-net-sqlcipher\SQLite-net-sqlcipher.csproj", "{59DB03EF-E28D-431E-9058-74AF316800EE}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.Tests", "tests\SQLite.Tests\SQLite.Tests.csproj", "{80B66A43-B358-4438-BF06-6351B86B121A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-static", "nuget\SQLite-net-static\SQLite-net-static.csproj", "{7CD60DAE-D505-4C2E-80B3-296556CE711E}"
@@ -67,18 +65,6 @@ Global
{53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.Build.0 = Debug|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|Any CPU.Build.0 = Release|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhone.ActiveCfg = Release|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhone.Build.0 = Release|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhone.Build.0 = Debug|Any CPU
{80B66A43-B358-4438-BF06-6351B86B121A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80B66A43-B358-4438-BF06-6351B86B121A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80B66A43-B358-4438-BF06-6351B86B121A}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/nuget/SQLite-net-base/SQLite-net-base.csproj b/nuget/SQLite-net-base/SQLite-net-base.csproj
index 8fd9ae2e..0a78763d 100644
--- a/nuget/SQLite-net-base/SQLite-net-base.csproj
+++ b/nuget/SQLite-net-base/SQLite-net-base.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj b/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj
deleted file mode 100644
index 714af08a..00000000
--- a/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
- netstandard2.0;net8.0;net9.0
- SQLite-net
- sqlite-net-sqlcipher
- SQLite-net SQLCipher .NET Standard Library
-
- SQLite-net is an open source and light weight library providing easy SQLite database storage for .NET, Mono, and Xamarin applications.
- This version uses SQLitePCLRaw to provide platform independent versions of SQLite with the SQLCipher extension.
- This enables secure access to the database with password (key) access.
-
- sqlite-net;sqlite;database;orm;encryption;sqlcipher
- true
-
-
-
- USE_SQLITEPCL_RAW;RELEASE
- bin\Release\$(TargetFramework)\SQLite-net.xml
-
-
- USE_SQLITEPCL_RAW;DEBUG
- bin\Debug\$(TargetFramework)\SQLite-net.xml
-
-
-
-
-
-
-
- SQLite.cs
-
-
- SQLiteAsync.cs
-
-
-
-
diff --git a/nuget/SQLite-net-std/SQLite-net-std.csproj b/nuget/SQLite-net-std/SQLite-net-std.csproj
index 4a1e6d1b..92e5d56c 100644
--- a/nuget/SQLite-net-std/SQLite-net-std.csproj
+++ b/nuget/SQLite-net-std/SQLite-net-std.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net8.0;net9.0
+ netstandard2.0;net8.0;net9.0;net8.0-ios
SQLite-net
sqlite-net-pcl
SQLite-net Official .NET Standard Library
@@ -10,6 +10,7 @@
This version uses SQLitePCLRaw to provide platform independent versions of SQLite.
true
+ $([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0-ios'))
@@ -22,8 +23,35 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(DefineConstants);PROVIDER_sqlite3
+
+
+
+ $(DefineConstants);PROVIDER_e_sqlite3
+
+
SQLite.cs
diff --git a/nuget/SQLite-net-std/batteries_v2.cs b/nuget/SQLite-net-std/batteries_v2.cs
new file mode 100644
index 00000000..efef85db
--- /dev/null
+++ b/nuget/SQLite-net-std/batteries_v2.cs
@@ -0,0 +1,20 @@
+
+using System;
+
+namespace SQLitePCL
+{
+ internal static class Batteries_V2
+ {
+ public static void Init()
+ {
+#if PROVIDER_sqlite3
+ SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
+#elif PROVIDER_e_sqlite3
+ SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3());
+#else
+#error batteries_v2.cs built with nothing specified
+#endif
+ }
+ }
+}
+
diff --git a/src/SQLite.cs b/src/SQLite.cs
index 72525c56..dec2f7ff 100644
--- a/src/SQLite.cs
+++ b/src/SQLite.cs
@@ -25,6 +25,7 @@
using System;
using System.Collections;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
#if NET8_0_OR_GREATER
@@ -611,6 +612,9 @@ public SQLiteConnection (SQLiteConnectionString connectionString)
throw new InvalidOperationException ("Encryption keys must be strings or byte arrays");
}
connectionString.PostKeyAction?.Invoke (this);
+
+ foreach (var handler in CustomTypeRegistry.Handlers)
+ handler.Initialize (this);
}
///
@@ -706,6 +710,28 @@ public void EnableLoadExtension (bool enabled)
throw SQLiteException.New (r, msg);
}
}
+
+ ///
+ /// Load extension.
+ ///
+ public void LoadExtension (string filename)
+ {
+ SQLite3.Result r = SQLite3.LoadExtension (Handle, filename, null, out var msg);
+ if (r != SQLite3.Result.OK) {
+ throw SQLiteException.New (r, msg);
+ }
+ }
+
+ ///
+ /// Load extension.
+ ///
+ public void LoadExtension (string filename, string customInitFunctionName)
+ {
+ SQLite3.Result r = SQLite3.LoadExtension (Handle, filename, customInitFunctionName, out var msg);
+ if (r != SQLite3.Result.OK) {
+ throw SQLiteException.New (r, msg);
+ }
+ }
#if !USE_SQLITEPCL_RAW
static byte[] GetNullTerminatedUtf8 (string s)
@@ -893,17 +919,52 @@ public CreateTableResult CreateTable (
var @virtual = fts ? "virtual " : string.Empty;
var @using = fts3 ? "using fts3 " : fts4 ? "using fts4 " : string.Empty;
- // Build query.
- var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n";
- var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks));
- var decl = string.Join (",\n", decls.ToArray ());
- query += decl;
- query += ")";
- if (map.WithoutRowId) {
- query += " without rowid";
+ // Build query - but exclude custom type columns from initial CREATE TABLE
+ var standardColumns = map.Columns.Where (c => !c.HasCustomTypeHandler).ToArray ();
+
+ if (standardColumns.Length > 0) {
+ // Build query.
+ var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n";
+ var decls = standardColumns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks));
+ var decl = string.Join (",\n", decls.ToArray ());
+ query += decl;
+ query += ")";
+ if (map.WithoutRowId) {
+ query += " without rowid";
+ }
+
+ Execute (query);}
+ else {
+ // All columns are custom types, create empty table first
+ var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(temp_col INTEGER)";
+ Execute (query);
}
+ // Add custom type columns using their specific handlers
+ foreach (var customCol in map.Columns.Where (c => c.HasCustomTypeHandler)) {
+ var handler = customCol.CustomTypeHandler;
+ var metadata = customCol.GetCustomTypeMetadata ();
+ var (addColSql, commandType) = handler.GetAddColumnSql (map.TableName, metadata);
+
+ if (!string.IsNullOrEmpty (addColSql)) {
+ if(commandType == CommandType.ExecuteScalar)
+ ExecuteScalar(addColSql);
+ else
+ Execute (addColSql);
+ }
+ else {
+ // Fallback to standard ALTER TABLE
+ var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (customCol, StoreDateTimeAsTicks, StoreTimeSpanAsTicks);
+ Execute (addCol);
+ }
- Execute (query);
+ // Call the OnTableCreated callback
+ handler.OnTableCreated ( this, map.TableName, metadata );
+ }
+
+ // Remove temp column if we had to create one
+ if (standardColumns.Length == 0) {
+ Execute ($"ALTER TABLE \"{map.TableName}\" DROP COLUMN temp_col");
+ }
}
else {
result = CreateTableResult.Migrated;
@@ -938,7 +999,28 @@ public CreateTableResult CreateTable (
foreach (var indexName in indexes.Keys) {
var index = indexes[indexName];
var columns = index.Columns.OrderBy (i => i.Order).Select (i => i.ColumnName).ToArray ();
- CreateIndex (indexName, index.TableName, columns, index.Unique);
+
+ // Check if any column in this index uses a custom type
+ var customColumn = map.Columns.FirstOrDefault (c => columns.Contains (c.Name) && c.HasCustomTypeHandler);
+ if (customColumn != null) {
+ var handler = customColumn.CustomTypeHandler;
+ var metadata = customColumn.GetCustomTypeMetadata ();
+ var (createIndexSql, commandType) = handler.GetCreateIndexSql (indexName, index.TableName, customColumn.Name, index.Unique, metadata );
+
+ if (!string.IsNullOrEmpty (createIndexSql)) {
+ if(commandType == CommandType.ExecuteScalar)
+ ExecuteScalar(createIndexSql);
+ else
+ Execute (createIndexSql);
+ }
+ else {
+ // Fallback to standard index creation
+ CreateIndex (indexName, index.TableName, columns, index.Unique);
+ }
+ }
+ else {
+ CreateIndex (indexName, index.TableName, columns, index.Unique);
+ }
}
return result;
@@ -1241,8 +1323,30 @@ void MigrateTable (TableMapping map, List existingCols)
}
foreach (var p in toBeAdded) {
- var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks);
- Execute (addCol);
+ if (p.HasCustomTypeHandler) {
+ var handler = p.CustomTypeHandler;
+ var metadata = p.GetCustomTypeMetadata ();
+ var (addColSql, commandType) = handler.GetAddColumnSql (map.TableName, metadata );
+
+ if (!string.IsNullOrEmpty (addColSql)) {
+ if(commandType == CommandType.ExecuteScalar)
+ ExecuteScalar(addColSql);
+ else
+ Execute (addColSql);
+ }
+ else {
+ // Fallback to standard ALTER TABLE
+ var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks);
+ Execute (addCol);
+ }
+
+ // Call the OnTableCreated callback
+ handler.OnTableCreated (this, map.TableName, metadata);
+ }
+ else {
+ var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks);
+ Execute (addCol);
+ }
}
}
@@ -2227,7 +2331,7 @@ public int Insert (
// We lock here to protect the prepared statement returned via GetInsertCommand.
// A SQLite prepared statement can be bound for only one operation at a time.
try {
- count = insertCmd.ExecuteNonQuery (vals);
+ count = insertCmd.ExecuteNonQuery (vals, map.Columns);
}
catch (SQLiteException ex) {
if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) {
@@ -2289,12 +2393,26 @@ PreparedSqlLiteInsertCommand CreateInsertCommand (TableMapping map, string extra
cols = map.InsertOrReplaceColumns;
}
- insertSql = string.Format ("insert {3} into \"{0}\"({1}) values ({2})", map.TableName,
- string.Join (",", (from c in cols
- select "\"" + c.Name + "\"").ToArray ()),
- string.Join (",", (from c in cols
- select "?").ToArray ()), extra);
+ // Build column names
+ var columnNames = string.Join (",", cols.Select (c => "\"" + c.Name + "\"").ToArray ());
+
+ // Build value placeholders - use custom expressions for custom types
+ var valuePlaceholders = new List ();
+ for (int i = 0; i < cols.Length; i++) {
+ var col = cols[i];
+ if (col.HasCustomTypeHandler) {
+ var handler = col.CustomTypeHandler;
+ var metadata = col.GetCustomTypeMetadata ();
+ var expression = (string)handler.GetInsertExpression ("?", metadata );
+ valuePlaceholders.Add (expression);
+ }
+ else {
+ valuePlaceholders.Add ("?");
+ }
+ }
+ insertSql = string.Format ("insert {3} into \"{0}\"({1}) values ({2})",
+ map.TableName, columnNames, string.Join (",", valuePlaceholders.ToArray ()), extra);
}
var insertCommand = new PreparedSqlLiteInsertCommand (this, insertSql);
@@ -2576,6 +2694,27 @@ public void Backup (string destinationDatabasePath, string databaseName = "main"
throw SQLiteException.New (r, msg);
}
}
+
+ ///
+ /// Defines a custom type handler for use with SQLite operations
+ ///
+ /// The custom type
+ /// The handler that defines how to work with this type
+ public void DefineCustomType (CustomTypeHandler handler)
+ {
+ if (CustomTypeRegistry.RegisterHandler (handler))
+ handler.Initialize (this);
+ }
+
+ ///
+ /// Gets the custom type handler for a given type
+ ///
+ /// The custom type
+ /// The handler, or null if not registered
+ public CustomTypeHandler GetCustomTypeHandler ()
+ {
+ return CustomTypeRegistry.GetHandler ();
+ }
~SQLiteConnection ()
{
@@ -3200,6 +3339,30 @@ private static Type GetMemberType(MemberInfo m)
default: throw new InvalidProgramException($"{nameof(TableMapping)} supports properties or fields only.");
}
}
+
+ ///
+ /// Gets whether this column uses a custom type handler
+ ///
+ public bool HasCustomTypeHandler => CustomTypeRegistry.HasHandler (ColumnType);
+
+ ///
+ /// Gets the custom type handler for this column, if any
+ ///
+ public ICustomTypeHandler CustomTypeHandler => CustomTypeRegistry.TryGetHandler (ColumnType, out var handler) ? handler : null;
+
+ private CustomTypeMetadata _customTypeMetadata;
+
+ ///
+ /// Gets the custom type metadata for this column, including access to custom attributes
+ ///
+ public CustomTypeMetadata GetCustomTypeMetadata ()
+ {
+ if (_customTypeMetadata == null) {
+ _customTypeMetadata = new CustomTypeMetadata (this);
+ }
+ return _customTypeMetadata;
+ }
+
}
internal enum MapMethod
@@ -3339,6 +3502,11 @@ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks,
else if (clrType == typeof (Guid)) {
return "varchar(36)";
}
+ else if(CustomTypeRegistry.TryGetHandler(clrType, out var handler))
+ {
+ var metadata = p.GetCustomTypeMetadata();
+ return handler.GetSqlType(metadata);
+ }
else {
throw new NotSupportedException ("Don't know about " + clrType);
}
@@ -3575,7 +3743,7 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map)
for (int i = 0; i < cols.Length; i++) {
var name = SQLite3.ColumnName16 (stmt, i);
cols[i] = map.FindColumn (name);
- if (cols[i] != null)
+ if (cols[i] != null && !cols[i].HasCustomTypeHandler)
if (getSetter != null) {
fastColumnSetters[i] = (Action
+
+ USE_SQLITEPCL_RAW;RELEASE
+
+
+ USE_SQLITEPCL_RAW;DEBUG
+
+
SQLite.cs