Skip to content

Commit a9f07ac

Browse files
authored
Use microsecond precision for timestamps (#183)
1 parent dd65d17 commit a9f07ac

16 files changed

Lines changed: 353 additions & 153 deletions

crates/core/src/kv.rs

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ use powersync_sqlite_nostd as sqlite;
77
use powersync_sqlite_nostd::{Connection, Context};
88
use sqlite::ResultCode;
99

10-
use crate::create_sqlite_optional_text_fn;
1110
use crate::create_sqlite_text_fn;
1211
use crate::error::PowerSyncError;
13-
use crate::sync::BucketPriority;
1412

1513
fn powersync_client_id_impl(
1614
ctx: *mut sqlite::context,
@@ -39,30 +37,6 @@ create_sqlite_text_fn!(
3937
"powersync_client_id"
4038
);
4139

42-
fn powersync_last_synced_at_impl(
43-
ctx: *mut sqlite::context,
44-
_args: &[*mut sqlite::value],
45-
) -> Result<Option<String>, ResultCode> {
46-
let db = ctx.db_handle();
47-
48-
// language=SQLite
49-
let statement = db.prepare_v2("select last_synced_at from ps_sync_state where priority = ?")?;
50-
statement.bind_int(1, BucketPriority::SENTINEL.into())?;
51-
52-
if statement.step()? == ResultCode::ROW {
53-
let client_id = statement.column_text(0)?;
54-
Ok(Some(client_id.to_string()))
55-
} else {
56-
Ok(None)
57-
}
58-
}
59-
60-
create_sqlite_optional_text_fn!(
61-
powersync_last_synced_at,
62-
powersync_last_synced_at_impl,
63-
"powersync_last_synced_at"
64-
);
65-
6640
pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
6741
db.create_function_v2(
6842
"powersync_client_id",
@@ -74,16 +48,6 @@ pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
7448
None,
7549
None,
7650
)?;
77-
db.create_function_v2(
78-
"powersync_last_synced_at",
79-
0,
80-
sqlite::UTF8 | sqlite::DETERMINISTIC,
81-
None,
82-
Some(powersync_last_synced_at),
83-
None,
84-
None,
85-
None,
86-
)?;
8751

8852
Ok(())
8953
}

crates/core/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ mod pre_close_vtab;
2424
mod schema;
2525
mod state;
2626
mod sync;
27-
mod sync_local;
2827
mod update_hooks;
2928
mod utils;
3029
mod uuid;

crates/core/src/migrations.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ use alloc::format;
44
use alloc::string::{String, ToString};
55
use alloc::vec::Vec;
66

7-
use powersync_sqlite_nostd as sqlite;
7+
use powersync_sqlite_nostd::{self as sqlite, Destructor};
88
use powersync_sqlite_nostd::{Connection, Context};
9+
use serde::Serialize;
10+
use serde_json::json;
911
use sqlite::ResultCode;
1012

1113
use crate::error::{PSResult, PowerSyncError};
14+
use crate::ext::SafeManagedStmt;
1215
use crate::fix_data::apply_v035_fix;
1316
use crate::schema::inspection::ExistingView;
1417
use crate::sync::BucketPriority;
1518

16-
pub const LATEST_VERSION: i32 = 12;
19+
pub const LATEST_VERSION: i32 = 13;
1720

1821
pub fn powersync_migrate(
1922
ctx: *mut sqlite::context,
@@ -424,5 +427,53 @@ json_object('sql', 'DELETE FROM ps_migration WHERE id >= 12')
424427
local_db.exec_safe(stmt).into_db_result(local_db)?;
425428
}
426429

430+
if current_version < 13 && target_version >= 13 {
431+
let up = "\
432+
UPDATE ps_stream_subscriptions SET expires_at = expires_at * 1000000, last_synced_at = last_synced_at * 1000000;
433+
ALTER TABLE ps_sync_state RENAME TO ps_sync_state_old;
434+
CREATE TABLE ps_sync_state (
435+
priority INTEGER NOT NULL PRIMARY KEY,
436+
last_synced_at INTEGER NOT NULL
437+
) STRICT;
438+
INSERT INTO ps_sync_state (priority, last_synced_at)
439+
SELECT priority, unixepoch(last_synced_at) * 1000000 FROM ps_sync_state_old;
440+
DROP TABLE ps_sync_state_old;
441+
";
442+
local_db.exec_safe(up).into_db_result(local_db)?;
443+
444+
const DOWN_STATEMENTS: &[&str] = &[
445+
"UPDATE ps_stream_subscriptions SET expires_at = expires_at / 1000000, last_synced_at = last_synced_at / 1000000",
446+
"ALTER TABLE ps_sync_state RENAME TO ps_sync_state_new",
447+
"CREATE TABLE ps_sync_state (
448+
priority INTEGER NOT NULL PRIMARY KEY,
449+
last_synced_at TEXT NOT NULL
450+
) STRICT;",
451+
"INSERT INTO ps_sync_state (priority, last_synced_at) SELECT priority, datetime(last_synced_at / 1000000, 'unixepoch') FROM ps_sync_state_new",
452+
"DROP TABLE ps_sync_state_new",
453+
"DELETE FROM ps_migration WHERE id >= 13",
454+
];
455+
let down = serialize_down_statements(DOWN_STATEMENTS)?;
456+
let track_migration =
457+
local_db.prepare_v2("INSERT INTO ps_migration(id, down_migrations) VALUES (?, ?)")?;
458+
track_migration.bind_int(1, 13)?;
459+
track_migration.bind_text(2, &down, Destructor::STATIC)?;
460+
track_migration.exec()?;
461+
}
462+
427463
Ok(())
428464
}
465+
466+
fn serialize_down_statements(statements: &[&'static str]) -> Result<String, PowerSyncError> {
467+
struct DownStatements<'a>(&'a [&'static str]);
468+
469+
impl<'a> Serialize for DownStatements<'a> {
470+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
471+
where
472+
S: serde::Serializer,
473+
{
474+
serializer.collect_seq(self.0.iter().map(|s| json!({"sql": s})).into_iter())
475+
}
476+
}
477+
478+
serde_json::to_string(&DownStatements(statements)).map_err(PowerSyncError::internal)
479+
}

crates/core/src/state.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use powersync_sqlite_nostd::{self as sqlite, Context};
1212
use sqlite::{Connection, ResultCode};
1313

1414
use crate::{
15+
error::PowerSyncError,
1516
schema::{InferredSchemaCache, Schema},
16-
sync::SyncClient,
17+
sync::{SyncClient, storage_adapter::StorageAdapter},
1718
};
1819

1920
/// State that is shared for a SQLite database connection after the core extension has been
@@ -27,6 +28,7 @@ pub struct DatabaseState {
2728
schema: RefCell<Option<Schema>>,
2829
pending_updates: RefCell<BTreeSet<String>>,
2930
commited_updates: RefCell<BTreeSet<String>>,
31+
pub storage_adapter: RefCell<Option<Rc<StorageAdapter>>>,
3032
pub sync_client: RefCell<Option<SyncClient>>,
3133
/// Cached put and delete statements for raw tables, used by the `sync_local` step of the sync
3234
/// client.
@@ -97,10 +99,29 @@ impl DatabaseState {
9799
core::mem::replace(&mut *committed, Default::default())
98100
}
99101

102+
pub fn storage_adapter(
103+
&self,
104+
db: *mut sqlite::sqlite3,
105+
) -> Result<Rc<StorageAdapter>, PowerSyncError> {
106+
let mut adapter = self.storage_adapter.borrow_mut();
107+
Ok(match *adapter {
108+
Some(ref adapter) => {
109+
debug_assert!(db == adapter.db);
110+
adapter.clone()
111+
}
112+
None => {
113+
let created = Rc::new(StorageAdapter::new(db)?);
114+
*adapter = Some(created.clone());
115+
created
116+
}
117+
})
118+
}
119+
100120
/// Releases global resources (like prepared statements for the sync client) referenced from
101121
/// this state.
102122
pub fn release_resources(&self) {
103-
self.sync_client.replace(None);
123+
self.sync_client.take();
124+
self.storage_adapter.take();
104125
}
105126

106127
/// ## Safety

crates/core/src/sync/interface.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use crate::error::PowerSyncError;
99
use crate::schema::Schema;
1010
use crate::state::DatabaseState;
1111
use crate::sync::diagnostics::{DiagnosticOptions, DiagnosticsEvent};
12-
use crate::sync::storage_adapter::StorageAdapter;
1312
use crate::sync::subscriptions::{StreamKey, apply_subscriptions};
1413
use alloc::borrow::Cow;
1514
use alloc::boxed::Box;
@@ -267,9 +266,10 @@ pub fn register(db: *mut sqlite::sqlite3, state: Rc<DatabaseState>) -> Result<()
267266
}
268267
}),
269268
"subscriptions" => {
269+
let adapter = state.storage_adapter(db)?;
270270
let request = serde_json::from_str(payload.text())
271271
.map_err(PowerSyncError::as_argument_error)?;
272-
return apply_subscriptions(db, request);
272+
return apply_subscriptions(&adapter, request);
273273
}
274274
_ => {
275275
return Err(PowerSyncError::argument_error("Unknown operation"));
@@ -278,14 +278,15 @@ pub fn register(db: *mut sqlite::sqlite3, state: Rc<DatabaseState>) -> Result<()
278278

279279
let instructions = {
280280
let mut client = state.sync_client.borrow_mut();
281-
282-
client
283-
.get_or_insert_with(|| {
281+
let client = match *client {
282+
Some(ref mut client) => client,
283+
None => {
284284
let state = unsafe { DatabaseState::clone_from(ctx.user_data()) };
285-
286-
SyncClient::new(db, &state)
287-
})
288-
.push_event(event)
285+
let created = SyncClient::new(db, &state)?;
286+
client.insert(created)
287+
}
288+
};
289+
client.push_event(event)
289290
}?;
290291

291292
let formatted =
@@ -316,11 +317,11 @@ pub fn register(db: *mut sqlite::sqlite3, state: Rc<DatabaseState>) -> Result<()
316317
"powersync_offline_sync_status",
317318
0,
318319
sqlite::UTF8 | sqlite::DIRECTONLY | SQLITE_RESULT_SUBTYPE,
319-
None,
320+
Some(Rc::into_raw(state.clone()) as *mut c_void),
320321
Some(powersync_offline_sync_status),
321322
None,
322323
None,
323-
None,
324+
Some(DatabaseState::destroy_rc),
324325
)?;
325326

326327
Ok(())
@@ -330,7 +331,9 @@ fn powersync_offline_sync_status_impl(
330331
ctx: *mut sqlite::context,
331332
_args: &[*mut sqlite::value],
332333
) -> Result<String, PowerSyncError> {
333-
let adapter = StorageAdapter::new(ctx.db_handle())?;
334+
let db_state = unsafe { DatabaseState::from_context(&ctx) };
335+
let adapter = db_state.storage_adapter(ctx.db_handle())?;
336+
334337
let state = adapter.offline_sync_state()?;
335338
let serialized = serde_json::to_string(&state).map_err(PowerSyncError::internal)?;
336339

crates/core/src/sync/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod operations;
1111
pub mod storage_adapter;
1212
mod streaming_sync;
1313
mod subscriptions;
14+
mod sync_local;
1415
mod sync_status;
1516

1617
pub use bucket_priority::BucketPriority;

0 commit comments

Comments
 (0)