Skip to content

Commit 92b0a7e

Browse files
authored
Merge pull request #36 from powersync-ja/dapper-query-engine
Use Dapper as the internal query engine
2 parents 19574b6 + e7fa47f commit 92b0a7e

11 files changed

Lines changed: 330 additions & 186 deletions

File tree

PowerSync/PowerSync.Common/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# PowerSync.Common Changelog
22

3+
## 0.0.8-alpha.1
4+
- Replaced the old JSON-based method of extracting type information from queries with using Dapper internally for queries, improving memory usage and execution time for querying.
5+
- Added non-generic overloads for `GetAll()`, `GetOptional()`, `Get()`, `Watch()` which return `dynamic`:
6+
7+
```csharp
8+
dynamic asset = db.Get("SELECT id, description, make FROM assets");
9+
Console.WriteLine($"Asset ID: {asset.id}");
10+
```
11+
312
## 0.0.7-alpha.1
413
- Added fallback to check the application's root directory for the PowerSync extension - fixing compatibility with WPF/WAP, .NET Framework <= 4.8, and other platforms that flatten DLLs into the base folder.
514
- Added `ExecuteBatch()` implementation.

PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,13 @@ public interface IPowerSyncDatabase : IEventStream<PowerSyncDBEvent>
6363
Task<NonQueryResult> ExecuteBatch(string query, object?[][]? parameters = null);
6464

6565
Task<T[]> GetAll<T>(string sql, object?[]? parameters = null);
66+
Task<dynamic[]> GetAll(string sql, object?[]? parameters = null);
6667

6768
Task<T?> GetOptional<T>(string sql, object?[]? parameters = null);
69+
Task<dynamic?> GetOptional(string sql, object?[]? parameters = null);
6870

6971
Task<T> Get<T>(string sql, object?[]? parameters = null);
72+
Task<dynamic> Get(string sql, object?[]? parameters = null);
7073

7174
Task<T> ReadLock<T>(Func<ILockContext, Task<T>> fn, DBLockOptions? options = null);
7275

@@ -422,7 +425,8 @@ await Database.WriteTransaction(async tx =>
422425
Closed = true;
423426
}
424427

