Skip to content

Commit 819c3cf

Browse files
committed
Fix null values being stored as empty strings
Add subtype method on context
1 parent c90e2be commit 819c3cf

File tree

14 files changed

+83
-32
lines changed

14 files changed

+83
-32
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ inherits = "release"
2828
inherits = "wasm"
2929

3030
[workspace.package]
31-
version = "0.4.3"
31+
version = "0.4.4"
3232
edition = "2024"
3333
authors = ["JourneyApps"]
3434
keywords = ["sqlite", "powersync"]
@@ -37,5 +37,4 @@ homepage = "https://powersync.com"
3737
repository = "https://github.com/powersync-ja/powersync-sqlite-core"
3838

3939
[workspace.dependencies]
40-
sqlite_nostd = { path="./sqlite-rs-embedded/sqlite_nostd" }
41-
40+
sqlite_nostd = { path = "./sqlite-rs-embedded/sqlite_nostd" }

android/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ plugins {
1313
}
1414

1515
group = "com.powersync"
16-
version = "0.4.3"
16+
version = "0.4.4"
1717
description = "PowerSync Core SQLite Extension"
1818

1919
val localRepo = uri("build/repository/")

android/src/prefab/prefab.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"name": "powersync_sqlite_core",
33
"schema_version": 2,
44
"dependencies": [],
5-
"version": "0.4.3"
5+
"version": "0.4.4"
66
}

crates/core/src/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub const FULL_GIT_HASH: &'static str = env!("GIT_HASH");
99
// we're testing with the minimum version we claim to support.
1010
pub const MIN_SQLITE_VERSION_NUMBER: c_int = 3044000;
1111

12+
pub const SUBTYPE_JSON: u32 = 'J' as u32;
13+
1214
pub fn short_git_hash() -> &'static str {
1315
&FULL_GIT_HASH[..8]
1416
}

crates/core/src/diff.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@ use sqlite::ResultCode;
77
use sqlite_nostd as sqlite;
88
use sqlite_nostd::{Connection, Context, Value};
99

10-
use serde_json as json;
11-
10+
use crate::constants::SUBTYPE_JSON;
1211
use crate::create_sqlite_text_fn;
1312
use crate::error::PowerSyncError;
13+
use serde_json as json;
14+
use sqlite_nostd::bindings::SQLITE_RESULT_SUBTYPE;
1415

1516
fn powersync_diff_impl(
16-
_ctx: *mut sqlite::context,
17+
ctx: *mut sqlite::context,
1718
args: &[*mut sqlite::value],
1819
) -> Result<String, PowerSyncError> {
1920
let data_old = args[0].text();
2021
let data_new = args[1].text();
2122

23+
ctx.result_subtype(SUBTYPE_JSON);
2224
diff_objects(data_old, data_new)
2325
}
2426

