diff --git a/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs b/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs index c1ee6fa..420a0c1 100644 --- a/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs +++ b/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs @@ -58,13 +58,13 @@ public interface IPowerSyncDatabase : IEventStream public Task GetNextCrudTransaction(); - Task Execute(string query, object[]? parameters = null); + Task Execute(string query, object?[]? parameters = null); - Task GetAll(string sql, object[]? parameters = null); + Task GetAll(string sql, object?[]? parameters = null); - Task GetOptional(string sql, object[]? parameters = null); + Task GetOptional(string sql, object?[]? parameters = null); - Task Get(string sql, object[]? parameters = null); + Task Get(string sql, object?[]? parameters = null); Task ReadLock(Func> fn, DBLockOptions? options = null); @@ -549,24 +549,24 @@ public async Task GetClientId() return await BucketStorageAdapter.GetClientId(); } - public async Task Execute(string query, object[]? parameters = null) + public async Task Execute(string query, object?[]? parameters = null) { await WaitForReady(); return await Database.Execute(query, parameters); } - public async Task GetAll(string query, object[]? parameters = null) + public async Task GetAll(string query, object?[]? parameters = null) { await WaitForReady(); return await Database.GetAll(query, parameters); } - public async Task GetOptional(string query, object[]? parameters = null) + public async Task GetOptional(string query, object?[]? parameters = null) { await WaitForReady(); return await Database.GetOptional(query, parameters); } - public async Task Get(string query, object[]? parameters = null) + public async Task Get(string query, object?[]? parameters = null) { await WaitForReady(); return await Database.Get(query, parameters); @@ -614,7 +614,7 @@ public async Task WriteTransaction(Func> fn, DBLockO /// Use to specify the minimum interval between queries. /// Source tables are automatically detected using EXPLAIN QUERY PLAN. /// - public Task Watch(string query, object[]? parameters, WatchHandler handler, SQLWatchOptions? options = null) + public Task Watch(string query, object?[]? parameters, WatchHandler handler, SQLWatchOptions? options = null) { var tcs = new TaskCompletionSource(); Task.Run(async () => @@ -658,7 +658,7 @@ public Task Watch(string query, object[]? parameters, WatchHandler handler private record ExplainedResult(string opcode, int p2, int p3); private record TableSelectResult(string tbl_name); - public async Task ResolveTables(string sql, object[]? parameters = null, SQLWatchOptions? options = null) + public async Task ResolveTables(string sql, object?[]? parameters = null, SQLWatchOptions? options = null) { List resolvedTables = options?.Tables != null ? [.. options.Tables] : []; diff --git a/PowerSync/PowerSync.Common/DB/IDBAdapter.cs b/PowerSync/PowerSync.Common/DB/IDBAdapter.cs index a31ee49..af81429 100644 --- a/PowerSync/PowerSync.Common/DB/IDBAdapter.cs +++ b/PowerSync/PowerSync.Common/DB/IDBAdapter.cs @@ -30,19 +30,19 @@ public class QueryRows public interface IDBGetUtils { // Execute a read-only query and return results. - Task GetAll(string sql, object[]? parameters = null); + Task GetAll(string sql, object?[]? parameters = null); // Execute a read-only query and return the first result, or null if the ResultSet is empty. - Task GetOptional(string sql, object[]? parameters = null); + Task GetOptional(string sql, object?[]? parameters = null); // Execute a read-only query and return the first result, error if the ResultSet is empty. - Task Get(string sql, object[]? parameters = null); + Task Get(string sql, object?[]? parameters = null); } public interface ILockContext : IDBGetUtils { // Execute a single write statement. - Task Execute(string query, object[]? parameters = null); + Task Execute(string query, object?[]? parameters = null); } public interface ITransaction : ILockContext @@ -117,7 +117,7 @@ public interface IDBAdapter : IEventStream, ILockContext /// /// Execute a batch of write statements. /// - Task ExecuteBatch(string query, object[][]? parameters = null); + Task ExecuteBatch(string query, object?[][]? parameters = null); /// /// The name of the adapter. diff --git a/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs b/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs index 1ca43a0..6b042ef 100644 --- a/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs +++ b/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs @@ -144,29 +144,29 @@ protected virtual void LoadExtension(SqliteConnection db) readConnection?.Close(); } - public async Task Execute(string query, object[]? parameters = null) + public async Task Execute(string query, object?[]? parameters = null) { return await WriteLock((ctx) => ctx.Execute(query, parameters)); } - public Task ExecuteBatch(string query, object[][]? parameters = null) + public Task ExecuteBatch(string query, object?[][]? parameters = null) { // https://learn.microsoft.com/en-gb/dotnet/standard/data/sqlite/batching throw new NotImplementedException(); } - public async Task Get(string sql, object[]? parameters = null) + public async Task Get(string sql, object?[]? parameters = null) { return await ReadLock((ctx) => ctx.Get(sql, parameters)); ; } - public async Task GetAll(string sql, object[]? parameters = null) + public async Task GetAll(string sql, object?[]? parameters = null) { return await ReadLock((ctx) => ctx.GetAll(sql, parameters)); } - public async Task GetOptional(string sql, object[]? parameters = null) + public async Task GetOptional(string sql, object?[]? parameters = null) { return await ReadLock((ctx) => ctx.GetOptional(sql, parameters)); } @@ -302,22 +302,22 @@ public async Task Rollback() await connection.Execute("ROLLBACK"); } - public Task Execute(string query, object[]? parameters = null) + public Task Execute(string query, object?[]? parameters = null) { return connection.Execute(query, parameters); } - public Task Get(string sql, object[]? parameters = null) + public Task Get(string sql, object?[]? parameters = null) { return connection.Get(sql, parameters); } - public Task GetAll(string sql, object[]? parameters = null) + public Task GetAll(string sql, object?[]? parameters = null) { return connection.GetAll(sql, parameters); } - public Task GetOptional(string sql, object[]? parameters = null) + public Task GetOptional(string sql, object?[]? parameters = null) { return connection.GetOptional(sql, parameters); } diff --git a/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteConnection.cs b/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteConnection.cs index c67dd97..922152e 100644 --- a/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteConnection.cs +++ b/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteConnection.cs @@ -72,7 +72,7 @@ public void FlushUpdates() Emit(new DBAdapterEvent { TablesUpdated = batchedUpdate }); } - private static void PrepareCommand(SqliteCommand command, string query, object[]? parameters) + private static void PrepareCommand(SqliteCommand command, string query, object?[]? parameters) { if (parameters == null || parameters.Length == 0) { @@ -113,7 +113,7 @@ private static void PrepareCommand(SqliteCommand command, string query, object[] } } - public async Task Execute(string query, object[]? parameters = null) + public async Task Execute(string query, object?[]? parameters = null) { using var command = Db.CreateCommand(); PrepareCommand(command, query, parameters); @@ -127,7 +127,7 @@ public async Task Execute(string query, object[]? parameters = n }; } - public async Task ExecuteQuery(string query, object[]? parameters = null) + public async Task ExecuteQuery(string query, object?[]? parameters = null) { var result = new QueryResult(); using var command = Db.CreateCommand(); @@ -151,7 +151,7 @@ public async Task ExecuteQuery(string query, object[]? parameters = return result; } - public async Task GetAll(string sql, object[]? parameters = null) + public async Task GetAll(string sql, object?[]? parameters = null) { var result = await ExecuteQuery(sql, parameters); @@ -177,7 +177,7 @@ public async Task GetAll(string sql, object[]? parameters = null) return [.. items]; } - public async Task GetOptional(string sql, object[]? parameters = null) + public async Task GetOptional(string sql, object?[]? parameters = null) { var result = await ExecuteQuery(sql, parameters); @@ -198,7 +198,7 @@ public async Task GetAll(string sql, object[]? parameters = null) return JsonConvert.DeserializeObject(json); } - public async Task Get(string sql, object[]? parameters = null) + public async Task Get(string sql, object?[]? parameters = null) { return await GetOptional(sql, parameters) ?? throw new InvalidOperationException("Result set is empty"); } diff --git a/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs b/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs index 9c25c17..9bbceb4 100644 --- a/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs +++ b/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs @@ -70,6 +70,25 @@ await db.Execute( Assert.Equal(age.ToString(), row.make); } + [Fact] + public async Task QueryWithNullParams() + { + var id = Guid.NewGuid().ToString(); + var name = "Test user"; + + await db.Execute( + "INSERT INTO assets(id, description, make) VALUES(?, ?, ?)", + [id, name, null] + ); + + var result = await db.GetAll("SELECT id, description, make FROM assets WHERE id = ? AND make IS NULL", [id]); + + Assert.Single(result); + var row = result.First(); + Assert.Equal(name, row.description); + Assert.Equal(null, row.make); + } + [Fact] public async Task FailedInsertTest() { diff --git a/demos/CommandLine/Demo.cs b/demos/CommandLine/Demo.cs index 5a0e8cf..036a7a3 100644 --- a/demos/CommandLine/Demo.cs +++ b/demos/CommandLine/Demo.cs @@ -102,7 +102,6 @@ static async Task Main() await db.Connect(connector, new PowerSync.Common.Client.Sync.Stream.PowerSyncConnectionOptions { - ClientImplementation = PowerSync.Common.Client.Sync.Stream.SyncClientImplementation.RUST, AppMetadata = new Dictionary { { "app_version", GetAppVersion() },