425-
private record UploadQueueStatsResult(int size, int count);
428+
private record UploadQueueStatsSizeCountResult(long size, long count);
429+
private record UploadQueueStatsCountResult(long count);
426430
/// <summary>
427431
/// Get upload queue size estimate and count.
428432
/// </summary>
@@ -432,23 +436,22 @@ public async Task<UploadQueueStats> GetUploadQueueStats(bool includeSize = false
432436
{
433437
if (includeSize)
434438
{
435-
var result = await tx.Get<UploadQueueStatsResult>(
439+
var result = await tx.Get<UploadQueueStatsSizeCountResult>(
436440
$"SELECT SUM(cast(data as blob) + 20) as size, count(*) as count FROM {PSInternalTable.CRUD}"
437441
);
438442

439443
return new UploadQueueStats(result.count, result.size);
440444
}
441445
else
442446
{
443-
var result = await tx.Get<UploadQueueStatsResult>(
447+
var result = await tx.Get<UploadQueueStatsCountResult>(
444448
$"SELECT count(*) as count FROM {PSInternalTable.CRUD}"
445449
);
446450
return new UploadQueueStats(result.count);
447451
}
448452
});
449453
}
450454

451-
452455
/// <summary>
453456
/// Get a batch of crud data to upload.
454457
/// <para />
@@ -468,7 +471,7 @@ public async Task<UploadQueueStats> GetUploadQueueStats(bool includeSize = false
468471
/// </summary>
469472
public async Task<CrudBatch?> GetCrudBatch(int limit = 100)
470473
{
471-
var crudResult = await GetAll<CrudEntryJSON>($"SELECT id, tx_id, data FROM {PSInternalTable.CRUD} ORDER BY id ASC LIMIT ?", [limit + 1]);
474+
var crudResult = await GetAll<CrudEntryJSON>($"SELECT id, tx_id as transactionId, data FROM {PSInternalTable.CRUD} ORDER BY id ASC LIMIT ?", [limit + 1]);
472475

473476
var all = crudResult.Select(CrudEntry.FromRow).ToList();
474477

@@ -510,7 +513,7 @@ public async Task<UploadQueueStats> GetUploadQueueStats(bool includeSize = false
510513
return await ReadTransaction(async tx =>
511514
{
512515
var first = await tx.GetOptional<CrudEntryJSON>(
513-
$"SELECT id, tx_id, data FROM {PSInternalTable.CRUD} ORDER BY id ASC LIMIT 1");
516+
$"SELECT id, tx_id AS transactionId, data FROM {PSInternalTable.CRUD} ORDER BY id ASC LIMIT 1");
514517

515518
if (first == null)
516519
{
@@ -528,7 +531,7 @@ public async Task<UploadQueueStats> GetUploadQueueStats(bool includeSize = false
528531
else
529532
{
530533
var result = await tx.GetAll<CrudEntryJSON>(
531-
$"SELECT id, tx_id, data FROM {PSInternalTable.CRUD} WHERE tx_id = ? ORDER BY id ASC",
534+
$"SELECT id, tx_id as transactionId, data FROM {PSInternalTable.CRUD} WHERE tx_id = ? ORDER BY id ASC",
532535
[txId]);
533536

534537
all = result.Select(CrudEntry.FromRow).ToList();
@@ -596,17 +599,36 @@ public async Task<T[]> GetAll<T>(string query, object?[]? parameters = null)
596599
return await Database.GetAll<T>(query, parameters);
597600
}
598601

602+
public async Task<dynamic[]> GetAll(string query, object?[]? parameters = null)
603+
{
604+
await WaitForReady();
605+
return await Database.GetAll(query, parameters);
606+
}
607+
599608
public async Task<T?> GetOptional<T>(string query, object?[]? parameters = null)
600609
{
601610
await WaitForReady();
602611
return await Database.GetOptional<T>(query, parameters);
603612
}
613+
614+
public async Task<dynamic?> GetOptional(string query, object?[]? parameters = null)
615+
{
616+
await WaitForReady();
617+
return await Database.GetOptional(query, parameters);
618+
}
619+
604620
public async Task<T> Get<T>(string query, object?[]? parameters = null)
605621
{
606622
await WaitForReady();
607623
return await Database.Get<T>(query, parameters);
608624
}
609625

626+
public async Task<dynamic> Get(string query, object?[]? parameters = null)
627+
{
628+
await WaitForReady();
629+
return await Database.Get(query, parameters);
630+
}
631+
610632
public async Task<T> ReadLock<T>(Func<ILockContext, Task<T>> fn, DBLockOptions? options = null)
611633
{
612634
await WaitForReady();
@@ -650,14 +672,32 @@ public async Task<T> WriteTransaction<T>(Func<ITransaction, Task<T>> fn, DBLockO
650672
/// Source tables are automatically detected using <c>EXPLAIN QUERY PLAN</c>.
651673
/// </summary>
652674
public Task Watch<T>(string query, object?[]? parameters, WatchHandler<T> handler, SQLWatchOptions? options = null)
675+
=> WatchInternal(query, parameters, handler, options, GetAll<T>);
676+
677+
/// <summary>
678+
/// Executes a read query every time the source tables are modified.
679+
/// <para />
680+
/// Use <see cref="SQLWatchOptions.ThrottleMs"/> to specify the minimum interval between queries.
681+
/// Source tables are automatically detected using <c>EXPLAIN QUERY PLAN</c>.
682+
/// </summary>
683+
public Task Watch(string query, object?[]? parameters, WatchHandler<dynamic> handler, SQLWatchOptions? options = null)
684+
=> WatchInternal(query, parameters, handler, options, GetAll);
685+
686+
private Task WatchInternal<T>(
687+
string query,
688+
object?[]? parameters,
689+
WatchHandler<T> handler,
690+
SQLWatchOptions? options,
691+
Func<string, object?[]?, Task<T[]>> getter
692+
)
653693
{
654694
var tcs = new TaskCompletionSource<bool>();
655695
Task.Run(async () =>
656696
{
657697
try
658698
{
659699
var resolvedTables = await ResolveTables(query, parameters, options);
660-
var result = await GetAll<T>(query, parameters);
700+
var result = await getter(query, parameters);
661701
handler.OnResult(result);
662702

663703
OnChange(new WatchOnChangeHandler
@@ -666,7 +706,7 @@ public Task Watch<T>(string query, object?[]? parameters, WatchHandler<T> handle
666706
{
667707
try
668708
{
669-
var result = await GetAll<T>(query, parameters);
709+
var result = await getter(query, parameters);
670710
handler.OnResult(result);
671711
}
672712
catch (Exception ex)
@@ -691,7 +731,16 @@ public Task Watch<T>(string query, object?[]? parameters, WatchHandler<T> handle
691731
return tcs.Task;
692732
}
693733

694-
private record ExplainedResult(string opcode, int p2, int p3);
734+
private class ExplainedResult
735+
{
736+
public int addr = 0;
737+
public string opcode = "";
738+
public int p1 = 0;
739+
public int p2 = 0;
740+
public int p3 = 0;
741+
public string p4 = "";
742+
public int p5 = 0;
743+
}
695744
private record TableSelectResult(string tbl_name);
696745
public async Task<string[]> ResolveTables(string sql, object?[]? parameters = null, SQLWatchOptions? options = null)
697746
{
@@ -718,7 +767,6 @@ public async Task<string[]> ResolveTables(string sql, object?[]? parameters = nu
718767
resolvedTables.Add(POWERSYNC_TABLE_MATCH.Replace(table.tbl_name, ""));
719768
}
720769
}
721-
722770
return [.. resolvedTables];
723771
}
724772

PowerSync/PowerSync.Common/DB/Crud/UploadQueueStatus.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
namespace PowerSync.Common.DB.Crud;
22

3-
public class UploadQueueStats(int count, long? size = null)
3+
public class UploadQueueStats(long count, long? size = null)
44
{
5-
public int Count { get; set; } = count;
5+
public long Count { get; set; } = count;
66

77
public long? Size { get; set; } = size;
88

PowerSync/PowerSync.Common/DB/IDBAdapter.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ public interface IDBGetUtils
3131
{
3232
// Execute a read-only query and return results.
3333
Task<T[]> GetAll<T>(string sql, object?[]? parameters = null);
34+
Task<dynamic[]> GetAll(string sql, object?[]? parameters = null);
3435

3536
// Execute a read-only query and return the first result, or null if the ResultSet is empty.
3637
Task<T?> GetOptional<T>(string sql, object?[]? parameters = null);
38+
Task<dynamic?> GetOptional(string sql, object?[]? parameters = null);
3739

3840
// Execute a read-only query and return the first result, error if the ResultSet is empty.
3941
Task<T> Get<T>(string sql, object?[]? parameters = null);
42+
Task<dynamic> Get(string sql, object?[]? parameters = null);
4043
}
4144

4245
public interface ILockContext : IDBGetUtils

PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,22 +154,36 @@ public async Task<NonQueryResult> ExecuteBatch(string query, object?[][]? parame
154154
return await WriteTransaction((ctx) => ctx.ExecuteBatch(query, parameters));
155155
}
156156

157-
public async Task<T> Get<T>(string sql, object?[]? parameters = null)
157+
public async Task<T[]> GetAll<T>(string sql, object?[]? parameters = null)
158158
{
159-
return await ReadLock((ctx) => ctx.Get<T>(sql, parameters));
160-
;
159+
return await ReadLock((ctx) => ctx.GetAll<T>(sql, parameters));
161160
}
162161

163-
public async Task<T[]> GetAll<T>(string sql, object?[]? parameters = null)
162+
public async Task<dynamic[]> GetAll(string sql, object?[]? parameters = null)
164163
{
165-
return await ReadLock((ctx) => ctx.GetAll<T>(sql, parameters));
164+
return await ReadLock((ctx) => ctx.GetAll(sql, parameters));
166165
}
167166

168167
public async Task<T?> GetOptional<T>(string sql, object?[]? parameters = null)
169168
{
170169
return await ReadLock((ctx) => ctx.GetOptional<T>(sql, parameters));
171170
}
172171

172+
public async Task<dynamic?> GetOptional(string sql, object?[]? parameters = null)
173+
{
174+
return await ReadLock((ctx) => ctx.GetOptional(sql, parameters));
175+
}
176+
177+
public async Task<T> Get<T>(string sql, object?[]? parameters = null)
178+
{
179+
return await ReadLock((ctx) => ctx.Get<T>(sql, parameters));
180+
}
181+
182+
public async Task<dynamic> Get(string sql, object?[]? parameters = null)
183+
{
184+
return await ReadLock((ctx) => ctx.Get(sql, parameters));
185+
}
186+
173187
public async Task<T> ReadTransaction<T>(Func<ITransaction, Task<T>> fn, DBLockOptions? options = null)
174188
{
175189
return await ReadLock((ctx) => InternalTransaction(new MDSQLiteTransaction(readConnection!)!, fn));
@@ -311,18 +325,33 @@ public Task<NonQueryResult> ExecuteBatch(string query, object?[][]? parameters =
311325
return connection.ExecuteBatch(query, parameters);
312326
}
313327

314-
public Task<T> Get<T>(string sql, object?[]? parameters = null)
328+
public Task<T[]> GetAll<T>(string sql, object?[]? parameters = null)
315329
{
316-
return connection.Get<T>(sql, parameters);
330+
return connection.GetAll<T>(sql, parameters);
317331
}
318332

319-
public Task<T[]> GetAll<T>(string sql, object?[]? parameters = null)
333+
public Task<dynamic[]> GetAll(string sql, object?[]? parameters = null)
320334
{
321-
return connection.GetAll<T>(sql, parameters);
335+
return connection.GetAll(sql, parameters);
322336
}
323337

324338
public Task<T?> GetOptional<T>(string sql, object?[]? parameters = null)
325339
{
326340
return connection.GetOptional<T>(sql, parameters);
327341
}
342+
343+
public Task<dynamic?> GetOptional(string sql, object?[]? parameters = null)
344+
{
345+
return connection.GetOptional(sql, parameters);
346+
}
347+
348+
public Task<T> Get<T>(string sql, object?[]? parameters = null)
349+
{
350+
return connection.Get<T>(sql, parameters);
351+
}
352+
353+
public Task<dynamic> Get(string sql, object?[]? parameters = null)
354+
{
355+
return connection.Get(sql, parameters);
356+
}
328357
}

0 commit comments

Comments
 (0)