@@ -66,7 +68,7 @@ pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
6668
db.create_function_v2(
6769
"powersync_diff",
6870
2,
69-
sqlite::UTF8 | sqlite::DETERMINISTIC,
71+
sqlite::UTF8 | sqlite::DETERMINISTIC | SQLITE_RESULT_SUBTYPE,
7072
None,
7173
Some(powersync_diff),
7274
None,

crates/core/src/json_merge.rs renamed to crates/core/src/json_util.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,34 @@ extern crate alloc;
33
use alloc::string::{String, ToString};
44
use core::ffi::c_int;
55

6+
use crate::constants::SUBTYPE_JSON;
7+
use crate::create_sqlite_text_fn;
8+
use crate::error::PowerSyncError;
69
use sqlite::ResultCode;
710
use sqlite_nostd as sqlite;
11+
use sqlite_nostd::bindings::{SQLITE_RESULT_SUBTYPE, SQLITE_SUBTYPE};
812
use sqlite_nostd::{Connection, Context, Value};
913

10-
use crate::create_sqlite_text_fn;
11-
use crate::error::PowerSyncError;
14+
extern "C" fn powersync_strip_subtype(
15+
ctx: *mut sqlite::context,
16+
argc: c_int,
17+
argv: *mut *mut sqlite::value,
18+
) {
19+
if argc != 1 {
20+
return;
21+
}
22+
23+
let arg = unsafe { *argv };
24+
ctx.result_value(arg);
25+
ctx.result_subtype(0);
26+
}
1227

1328
/// Given any number of JSON TEXT arguments, merge them into a single JSON object.
1429
///
1530
/// This assumes each argument is a valid JSON object, with no duplicate keys.
1631
/// No JSON parsing or validation is performed - this performs simple string concatenation.
1732
fn powersync_json_merge_impl(
18-
_ctx: *mut sqlite::context,
33+
ctx: *mut sqlite::context,
1934
args: &[*mut sqlite::value],
2035
) -> Result<String, PowerSyncError> {
2136
if args.is_empty() {
@@ -42,6 +57,7 @@ fn powersync_json_merge_impl(
4257

4358
// Close the outer brace
4459
result.push('}');
60+
ctx.result_subtype(SUBTYPE_JSON);
4561
Ok(result)
4662
}
4763

@@ -55,13 +71,24 @@ pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
5571
db.create_function_v2(
5672
"powersync_json_merge",
5773
-1,
58-
sqlite::UTF8 | sqlite::DETERMINISTIC,
74+
sqlite::UTF8 | sqlite::DETERMINISTIC | SQLITE_RESULT_SUBTYPE,
5975
None,
6076
Some(powersync_json_merge),
6177
None,
6278
None,
6379
None,
6480
)?;
6581

82+
db.create_function_v2(
83+
"powersync_strip_subtype",
84+
-1,
85+
sqlite::UTF8 | sqlite::DETERMINISTIC | SQLITE_SUBTYPE | SQLITE_RESULT_SUBTYPE,
86+
None,
87+
Some(powersync_strip_subtype),
88+
None,
89+
None,
90+
None,
91+
)?;
92+
6693
Ok(())
6794
}

crates/core/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ mod diff;
2323
mod error;
2424
mod ext;
2525
mod fix_data;
26-
mod json_merge;
26+
mod json_util;
2727
mod kv;
2828
mod macros;
2929
mod migrations;
@@ -73,7 +73,7 @@ fn init_extension(db: *mut sqlite::sqlite3) -> Result<(), PowerSyncError> {
7373
crate::uuid::register(db)?;
7474
crate::diff::register(db)?;
7575
crate::fix_data::register(db)?;
76-
crate::json_merge::register(db)?;
76+
crate::json_util::register(db)?;
7777
crate::view_admin::register(db)?;
7878
crate::checkpoint::register(db)?;
7979
crate::kv::register(db)?;

crates/core/src/sync/interface.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
use core::cell::RefCell;
22
use core::ffi::{c_int, c_void};
33

4+
use super::streaming_sync::SyncClient;
5+
use super::sync_status::DownloadSyncStatus;
6+
use crate::constants::SUBTYPE_JSON;
7+
use crate::error::PowerSyncError;
8+
use crate::schema::Schema;
9+
use crate::state::DatabaseState;
410
use alloc::borrow::Cow;
511
use alloc::boxed::Box;
612
use alloc::rc::Rc;
713
use alloc::sync::Arc;
814
use alloc::{string::String, vec::Vec};
915
use serde::{Deserialize, Serialize};
1016
use sqlite::{ResultCode, Value};
17+
use sqlite_nostd::bindings::SQLITE_RESULT_SUBTYPE;
1118
use sqlite_nostd::{self as sqlite, ColumnType};
1219
use sqlite_nostd::{Connection, Context};
1320

14-
use crate::error::PowerSyncError;
15-
use crate::schema::Schema;
16-
use crate::state::DatabaseState;
17-
18-
use super::streaming_sync::SyncClient;
19-
use super::sync_status::DownloadSyncStatus;
20-
2121
/// Payload provided by SDKs when requesting a sync iteration.
2222
#[derive(Default, Deserialize)]
2323
pub struct StartSyncStream {
@@ -186,6 +186,7 @@ pub fn register(db: *mut sqlite::sqlite3, state: Arc<DatabaseState>) -> Result<(
186186
let formatted =
187187
serde_json::to_string(&instructions).map_err(PowerSyncError::internal)?;
188188
ctx.result_text_transient(&formatted);
189+
ctx.result_subtype(SUBTYPE_JSON);
189190

190191
Ok(())
191192
})();
@@ -206,7 +207,7 @@ pub fn register(db: *mut sqlite::sqlite3, state: Arc<DatabaseState>) -> Result<(
206207
db.create_function_v2(
207208
"powersync_control",
208209
2,
209-
sqlite::UTF8 | sqlite::DIRECTONLY,
210+
sqlite::UTF8 | sqlite::DIRECTONLY | SQLITE_RESULT_SUBTYPE,
210211
Some(Box::into_raw(controller).cast()),
211212
Some(control),
212213
None,

crates/core/src/views.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ fn json_object_fragment<'a>(
421421
// function, meaning that it has a JSON subtype active - causing the json_object() call
422422
// we're about to emit to include it as a subobject instead of a string.
423423
"TEXT" | "text" => format!(
424-
"{:}, concat({:}.{:})",
424+
"{:}, powersync_strip_subtype({:}.{:})",
425425
QuotedString(name),
426426
prefix,
427427
quote_identifier(name)

0 commit comments

Comments
 (0)