Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@ databases:
analytics:
url: ${ANALYTICS_URL:postgres://localhost/analytics}
pool: { max: 5 }
history_max_entries: 200 # console query history caps, defaults shown
history_max_bytes: 262144 # 0 disables recording
```

`history_max_entries` / `history_max_bytes` cap the per-database console query history stored on the [`state`](../state) worker — whichever cap hits first, oldest entries are dropped. Applied live.

Set or replace the whole value:

```bash
Expand Down Expand Up @@ -225,7 +229,7 @@ Stored in the [`state`](https://github.com/iii-hq/workers/tree/main/state) worke
| `database::saveQuery` | Save a named query. Saving under an existing name replaces it. |
| `database::listSavedQueries` | Saved queries for a database, sorted by name. |
| `database::deleteSavedQuery` | Delete by id or by name. |
| `database::history` | Recent queries, newest first. Best effort — recording never blocks or fails a query, so this is a convenience rather than an audit log. For an audit trail bind `database::row-changed`. |
| `database::history` | Recent queries, newest first. Best effort — recording never blocks or fails a query, so this is a convenience rather than an audit log. For an audit trail bind `database::row-changed`. Stored history is capped per database (`history_max_entries` / `history_max_bytes`, defaults 200 entries / 256KB — oldest dropped first, `0` disables) and holds metadata only: SQL text (truncated to 4000 chars), verb, timing, row count — never result rows. An oversized or unreadable stored value is replaced wholesale on the next write. |

## Triggers

Expand Down
6 changes: 6 additions & 0 deletions database/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,9 @@ databases:
# orders:
# url: mysql://user:pass@localhost:3306/orders
# capture: native

# Console query history caps (per database, stored on the `state` worker).
# Whichever cap hits first, oldest entries are dropped; 0 disables recording.
# Defaults shown.
# history_max_entries: 200
# history_max_bytes: 262144
43 changes: 42 additions & 1 deletion database/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ pub struct WorkerConfig {
#[serde(default)]
#[schemars(schema_with = "databases_schema")]
pub databases: HashMap<String, DatabaseConfig>,
/// Maximum entries kept per database in the console query history
/// (`database::history`, stored on the `state` worker). Oldest entries
/// are dropped first. `0` disables history recording. Applied live.
#[serde(default = "default_history_max_entries")]
pub history_max_entries: usize,
/// Maximum JSON-serialized size in bytes of one database's stored query
/// history; oldest entries are dropped until the list fits. The hard
/// guard that keeps history from growing into a value large enough to
/// break the `state` worker's connection. `0` disables history
/// recording. Applied live.
#[serde(default = "default_history_max_bytes")]
pub history_max_bytes: usize,
}

fn worker_config_example() -> WorkerConfig {
Expand Down Expand Up @@ -211,6 +223,12 @@ fn default_idle_timeout_ms() -> u64 {
fn default_acquire_timeout_ms() -> u64 {
5_000
}
fn default_history_max_entries() -> usize {
200
}
fn default_history_max_bytes() -> usize {
262_144
}

impl Default for WorkerConfig {
fn default() -> Self {
Expand All @@ -231,6 +249,8 @@ impl WorkerConfig {
driver: DriverKind::default(),
},
)]),
history_max_entries: default_history_max_entries(),
history_max_bytes: default_history_max_bytes(),
})
.expect("built-in default config is valid")
}
Expand Down Expand Up @@ -280,7 +300,9 @@ impl WorkerConfig {
"acquire_timeout_ms": default_acquire_timeout_ms(),
}
}
}
},
"history_max_entries": default_history_max_entries(),
"history_max_bytes": default_history_max_bytes(),
}),
);
}
Expand Down Expand Up @@ -569,9 +591,28 @@ mod tests {
);
}

for field in ["history_max_entries", "history_max_bytes"] {
assert!(
schema["properties"][field].get("description").is_some(),
"missing description for {field}"
);
}

assert!(schema.get("example").is_some());
}

#[test]
fn history_caps_default_and_override() {
let d = cfg("databases:\n p:\n url: postgres://u@h/db\n");
assert_eq!(d.history_max_entries, 200);
assert_eq!(d.history_max_bytes, 262_144);

let c = cfg("databases:\n p:\n url: postgres://u@h/db\n\
history_max_entries: 25\nhistory_max_bytes: 4096\n");
assert_eq!(c.history_max_entries, 25);
assert_eq!(c.history_max_bytes, 4096);
}

/// Regenerate the e2e harness schema fixture when `WorkerConfig` changes:
/// `EXPORT_E2E_SCHEMA=1 cargo test -p database export_e2e_schema_fixture -- --ignored`
#[test]
Expand Down
1 change: 1 addition & 0 deletions database/src/handlers/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub async fn handle(state: &AppState, req: QueryReq) -> Result<QueryResp, String
if let Some(iii) = state.client() {
crate::handlers::saved::record(
iii.clone(),
state.config.clone(),
db,
&req.sql,
started.elapsed().as_millis() as u64,
Expand Down
Loading