From ae2d57e1718f9fb162c62aa3f09731959601a38c Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Wed, 31 Dec 2025 12:58:55 +0200 Subject: [PATCH 1/4] Add tests --- .../Client/PowerSyncDatabaseTests.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs b/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs index b2d6dde..e4e3e62 100644 --- a/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs +++ b/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs @@ -69,6 +69,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 = ?", [id]); + + Assert.Single(result); + var row = result.First(); + Assert.Equal(name, row.description); + Assert.Equal(null, row.make); + } + [Fact] public async Task FailedInsertTest() { @@ -434,4 +453,4 @@ await db.WriteTransaction(async tx => var afterTx = await db.GetAll("SELECT * FROM assets"); Assert.Equal(2, afterTx.Length); } -} \ No newline at end of file +} From 7fd02416ebefdf0609e25ddf082589df731391b0 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Wed, 31 Dec 2025 13:07:52 +0200 Subject: [PATCH 2/4] Fix tests --- .../PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs b/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs index e4e3e62..ab45ab8 100644 --- a/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs +++ b/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTests.cs @@ -80,7 +80,7 @@ await db.Execute( [id, name, null] ); - var result = await db.GetAll("SELECT id, description, make FROM assets WHERE id = ?", [id]); + 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(); From 43f9f201f79b36e7e233c88feae1640eec241adc Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Wed, 31 Dec 2025 13:15:08 +0200 Subject: [PATCH 3/4] Update function signatures --- .../Client/PowerSyncDatabase.cs | 22 +++++++++---------- PowerSync/PowerSync.Common/DB/IDBAdapter.cs | 10 ++++----- .../MDSQLite/MDSQLiteAdapter.cs | 18 +++++++-------- .../MDSQLite/MDSQLiteConnection.cs | 14 ++++++------ 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs b/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs index 9f17882..17664fa 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); @@ -543,24 +543,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); @@ -608,7 +608,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 () => @@ -652,7 +652,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] : []; @@ -789,4 +789,4 @@ public class WatchOnChangeHandler { public Func OnChange { get; set; } = null!; public Action? OnError { get; set; } -} \ No newline at end of file +} diff --git a/PowerSync/PowerSync.Common/DB/IDBAdapter.cs b/PowerSync/PowerSync.Common/DB/IDBAdapter.cs index b0c906b..8782cc0 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, params object[]? parameters); + Task GetAll(string sql, params object?[]? parameters); // Execute a read-only query and return the first result, or null if the ResultSet is empty. - Task GetOptional(string sql, params object[]? parameters); + Task GetOptional(string sql, params object?[]? parameters); // Execute a read-only query and return the first result, error if the ResultSet is empty. - Task Get(string sql, params object[]? parameters); + Task Get(string sql, params object?[]? parameters); } 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 6c73c8b..92ccd7c 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"); } @@ -215,4 +215,4 @@ public async Task RefreshSchema() { await Get("PRAGMA table_info('sqlite_master')"); } -} \ No newline at end of file +} From 48ca53e606e818f3dc35ce1b4a08084a65954cb8 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Mon, 5 Jan 2026 17:47:08 +0200 Subject: [PATCH 4/4] Remove unneeded ClientImplementation in CommandLine demo --- demos/CommandLine/Demo.cs | 1 - 1 file changed, 1 deletion(-) 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() },