diff --git a/migrations/2025-11-01-114839_posts/up.sql b/migrations/2025-11-01-114839_posts/up.sql index 6f4d4692..1d461692 100644 --- a/migrations/2025-11-01-114839_posts/up.sql +++ b/migrations/2025-11-01-114839_posts/up.sql @@ -289,17 +289,16 @@ COMMENT ON INDEX idx_posts_mentions IS 'Find posts mentioning a specific actor ( -- enforced at the application level. -- ============================================================================= --- Convert to hypertable using rkey column (time-based partitioning via tid_timestamp) +-- Convert to hypertable using rkey column (integer partitioning on TID values) +-- TID encoding: 4 hours = 14400 seconds = 14400000000 microseconds << 10 bits = 14745600000000 SELECT create_hypertable( 'posts', - 'rkey', - chunk_time_interval => (86400000000::bigint * 1000), -- 1 day in microseconds - time_partitioning_func => 'tid_timestamp', + by_range('rkey', 14745600000000), -- 4 hours in TID units migrate_data => true, if_not_exists => true ); -COMMENT ON TABLE posts IS 'TimescaleDB hypertable for posts (complete + stubs). Partitioned by rkey with 1-day chunks. Auto-compressed after 12 hours. No retention policy (full network scale).'; +COMMENT ON TABLE posts IS 'TimescaleDB hypertable for posts (complete + stubs). Partitioned by rkey with 4-hour chunks. Manual compression. No retention policy (full network scale).'; -- Enable compression ALTER TABLE posts SET ( @@ -308,12 +307,8 @@ ALTER TABLE posts SET ( timescaledb.compress_orderby = 'rkey DESC' ); --- Add aggressive compression policy (12 hours) -SELECT add_compression_policy( - 'posts', - compress_after => INTERVAL '12 hours', - if_not_exists => true -); +-- Note: Compression policy omitted - compress chunks manually when needed +-- Automatic policies don't work well with integer-based partitioning -- Enable chunk skipping for parent/root post queries SELECT enable_chunk_skipping('posts', 'parent_post_actor_id'); diff --git a/migrations/2025-11-01-114841_engagement/up.sql b/migrations/2025-11-01-114841_engagement/up.sql index 1cc26464..af4a34cf 100644 --- a/migrations/2025-11-01-114841_engagement/up.sql +++ b/migrations/2025-11-01-114841_engagement/up.sql @@ -127,6 +127,7 @@ COMMENT ON TABLE labeler_likes IS 'Likes on labeler services (rare, ~0.01% of li CREATE TABLE reposts ( actor_id INTEGER NOT NULL REFERENCES actors(id) ON DELETE CASCADE, rkey INT8 NOT NULL, + cid BYTEA NOT NULL, post_actor_id INTEGER NOT NULL, post_rkey BIGINT NOT NULL, via_repost_actor_id INTEGER, diff --git a/migrations/2025-11-01-114843_thread_moderation/up.sql b/migrations/2025-11-01-114843_thread_moderation/up.sql index dcf50ebe..0b728b81 100644 --- a/migrations/2025-11-01-114843_thread_moderation/up.sql +++ b/migrations/2025-11-01-114843_thread_moderation/up.sql @@ -31,8 +31,8 @@ CREATE TABLE threadgates ( actor_id INTEGER NOT NULL REFERENCES actors(id) ON DELETE CASCADE, rkey INT8 NOT NULL, cid BYTEA NOT NULL, - post_actor_id INTEGER, - post_rkey BIGINT, + post_actor_id INTEGER NOT NULL, + post_rkey BIGINT NOT NULL, allow threadgate_rule[], PRIMARY KEY (actor_id, rkey) ); @@ -62,8 +62,8 @@ CREATE TABLE postgates ( actor_id INTEGER NOT NULL REFERENCES actors(id) ON DELETE CASCADE, rkey INT8 NOT NULL, cid BYTEA NOT NULL, - post_actor_id INTEGER, - post_rkey BIGINT, + post_actor_id INTEGER NOT NULL, + post_rkey BIGINT NOT NULL, rules postgate_rule[] NOT NULL DEFAULT '{}', PRIMARY KEY (actor_id, rkey) ); diff --git a/parakeet-db/src/models.rs b/parakeet-db/src/models.rs index e71e2a72..2e768a7c 100644 --- a/parakeet-db/src/models.rs +++ b/parakeet-db/src/models.rs @@ -191,9 +191,9 @@ pub struct Postgate { pub actor_id: i32, // FK to actors (who created the postgate) pub rkey: i64, // TID as INT8 pub cid: Vec, // 32-byte CID digest + pub post_actor_id: i32, // PK part 1: Natural key reference to posts (NOT NULL - required) + pub post_rkey: i64, // PK part 2: Natural key reference to posts (NOT NULL - required) pub rules: array_helpers::PostgateRuleArray, // ENUM array: everybody | nobody - pub post_actor_id: i32, // PK part 1: Natural key reference to posts - pub post_rkey: i64, // PK part 2: Natural key reference to posts // Note: created_at derived from TID rkey via created_at() method } @@ -259,12 +259,12 @@ pub struct LabelerLike { pub struct Repost { pub actor_id: i32, // PK part 1: FK to actors pub rkey: i64, // PK part 2: TID as INT8 - pub status: RepostStatus, // ENUM: complete | stub - pub cid: Vec, // Real CID from database (reposts can be referenced via like.via field) + pub cid: Vec, // Real CID - needed for like.via_repost references pub post_actor_id: i32, // Natural key reference to posts (regular → hypertable FK allowed) - NOT NULL pub post_rkey: i64, // Natural key reference to posts - NOT NULL pub via_repost_actor_id: Option, // Natural key self-reference (quote-repost-of-repost) pub via_repost_rkey: Option, + pub status: RepostStatus, // ENUM: complete | stub // Note: created_at derived from TID rkey via created_at() method } @@ -483,8 +483,8 @@ pub struct Threadgate { pub rkey: i64, // TID as INT8 pub cid: Vec, // 32-byte CID digest pub allow: Option, // ENUM array: mention | following | list - pub post_actor_id: i32, // PK part 1: Natural key reference to posts - pub post_rkey: i64, // PK part 2: Natural key reference to posts + pub post_actor_id: i32, // PK part 1: Natural key reference to posts (NOT NULL - required) + pub post_rkey: i64, // PK part 2: Natural key reference to posts (NOT NULL - required) // Note: created_at derived from TID rkey via created_at() method } diff --git a/parakeet-db/src/schema.rs b/parakeet-db/src/schema.rs index ce50edd3..719361f7 100644 --- a/parakeet-db/src/schema.rs +++ b/parakeet-db/src/schema.rs @@ -509,8 +509,8 @@ diesel::table! { actor_id -> Int4, rkey -> Int8, cid -> Bytea, - post_actor_id -> Nullable, - post_rkey -> Nullable, + post_actor_id -> Int4, + post_rkey -> Int8, rules -> Array>, } } @@ -589,6 +589,7 @@ diesel::table! { reposts (actor_id, rkey) { actor_id -> Int4, rkey -> Int8, + cid -> Bytea, post_actor_id -> Int4, post_rkey -> Int8, via_repost_actor_id -> Nullable, @@ -690,8 +691,8 @@ diesel::table! { actor_id -> Int4, rkey -> Int8, cid -> Bytea, - post_actor_id -> Nullable, - post_rkey -> Nullable, + post_actor_id -> Int4, + post_rkey -> Int8, allow -> Nullable>>, } } diff --git a/parakeet/tests/sql/db_module_test.rs b/parakeet/tests/sql/db_module_test.rs index 410e7af9..1e464f2b 100644 --- a/parakeet/tests/sql/db_module_test.rs +++ b/parakeet/tests/sql/db_module_test.rs @@ -1137,26 +1137,12 @@ async fn test_get_reposted_by_with_cursor() -> eyre::Result<()> { // BOOKMARK TESTS // ============================================================================ -#[tokio::test] -async fn test_get_subject_id_for_bookmark_nonexistent() -> eyre::Result<()> { - common::ensure_test_db_ready().await; - let pool = common::test_diesel_pool(); - let mut conn = pool.get().await - .wrap_err("Failed to get connection")?; - - // Query for non-existent subject - let result = db::get_subject_id_for_bookmark( - &mut conn, - "posts", - "did:plc:nonexistent", - "3ktpjpifdsr2d" - ).await; - - // Should return an error (not found), but SQL itself should be valid - assert!(result.is_err(), "Should fail for non-existent subject"); - - Ok(()) -} +// NOTE: Disabled after natural key migration - bookmarks now use (actor_id, rkey) directly +// #[tokio::test] +// async fn test_get_subject_id_for_bookmark_nonexistent() -> eyre::Result<()> { +// // This function no longer exists - bookmarks use natural keys +// Ok(()) +// } #[tokio::test] async fn test_get_user_bookmarks_empty() -> eyre::Result<()> { @@ -1732,37 +1718,18 @@ async fn test_get_owner_starterpacks_with_cursor() -> eyre::Result<()> { // URI RECONSTRUCTION TESTS // ============================================================================ -#[tokio::test] -async fn test_get_post_uris_by_ids_empty() -> eyre::Result<()> { - common::ensure_test_db_ready().await; - let pool = common::test_diesel_pool(); - let mut conn = pool.get().await - .wrap_err("Failed to get connection")?; - - // Query with empty array - let result = db::get_post_uris_by_ids(&mut conn, &[]) - .await - .wrap_err("SQL query failed")?; - assert_eq!(result.len(), 0, "Should return empty hashmap for empty input"); - - Ok(()) -} - -#[tokio::test] -async fn test_get_post_uris_by_ids_nonexistent() -> eyre::Result<()> { - common::ensure_test_db_ready().await; - let pool = common::test_diesel_pool(); - let mut conn = pool.get().await - .wrap_err("Failed to get connection")?; - - // Query for non-existent post IDs - let result = db::get_post_uris_by_ids(&mut conn, &[99999, 88888]) - .await - .wrap_err("SQL query failed")?; - assert_eq!(result.len(), 0, "Should return empty hashmap for non-existent posts"); - - Ok(()) -} +// NOTE: Disabled after natural key migration - posts no longer have IDs +// #[tokio::test] +// async fn test_get_post_uris_by_ids_empty() -> eyre::Result<()> { +// // This function no longer exists - posts use natural keys (actor_id, rkey) +// Ok(()) +// } + +// #[tokio::test] +// async fn test_get_post_uris_by_ids_nonexistent() -> eyre::Result<()> { +// // This function no longer exists - posts use natural keys (actor_id, rkey) +// Ok(()) +// } #[tokio::test] async fn test_get_feedgen_uris_by_ids_empty() -> eyre::Result<()> {