Skip to content

Commit 31e9356

Browse files
committed
Support custom schemas
1 parent df86223 commit 31e9356

7 files changed

Lines changed: 80 additions & 11 deletions

File tree

powersync/src/db/internal.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ use std::{
99
task::{Context, Poll},
1010
};
1111

12+
use crate::schema::SchemaOrCustom;
1213
use crate::{
1314
db::{
1415
core_extension::CoreExtensionVersion, pool::LeasedConnection, streams::SyncStreamTracker,
1516
},
1617
env::PowerSyncEnvironment,
1718
error::PowerSyncError,
18-
schema::Schema,
1919
sync::{MAX_OP_ID, coordinator::SyncCoordinator, status::SyncStatus, status::SyncStatusData},
2020
util::SharedFuture,
2121
};
@@ -28,7 +28,7 @@ pub struct InnerPowerSyncState {
2828
/// The schema passed to the database.
2929
///
3030
/// This is forwarded to the sync client for raw tables.
31-
pub schema: Arc<Schema>,
31+
pub schema: Arc<SchemaOrCustom>,
3232
/// A container for the current sync status.
3333
pub status: SyncStatus,
3434
/// A collection of currently-referenced sync stream subscriptions.
@@ -41,7 +41,11 @@ pub struct InnerPowerSyncState {
4141
}
4242

4343
impl InnerPowerSyncState {
44-
pub fn new(env: PowerSyncEnvironment, schema: Schema, sync: &Arc<SyncCoordinator>) -> Self {
44+
pub fn new(
45+
env: PowerSyncEnvironment,
46+
schema: SchemaOrCustom,
47+
sync: &Arc<SyncCoordinator>,
48+
) -> Self {
4549
Self {
4650
env,
4751
did_initialize: SharedFuture::new(),
@@ -73,7 +77,9 @@ impl InnerPowerSyncState {
7377
}
7478

7579
fn update_schema_internal(&self, conn: &Connection) -> Result<(), PowerSyncError> {
76-
self.schema.validate()?;
80+
if let SchemaOrCustom::Schema(schema) = self.schema.as_ref() {
81+
schema.validate()?;
82+
};
7783

7884
let serialized_schema = serde_json::to_string(&self.schema)?;
7985
conn.prepare("SELECT powersync_replace_schema(?)")?

powersync/src/db/mod.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::fmt::{Debug, Formatter};
44
use std::sync::Arc;
55

66
use crate::db::async_support::AsyncDatabaseTasks;
7+
use crate::schema::SchemaOrCustom;
78
use crate::sync::coordinator::SyncCoordinator;
89
use crate::{
910
CrudTransaction, SyncOptions,
@@ -13,7 +14,6 @@ use crate::{
1314
},
1415
env::PowerSyncEnvironment,
1516
error::PowerSyncError,
16-
schema::Schema,
1717
sync::{download::DownloadActor, status::SyncStatusData, upload::UploadActor},
1818
};
1919
use futures_lite::stream::{once, once_future};
@@ -47,11 +47,18 @@ impl PowerSyncDatabase {
4747
const PS_DATA_PREFIX: &'static str = "ps_data__";
4848
const PS_DATA_LOCAL_PREFIX: &'static str = "ps_data_local__";
4949

50-
pub fn new(env: PowerSyncEnvironment, schema: Schema) -> Self {
50+
/// Creates a new PowerSync database using the connection pool and HTTP client from the given
51+
/// [PowerSyncEnvironment].
52+
///
53+
/// On first use, the database will instantiate a PowerSync schema. Typically, a [Schema]
54+
/// instance would be passed for schemas. For cases where the schema is defined externally as a
55+
/// JSON object understood by the PowerSync SQLite core extension, it can also be passed as a
56+
/// [serde_json::value::RawValue] reference.
57+
pub fn new(env: PowerSyncEnvironment, schema: impl Into<SchemaOrCustom>) -> Self {
5158
let coordinator = Arc::new(SyncCoordinator::default());
5259

5360
Self {
54-
inner: Arc::new(InnerPowerSyncState::new(env, schema, &coordinator)),
61+
inner: Arc::new(InnerPowerSyncState::new(env, schema.into(), &coordinator)),
5562
sync: coordinator,
5663
}
5764
}

powersync/src/db/schema.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::collections::HashSet;
44
use serde::{Serialize, Serializer, ser::SerializeStruct};
55

66
use crate::error::PowerSyncError;
7+
use crate::util::SerializedJsonObject;
78

89
type SchemaString = Cow<'static, str>;
910

@@ -502,6 +503,30 @@ impl TrackPreviousValues {
502503
}
503504
}
504505

506+
#[derive(Serialize, Debug)]
507+
#[serde(untagged)]
508+
pub enum SchemaOrCustom {
509+
/// A schema created in Rust.
510+
Schema(Schema),
511+
/// A pre-serialized schema we just forward to the core extension.
512+
Custom(Box<SerializedJsonObject>),
513+
}
514+
515+
impl From<Schema> for SchemaOrCustom {
516+
fn from(value: Schema) -> Self {
517+
Self::Schema(value)
518+
}
519+
}
520+
521+
impl From<&serde_json::value::RawValue> for SchemaOrCustom {
522+
fn from(value: &serde_json::value::RawValue) -> Self {
523+
Self::Custom(unsafe {
524+
// Safety: The core extension will report an error if this isn't a JSON object.
525+
SerializedJsonObject::from_owned_value(value.to_owned())
526+
})
527+
}
528+
}
529+
505530
#[cfg(test)]
506531
mod test {
507532
use crate::schema::{Column, RawTable, RawTableSchema, Schema, Table, TrackPreviousValues};

powersync/src/sync/download/sync_iteration.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ use rusqlite::{
99
use serde::Serialize;
1010
use serde_json::value::RawValue;
1111

12+
use crate::schema::SchemaOrCustom;
1213
use crate::{
1314
SyncOptions,
1415
db::internal::InnerPowerSyncState,
1516
error::PowerSyncError,
16-
schema::Schema,
1717
sync::{
1818
download::http::sync_stream,
1919
instruction::{CloseSyncStream, Instruction, LogSeverity},
@@ -226,7 +226,7 @@ impl ToSql for PowerSyncControlArgument {
226226
#[derive(Debug, Serialize)]
227227
pub struct StartDownloadIteration {
228228
pub parameters: serde_json::Value,
229-
pub schema: Arc<Schema>,
229+
pub schema: Arc<SchemaOrCustom>,
230230
pub include_defaults: bool,
231231
pub active_streams: Vec<StreamKey>,
232232
}

powersync/src/util/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub struct SerializedJsonObject {
1919

2020
impl SerializedJsonObject {
2121
/// Safety: This must only be called for raw values that are known to be objects.
22-
unsafe fn from_owned_value(raw: Box<RawValue>) -> Box<Self> {
22+
pub unsafe fn from_owned_value(raw: Box<RawValue>) -> Box<Self> {
2323
unsafe {
2424
// Safety: Identical representation.
2525
std::mem::transmute(raw)

powersync/tests/crud_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ fn raw_table_crud_trigger() {
330330
let serialized_table = serde_json::to_string(&schema.raw_tables[0]).unwrap();
331331

332332
let test = DatabaseTest::new();
333-
let db = PowerSyncDatabase::new(test.in_memory(), Default::default());
333+
let db = PowerSyncDatabase::new(test.in_memory(), Schema::default());
334334

335335
{
336336
let mut writer = db.writer().await.unwrap();

powersync/tests/database_test.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ use std::sync::Arc;
22

33
use async_oneshot::oneshot;
44
use futures_lite::{StreamExt, future};
5+
use powersync::PowerSyncDatabase;
56
use powersync::error::PowerSyncError;
7+
use powersync::schema::{Column, Schema, Table};
68
use powersync_test_utils::{DatabaseTest, UserRow, execute, query_all};
79
use rusqlite::params;
10+
use serde_json::value::RawValue;
811
use serde_json::{Value, json};
912

1013
#[test]
@@ -232,3 +235,31 @@ fn test_watch_statement() {
232235
);
233236
});
234237
}
238+
239+
#[test]
240+
fn test_external_schema() {
241+
let test = DatabaseTest::new();
242+
let schema = {
243+
let mut original = Schema::default();
244+
original
245+
.tables
246+
.push(Table::create("users", vec![Column::text("name")], |_| {}));
247+
248+
let serialized = serde_json::to_string(&original).unwrap();
249+
let custom: Box<RawValue> = serde_json::from_str(&serialized).unwrap();
250+
custom
251+
};
252+
253+
// Passing a pre-serialized schema should still work correctly.
254+
let db = PowerSyncDatabase::new(test.in_memory(), schema.as_ref());
255+
future::block_on(execute(
256+
&db,
257+
"INSERT INTO users (id, name) VALUES (uuid(), ?)",
258+
params!["User"],
259+
));
260+
261+
future::block_on(async {
262+
let rows = query_all(&db, "SELECT name FROM users", params![]).await;
263+
assert_eq!(rows, json!([{"name": "User"}]));
264+
});
265+
}

0 commit comments

Comments
 (0)