From 6a58028ce2567d4eec79a029b6d5a815e46fb3dd Mon Sep 17 00:00:00 2001 From: Trezy Date: Wed, 25 Mar 2026 14:10:15 -0700 Subject: [PATCH] fix: timestamp comparisons when using Postgres --- .../20260325000000_text_to_timestamptz.sql | 53 +++++++++++++++++++ src/labeler.rs | 12 +++-- src/lua/db_api.rs | 16 ++++-- 3 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 migrations/postgres/20260325000000_text_to_timestamptz.sql diff --git a/migrations/postgres/20260325000000_text_to_timestamptz.sql b/migrations/postgres/20260325000000_text_to_timestamptz.sql new file mode 100644 index 0000000..bad7f5a --- /dev/null +++ b/migrations/postgres/20260325000000_text_to_timestamptz.sql @@ -0,0 +1,53 @@ +-- Restore TIMESTAMPTZ columns that were converted to TEXT by uuid_to_text. +-- The stored RFC 3339 strings are valid TIMESTAMPTZ literals, so the cast is safe. + +-- users +ALTER TABLE users ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; +ALTER TABLE users ALTER COLUMN last_used_at TYPE TIMESTAMPTZ USING last_used_at::timestamptz; + +-- user_permissions +ALTER TABLE user_permissions ALTER COLUMN granted_at TYPE TIMESTAMPTZ USING granted_at::timestamptz; + +-- api_keys +ALTER TABLE api_keys ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; +ALTER TABLE api_keys ALTER COLUMN last_used_at TYPE TIMESTAMPTZ USING last_used_at::timestamptz; +ALTER TABLE api_keys ALTER COLUMN revoked_at TYPE TIMESTAMPTZ USING revoked_at::timestamptz; + +-- event_logs +ALTER TABLE event_logs ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; + +-- backfill_jobs +ALTER TABLE backfill_jobs ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; +ALTER TABLE backfill_jobs ALTER COLUMN started_at TYPE TIMESTAMPTZ USING started_at::timestamptz; +ALTER TABLE backfill_jobs ALTER COLUMN completed_at TYPE TIMESTAMPTZ USING completed_at::timestamptz; + +-- dead_letter_hooks +ALTER TABLE dead_letter_hooks ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; + +-- lexicons +ALTER TABLE lexicons ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; +ALTER TABLE lexicons ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at::timestamptz; +ALTER TABLE lexicons ALTER COLUMN last_fetched_at TYPE TIMESTAMPTZ USING last_fetched_at::timestamptz; + +-- records +ALTER TABLE records ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; +ALTER TABLE records ALTER COLUMN indexed_at TYPE TIMESTAMPTZ USING indexed_at::timestamptz; + +-- labels +ALTER TABLE labels ALTER COLUMN cts TYPE TIMESTAMPTZ USING cts::timestamptz; +ALTER TABLE labels ALTER COLUMN exp TYPE TIMESTAMPTZ USING exp::timestamptz; + +-- labeler_subscriptions +ALTER TABLE labeler_subscriptions ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; +ALTER TABLE labeler_subscriptions ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at::timestamptz; + +-- rate_limits +ALTER TABLE rate_limits ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; +ALTER TABLE rate_limits ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at::timestamptz; + +-- rate_limit_allowlist +ALTER TABLE rate_limit_allowlist ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; + +-- script_variables +ALTER TABLE script_variables ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::timestamptz; +ALTER TABLE script_variables ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at::timestamptz; diff --git a/src/labeler.rs b/src/labeler.rs index b8dc1e9..52fe0ab 100644 --- a/src/labeler.rs +++ b/src/labeler.rs @@ -469,10 +469,14 @@ pub async fn spawn_label_gc(db: sqlx::AnyPool, backend: DatabaseBackend) { let interval = tokio::time::Duration::from_secs(3600); // 1 hour // Build database-specific cleanup query for expired labels - let expired_sql = adapt_sql( - "DELETE FROM labels WHERE exp IS NOT NULL AND exp < datetime('now')", - backend, - ); + let expired_sql = match backend { + DatabaseBackend::Sqlite => { + "DELETE FROM labels WHERE exp IS NOT NULL AND exp < datetime('now')".to_string() + } + DatabaseBackend::Postgres => { + "DELETE FROM labels WHERE exp IS NOT NULL AND exp < NOW()".to_string() + } + }; loop { tokio::time::sleep(interval).await; diff --git a/src/lua/db_api.rs b/src/lua/db_api.rs index e9dff00..037364d 100644 --- a/src/lua/db_api.rs +++ b/src/lua/db_api.rs @@ -534,14 +534,12 @@ pub fn register_db_api(lua: &Lua, state: Arc) -> LuaResult<()> { db_table.set("backlinks", backlinks_fn)?; // db.raw(sql, params?) -> rows[] - let state_raw = state; + let state_raw = state.clone(); let raw_fn = lua.create_async_function(move |lua, (sql, params): (String, Option)| { let state = state_raw.clone(); async move { - let backend = state.db_backend; - let adapted = adapt_sql(&sql, backend); - let mut query = sqlx::query(&adapted); + let mut query = sqlx::query(&sql); if let Some(ref params_table) = params { for value in params_table.sequence_values::() { let value = value?; @@ -600,6 +598,16 @@ pub fn register_db_api(lua: &Lua, state: Arc) -> LuaResult<()> { })?; db_table.set("raw", raw_fn)?; + // db.backend() -> "sqlite" | "postgres" + let backend = state.db_backend; + let backend_fn = lua.create_function(move |_, ()| { + Ok(match backend { + DatabaseBackend::Sqlite => "sqlite", + DatabaseBackend::Postgres => "postgres", + }) + })?; + db_table.set("backend", backend_fn)?; + lua.globals().set("db", db_table)?; Ok(()) } -- 2.51.2