Skip to content

Enable state store foreign key cascades#3444

Closed
Hmbown wants to merge 1 commit into
mainfrom
codex/v0.8.65-state-foreign-keys
Closed

Enable state store foreign key cascades#3444
Hmbown wants to merge 1 commit into
mainfrom
codex/v0.8.65-state-foreign-keys

Conversation

@Hmbown

@Hmbown Hmbown commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Goal

Make thread deletion honor the state schema existing cascading child-row cleanup.

Changes

  • Enable SQLite foreign key enforcement on every StateStore connection.
  • Add a regression test that persists child rows for messages, dynamic tools, checkpoints, and thread goals, deletes the parent thread, and verifies the child rows are gone.

Verification

  • cargo fmt --all -- --check
  • cargo test -p codewhale-state --locked delete_thread_cascades_child_rows
  • cargo test -p codewhale-state --locked thread_goal_requires_existing_thread
  • cargo test -p codewhale-state --locked
  • git diff --check

Risks

Low. The schema already declares the cascade relationships; this makes SQLite enforce them consistently for each connection.

Turn on SQLite foreign key enforcement for every StateStore connection so delete_thread honors the schema's cascading child-row cleanup.

Add a regression test that persists messages, dynamic tools, checkpoints, and thread goals before deleting the parent thread.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@Hmbown

Hmbown commented Jun 23, 2026

Copy link
Copy Markdown
Owner Author

Closing this narrower branch because the same state-store foreign-key enforcement is already covered in the broader v0.8.65 local-state hardening PR #3433.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enables foreign key constraints on the SQLite state database connection and adds a test to verify that deleting a thread cascades to delete associated child rows. Feedback suggests using 'execute' instead of 'execute_batch' for the single 'PRAGMA' statement to improve efficiency, and validating the table name against an allowlist in the 'table_count' test helper to avoid unsafe SQL interpolation patterns.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/state/src/lib.rs
Comment on lines +310 to +311
conn.execute_batch("PRAGMA foreign_keys = ON;")
.context("failed to enable state db foreign keys")?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using execute_batch for a single, simple PRAGMA statement is less efficient because it parses the input as a batch of multiple SQL statements. It is more idiomatic and efficient to use execute with empty parameters.

Suggested change
conn.execute_batch("PRAGMA foreign_keys = ON;")
.context("failed to enable state db foreign keys")?;
conn.execute("PRAGMA foreign_keys = ON;", [])
.context("failed to enable state db foreign keys")?;

Comment thread crates/state/src/lib.rs
Comment on lines +1824 to +1829
fn table_count(store: &StateStore, table: &str, thread_id: &str) -> i64 {
let conn = store.conn().expect("open test connection");
let query = format!("SELECT COUNT(*) FROM {table} WHERE thread_id = ?1");
conn.query_row(&query, params![thread_id], |row| row.get(0))
.expect("count rows")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Constructing SQL queries via string interpolation (format!) is a dangerous pattern that can lead to SQL injection vulnerabilities. Although this is a test helper and currently only called with static string literals, it is highly recommended to validate the table parameter against an allowlist of expected table names to prevent unsafe patterns from spreading or being copied into production code.

Suggested change
fn table_count(store: &StateStore, table: &str, thread_id: &str) -> i64 {
let conn = store.conn().expect("open test connection");
let query = format!("SELECT COUNT(*) FROM {table} WHERE thread_id = ?1");
conn.query_row(&query, params![thread_id], |row| row.get(0))
.expect("count rows")
}
fn table_count(store: &StateStore, table: &str, thread_id: &str) -> i64 {
let allowed_tables = ["messages", "thread_dynamic_tools", "checkpoints", "thread_goals"];
assert!(allowed_tables.contains(&table), "invalid table name: {table}");
let conn = store.conn().expect("open test connection");
let query = format!("SELECT COUNT(*) FROM {table} WHERE thread_id = ?1");
conn.query_row(&query, params![thread_id], |row| row.get(0))
.expect("count rows")
}

@Hmbown Hmbown closed this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant