Postgres, step 1: every query numbers its placeholders $1, $2, ... 5d90e0e045e2
The hub's 507 SQL strings now use Postgres's placeholder form, which SQLite (through sqlx) accepts too, so one text serves both databases (oxygenhub#211). Converted by a script that leaves quoted SQL strings, format braces and regexes alone; SQLite's numbered ?N became $N; the three statements that build their placeholders (test case filters, backup restore inserts and updates) and the inbox's assembled query number them as they go. No behaviour change: 247 tests on SQLite. Refs #211
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
Claude Code <[email protected]> · delegate for changes, until 2026-11-13 19:00Z · key ea5f78160088
- When
- · 2026-10-16 11:15 UTC
- Signature
- ✔ signed by Claude Code
- Hash
5d90e0e045e2765ec3216fdc770fe79882c0f8460dc4fe9cf573eadecec94b14- Depends on
- 0024ccb1b050 070a7b64b368 07b1a5bbf481 0b203bbf7cc3 0b20f1ec4007 0e04dfc20211 101740454617 10708ab8f54d 1553bfc9ae53 180dc43ad0e0 1870e4fa9808 18e381ea88c2 19bb31ff1f5c 1ac84c62ba53 1e8a6e8bb91b 1edac4b68c6d 1f78655a0466 20b8fe85888b 25ab8c73514a 2a0c09c09716 2a83f5303458 2fb70d019851 34beb1a7e376 36e5a9c15a84 3776b70d9b17 3d5c825aebba 3f4b0ea6eabf 3ff52534f69b 40dd3f86379a 417184c73f4e 42fa95540468 44a3d2ebaddd 4597750166e7 462f88d161ce 47e374020b59 4a4400c5812d 4ac01a732552 4d2e44d79b03 501d8b268dd4 554ccacfc123 5d54cf746e00 5dead1f4f26d 5ea4872f23e8 5ef6cf72cd19 60f11b71a740 617c2840446c 62d6e969f180 6a1780558353 6ca0b971ff07 736f6813543d 73ef9f767132 745d225de344 74be97962ed8 757195ad7a5d 7829b7c7bb59 7974adb0be66 7b9f4570daf5 7d2eb590051f 7edaff01d506 7f5f6c056d8f 83940d53664f 89e89836cae7 8ddebaa7e856 9008bf6c2b8c 90326c9601f9 92a797b20912 93501da2a162 941abdd6bb4c 9756c67d3aec 9aaa88b2d9c8 a0d4bbfbedd1 a160b98fb922 a36bab6b9ffc a3c8288b0141 a7e6e8e6094c b1e92a6233ee b644e1757711 b64e2537d702 b6b7e4cf6b24 bff9106edcba c8df33a6829f c987c86513df c98935537c57 cb71e18eb6f9 cc19697f8871 cc1cd197df1d d0fbea2eb46e d23464c0ac60 d3b048b27277 d7d182467382 d943160dafeb de505df5cf8d df5c69cbd047 df71c7892d27 e1ec9fbf538e e71583501b1d e71846aa07b9 e8e7158bbc73 e8fa53694f68 ea85398a51bc eb0c44a9ad37 ed89fae5180b eef677c48cd1 ef20fdc14e8b efd29a0033e8 f4d6ef418a96 f677d6270298 fd3db644df22 ff6569530173 ffb67a81188c
- Depended on by
- 40a43921ef42 0bc95135954d 951aed50c2f5 1000b40a2629 8501fd5815b9 e4203e141e90 ab4df6aae179 439ecd8708a1 b349e15a3eab 367f34b8e590 166f6e9bf4f1 a9f1c0bf403a e97500b70f9a 05f1a85a2842 53a0fb2e4ac6 107eaba52c9d 55673c650986 f0f6c2d96b1c 3b1bc27fe805 97b18869acf5 ea14c4ec5c3f d1bf2d698777
- In bookmarks
- api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
- In tags
- v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
- Edits
- 532 atoms, 0 tree operations, 32 files
Browse the tree at this change
modified crates/hub/src/api/mod.rs +1 −1
| … | 1400 unchanged lines |
||
| 1401 | 1401 | Some(id) => db::issues::milestone(&app.db, id).await?.map(|m| m.title), |
|
| 1402 | 1402 | None => None, |
|
| 1403 | 1403 | }; |
|
| 1404 | - | let comments: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM issue_comments WHERE issue_id = ?").bind(i.id).fetch_one(&app.db).await?; |
|
| 1404 | + | let comments: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM issue_comments WHERE issue_id = $1").bind(i.id).fetch_one(&app.db).await?; |
|
| 1405 | 1405 | Ok(Issue { |
|
| 1406 | 1406 | number: i.number, |
|
| 1407 | 1407 | title: i.title.clone(), |
|
| … | 1273 unchanged lines |
modified crates/hub/src/apt.rs +1 −1
| … | 132 unchanged lines |
||
| 133 | 133 | } |
|
| 134 | 134 | let rows: Vec<(i64, String, String)> = sqlx::query_as( |
|
| 135 | 135 | "SELECT a.id, a.name, a.hash FROM release_assets a JOIN releases r ON r.id = a.release_id \ |
|
| 136 | - | WHERE r.repo_id = ? AND a.name LIKE '%.deb' ORDER BY a.id", |
|
| 136 | + | WHERE r.repo_id = $1 AND a.name LIKE '%.deb' ORDER BY a.id", |
|
| 137 | 137 | ) |
|
| 138 | 138 | .bind(row.id) |
|
| 139 | 139 | .fetch_all(&app.db) |
|
| … | 280 unchanged lines |
modified crates/hub/src/assistants.rs +26 −22
| … | 170 unchanged lines |
||
| 171 | 171 | // ------------------------------------------------------------- storage |
|
| 172 | 172 | ||
| 173 | 173 | pub async fn list(pool: &Pool, scope: Scope) -> sqlx::Result<Vec<Assistant>> { |
|
| 174 | - | sqlx::query_as("SELECT * FROM assistants WHERE scope = ? AND scope_id = ? ORDER BY name") |
|
| 174 | + | sqlx::query_as("SELECT * FROM assistants WHERE scope = $1 AND scope_id = $2 ORDER BY name") |
|
| 175 | 175 | .bind(scope.kind()) |
|
| 176 | 176 | .bind(scope.id()) |
|
| 177 | 177 | .fetch_all(pool) |
|
| … | 1 unchanged lines |
||
| 179 | 179 | } |
|
| 180 | 180 | ||
| 181 | 181 | pub async fn get(pool: &Pool, id: i64) -> sqlx::Result<Option<Assistant>> { |
|
| 182 | - | sqlx::query_as("SELECT * FROM assistants WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 182 | + | sqlx::query_as("SELECT * FROM assistants WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 183 | 183 | } |
|
| 184 | 184 | ||
| 185 | 185 | /// Add an assistant; `None` when the level already has one of that name. |
|
| … | 1 unchanged lines |
||
| 187 | 187 | let now = now_ms(); |
|
| 188 | 188 | let r = sqlx::query_as( |
|
| 189 | 189 | "INSERT INTO assistants (scope, scope_id, name, endpoint, api, model, headers, token, enabled, budget, created_ms, updated_ms) \ |
|
| 190 | - | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING RETURNING *", |
|
| 190 | + | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ON CONFLICT DO NOTHING RETURNING *", |
|
| 191 | 191 | ) |
|
| 192 | 192 | .bind(scope.kind()) |
|
| 193 | 193 | .bind(scope.id()) |
|
| … | 17 unchanged lines |
||
| 211 | 211 | pub async fn update(pool: &Pool, id: i64, f: &Fields, token: Option<&[u8]>) -> sqlx::Result<bool> { |
|
| 212 | 212 | let r = match token { |
|
| 213 | 213 | Some(t) => sqlx::query( |
|
| 214 | - | "UPDATE assistants SET name = ?, endpoint = ?, api = ?, model = ?, headers = ?, enabled = ?, budget = ?, token = ?, updated_ms = ? \ |
|
| 215 | - | WHERE id = ? AND NOT EXISTS (SELECT 1 FROM assistants o WHERE o.scope = assistants.scope AND o.scope_id = assistants.scope_id AND o.name = ? AND o.id <> ?)", |
|
| 214 | + | "UPDATE assistants SET name = $1, endpoint = $2, api = $3, model = $4, headers = $5, enabled = $6, budget = $7, token = $8, updated_ms = $9 \ |
|
| 215 | + | WHERE id = $10 AND NOT EXISTS (SELECT 1 FROM assistants o WHERE o.scope = assistants.scope AND o.scope_id = assistants.scope_id AND o.name = $11 AND o.id <> $12)", |
|
| 216 | 216 | ) |
|
| 217 | 217 | .bind(&f.name) |
|
| 218 | 218 | .bind(&f.endpoint) |
|
| … | 10 unchanged lines |
||
| 229 | 229 | .execute(pool) |
|
| 230 | 230 | .await?, |
|
| 231 | 231 | None => sqlx::query( |
|
| 232 | - | "UPDATE assistants SET name = ?, endpoint = ?, api = ?, model = ?, headers = ?, enabled = ?, budget = ?, updated_ms = ? \ |
|
| 233 | - | WHERE id = ? AND NOT EXISTS (SELECT 1 FROM assistants o WHERE o.scope = assistants.scope AND o.scope_id = assistants.scope_id AND o.name = ? AND o.id <> ?)", |
|
| 232 | + | "UPDATE assistants SET name = $1, endpoint = $2, api = $3, model = $4, headers = $5, enabled = $6, budget = $7, updated_ms = $8 \ |
|
| 233 | + | WHERE id = $9 AND NOT EXISTS (SELECT 1 FROM assistants o WHERE o.scope = assistants.scope AND o.scope_id = assistants.scope_id AND o.name = $10 AND o.id <> $11)", |
|
| 234 | 234 | ) |
|
| 235 | 235 | .bind(&f.name) |
|
| 236 | 236 | .bind(&f.endpoint) |
|
| … | 23 unchanged lines |
||
| 260 | 260 | match scope { |
|
| 261 | 261 | Scope::Site => { |
|
| 262 | 262 | for table in ["proposal_reviews", "proposal_comments"] { |
|
| 263 | - | sqlx::query(&format!("UPDATE {table} SET via = ? WHERE via = ?")).bind(new).bind(old).execute(pool).await?; |
|
| 263 | + | sqlx::query(&format!("UPDATE {table} SET via = $1 WHERE via = $2")).bind(new).bind(old).execute(pool).await?; |
|
| 264 | 264 | } |
|
| 265 | - | sqlx::query("UPDATE proposal_events SET event = replace(event, ?, ?) WHERE event LIKE ?") |
|
| 265 | + | sqlx::query("UPDATE proposal_events SET event = replace(event, $1, $2) WHERE event LIKE $3") |
|
| 266 | 266 | .bind(old) |
|
| 267 | 267 | .bind(new) |
|
| 268 | 268 | .bind(&like) |
|
| … | 1 unchanged lines |
||
| 270 | 270 | .await?; |
|
| 271 | 271 | } |
|
| 272 | 272 | Scope::Org(_) => { |
|
| 273 | - | let in_org = "proposal_id IN (SELECT p.id FROM proposals p JOIN repositories r ON r.id = p.repo_id WHERE r.owner = ?)"; |
|
| 273 | + | let in_org = "proposal_id IN (SELECT p.id FROM proposals p JOIN repositories r ON r.id = p.repo_id WHERE r.owner = $1)"; |
|
| 274 | 274 | for table in ["proposal_reviews", "proposal_comments"] { |
|
| 275 | - | sqlx::query(&format!("UPDATE {table} SET via = ? WHERE via = ? AND {in_org}")) |
|
| 275 | + | sqlx::query(&format!("UPDATE {table} SET via = $1 WHERE via = $2 AND {in_org}")) |
|
| 276 | 276 | .bind(new) |
|
| 277 | 277 | .bind(old) |
|
| 278 | 278 | .bind(owner_hint) |
|
| 279 | 279 | .execute(pool) |
|
| 280 | 280 | .await?; |
|
| 281 | 281 | } |
|
| 282 | - | sqlx::query(&format!("UPDATE proposal_events SET event = replace(event, ?, ?) WHERE event LIKE ? AND {in_org}")) |
|
| 282 | + | sqlx::query(&format!("UPDATE proposal_events SET event = replace(event, $1, $2) WHERE event LIKE $3 AND {in_org}")) |
|
| 283 | 283 | .bind(old) |
|
| 284 | 284 | .bind(new) |
|
| 285 | 285 | .bind(&like) |
|
| … | 2 unchanged lines |
||
| 288 | 288 | .await?; |
|
| 289 | 289 | } |
|
| 290 | 290 | Scope::Repo(id) => { |
|
| 291 | - | let in_repo = "proposal_id IN (SELECT id FROM proposals WHERE repo_id = ?)"; |
|
| 291 | + | let in_repo = "proposal_id IN (SELECT id FROM proposals WHERE repo_id = $1)"; |
|
| 292 | 292 | for table in ["proposal_reviews", "proposal_comments"] { |
|
| 293 | - | sqlx::query(&format!("UPDATE {table} SET via = ? WHERE via = ? AND {in_repo}")) |
|
| 293 | + | sqlx::query(&format!("UPDATE {table} SET via = $1 WHERE via = $2 AND {in_repo}")) |
|
| 294 | 294 | .bind(new) |
|
| 295 | 295 | .bind(old) |
|
| 296 | 296 | .bind(id) |
|
| 297 | 297 | .execute(pool) |
|
| 298 | 298 | .await?; |
|
| 299 | 299 | } |
|
| 300 | - | sqlx::query(&format!("UPDATE proposal_events SET event = replace(event, ?, ?) WHERE event LIKE ? AND {in_repo}")) |
|
| 300 | + | sqlx::query(&format!("UPDATE proposal_events SET event = replace(event, $1, $2) WHERE event LIKE $3 AND {in_repo}")) |
|
| 301 | 301 | .bind(old) |
|
| 302 | 302 | .bind(new) |
|
| 303 | 303 | .bind(&like) |
|
| … | 6 unchanged lines |
||
| 310 | 310 | } |
|
| 311 | 311 | ||
| 312 | 312 | pub async fn set_enabled(pool: &Pool, id: i64, enabled: bool) -> sqlx::Result<()> { |
|
| 313 | - | sqlx::query("UPDATE assistants SET enabled = ?, updated_ms = ? WHERE id = ?") |
|
| 313 | + | sqlx::query("UPDATE assistants SET enabled = $1, updated_ms = $2 WHERE id = $3") |
|
| 314 | 314 | .bind(enabled) |
|
| 315 | 315 | .bind(now_ms()) |
|
| 316 | 316 | .bind(id) |
|
| … | 3 unchanged lines |
||
| 320 | 320 | } |
|
| 321 | 321 | ||
| 322 | 322 | pub async fn delete(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 323 | - | sqlx::query("DELETE FROM assistants WHERE id = ?").bind(id).execute(pool).await?; |
|
| 323 | + | sqlx::query("DELETE FROM assistants WHERE id = $1").bind(id).execute(pool).await?; |
|
| 324 | 324 | Ok(()) |
|
| 325 | 325 | } |
|
| 326 | 326 | ||
| 327 | 327 | /// Forget a level's assistants and prompt, when the organisation or |
|
| 328 | 328 | /// repository goes. |
|
| 329 | 329 | pub async fn delete_scope(pool: &Pool, scope: Scope) -> sqlx::Result<()> { |
|
| 330 | - | sqlx::query("DELETE FROM assistants WHERE scope = ? AND scope_id = ?").bind(scope.kind()).bind(scope.id()).execute(pool).await?; |
|
| 331 | - | sqlx::query("DELETE FROM assistant_prompts WHERE scope = ? AND scope_id = ?").bind(scope.kind()).bind(scope.id()).execute(pool).await?; |
|
| 330 | + | sqlx::query("DELETE FROM assistants WHERE scope = $1 AND scope_id = $2").bind(scope.kind()).bind(scope.id()).execute(pool).await?; |
|
| 331 | + | sqlx::query("DELETE FROM assistant_prompts WHERE scope = $1 AND scope_id = $2") |
|
| 332 | + | .bind(scope.kind()) |
|
| 333 | + | .bind(scope.id()) |
|
| 334 | + | .execute(pool) |
|
| 335 | + | .await?; |
|
| 332 | 336 | Ok(()) |
|
| 333 | 337 | } |
|
| 334 | 338 | ||
| 335 | 339 | /// The prompt a level adds, empty when none. |
|
| 336 | 340 | pub async fn prompt(pool: &Pool, scope: Scope) -> sqlx::Result<String> { |
|
| 337 | - | Ok(sqlx::query_scalar("SELECT prompt FROM assistant_prompts WHERE scope = ? AND scope_id = ?") |
|
| 341 | + | Ok(sqlx::query_scalar("SELECT prompt FROM assistant_prompts WHERE scope = $1 AND scope_id = $2") |
|
| 338 | 342 | .bind(scope.kind()) |
|
| 339 | 343 | .bind(scope.id()) |
|
| 340 | 344 | .fetch_optional(pool) |
|
| … | 3 unchanged lines |
||
| 344 | 348 | ||
| 345 | 349 | pub async fn set_prompt(pool: &Pool, scope: Scope, text: &str) -> sqlx::Result<()> { |
|
| 346 | 350 | if text.trim().is_empty() { |
|
| 347 | - | sqlx::query("DELETE FROM assistant_prompts WHERE scope = ? AND scope_id = ?") |
|
| 351 | + | sqlx::query("DELETE FROM assistant_prompts WHERE scope = $1 AND scope_id = $2") |
|
| 348 | 352 | .bind(scope.kind()) |
|
| 349 | 353 | .bind(scope.id()) |
|
| 350 | 354 | .execute(pool) |
|
| 351 | 355 | .await?; |
|
| 352 | 356 | } else { |
|
| 353 | 357 | sqlx::query( |
|
| 354 | - | "INSERT INTO assistant_prompts (scope, scope_id, prompt, updated_ms) VALUES (?, ?, ?, ?) \ |
|
| 358 | + | "INSERT INTO assistant_prompts (scope, scope_id, prompt, updated_ms) VALUES ($1, $2, $3, $4) \ |
|
| 355 | 359 | ON CONFLICT (scope, scope_id) DO UPDATE SET prompt = excluded.prompt, updated_ms = excluded.updated_ms", |
|
| 356 | 360 | ) |
|
| 357 | 361 | .bind(scope.kind()) |
|
| … | 685 unchanged lines |
modified crates/hub/src/backups.rs +43 −39
| … | 86 unchanged lines |
||
| 87 | 87 | } |
|
| 88 | 88 | ||
| 89 | 89 | pub async fn setting(pool: &Pool, owner: &str) -> sqlx::Result<Option<Setting>> { |
|
| 90 | - | sqlx::query_as("SELECT * FROM backup_settings WHERE owner = ?").bind(owner).fetch_optional(pool).await |
|
| 90 | + | sqlx::query_as("SELECT * FROM backup_settings WHERE owner = $1").bind(owner).fetch_optional(pool).await |
|
| 91 | 91 | } |
|
| 92 | 92 | ||
| 93 | 93 | pub async fn save(pool: &Pool, s: &Setting) -> sqlx::Result<()> { |
|
| 94 | 94 | sqlx::query( |
|
| 95 | 95 | "INSERT INTO backup_settings (owner, kind, every, destination, target, prefix, provider, keep_days, updated_ms, region) \ |
|
| 96 | - | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(owner) DO UPDATE SET kind = excluded.kind, every = excluded.every, \ |
|
| 96 | + | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT(owner) DO UPDATE SET kind = excluded.kind, every = excluded.every, \ |
|
| 97 | 97 | destination = excluded.destination, target = excluded.target, prefix = excluded.prefix, provider = excluded.provider, \ |
|
| 98 | 98 | keep_days = excluded.keep_days, updated_ms = excluded.updated_ms, region = excluded.region", |
|
| 99 | 99 | ) |
|
| … | 13 unchanged lines |
||
| 113 | 113 | } |
|
| 114 | 114 | ||
| 115 | 115 | pub async fn runs(pool: &Pool, owner: &str, limit: i64) -> sqlx::Result<Vec<Run>> { |
|
| 116 | - | sqlx::query_as("SELECT * FROM backup_runs WHERE owner = ? ORDER BY id DESC LIMIT ?").bind(owner).bind(limit).fetch_all(pool).await |
|
| 116 | + | sqlx::query_as("SELECT * FROM backup_runs WHERE owner = $1 ORDER BY id DESC LIMIT $2").bind(owner).bind(limit).fetch_all(pool).await |
|
| 117 | 117 | } |
|
| 118 | 118 | ||
| 119 | 119 | /// What a repository may choose about its backup (§5): with its owner's, |
|
| … | 64 unchanged lines |
||
| 184 | 184 | for s in settings { |
|
| 185 | 185 | let last = runs(pool, &s.owner, 1).await?.into_iter().next(); |
|
| 186 | 186 | let failed_day: i64 = |
|
| 187 | - | sqlx::query_scalar("SELECT COUNT(*) FROM backup_runs WHERE owner = ? AND status = 'failed' AND started_ms >= ?") |
|
| 187 | + | sqlx::query_scalar("SELECT COUNT(*) FROM backup_runs WHERE owner = $1 AND status = 'failed' AND started_ms >= $2") |
|
| 188 | 188 | .bind(&s.owner) |
|
| 189 | 189 | .bind(day_ago) |
|
| 190 | 190 | .fetch_one(pool) |
|
| … | 860 unchanged lines |
||
| 1051 | 1051 | } |
|
| 1052 | 1052 | let mut out = serde_json::Map::new(); |
|
| 1053 | 1053 | for (kind, sql) in [ |
|
| 1054 | - | ("accounts", "SELECT id, username FROM accounts WHERE id = ?"), |
|
| 1055 | - | ("organisations", "SELECT id, name FROM organisations WHERE id = ?"), |
|
| 1056 | - | ("teams", "SELECT t.id, o.name || '/' || t.name FROM teams t JOIN organisations o ON o.id = t.org_id WHERE t.id = ?"), |
|
| 1054 | + | ("accounts", "SELECT id, username FROM accounts WHERE id = $1"), |
|
| 1055 | + | ("organisations", "SELECT id, name FROM organisations WHERE id = $1"), |
|
| 1056 | + | ("teams", "SELECT t.id, o.name || '/' || t.name FROM teams t JOIN organisations o ON o.id = t.org_id WHERE t.id = $1"), |
|
| 1057 | 1057 | ] { |
|
| 1058 | 1058 | let mut m = serde_json::Map::new(); |
|
| 1059 | 1059 | for id in ids.get(kind).into_iter().flatten() { |
|
| … | 9 unchanged lines |
||
| 1069 | 1069 | /// The id `name` has on this hub as one of `_names`' kinds. |
|
| 1070 | 1070 | async fn id_by_name(pool: &Pool, kind: &str, name: &str) -> Result<Option<i64>> { |
|
| 1071 | 1071 | Ok(match kind { |
|
| 1072 | - | "accounts" => sqlx::query_scalar("SELECT id FROM accounts WHERE username = ?").bind(name).fetch_optional(pool).await?, |
|
| 1073 | - | "organisations" => sqlx::query_scalar("SELECT id FROM organisations WHERE name = ?").bind(name).fetch_optional(pool).await?, |
|
| 1072 | + | "accounts" => sqlx::query_scalar("SELECT id FROM accounts WHERE username = $1").bind(name).fetch_optional(pool).await?, |
|
| 1073 | + | "organisations" => sqlx::query_scalar("SELECT id FROM organisations WHERE name = $1").bind(name).fetch_optional(pool).await?, |
|
| 1074 | 1074 | "teams" => match name.split_once('/') { |
|
| 1075 | 1075 | Some((org, team)) => { |
|
| 1076 | - | sqlx::query_scalar("SELECT t.id FROM teams t JOIN organisations o ON o.id = t.org_id WHERE o.name = ? AND t.name = ?") |
|
| 1076 | + | sqlx::query_scalar("SELECT t.id FROM teams t JOIN organisations o ON o.id = t.org_id WHERE o.name = $1 AND t.name = $2") |
|
| 1077 | 1077 | .bind(org) |
|
| 1078 | 1078 | .bind(team) |
|
| 1079 | 1079 | .fetch_optional(pool) |
|
| … | 147 unchanged lines |
||
| 1227 | 1227 | let sql = format!( |
|
| 1228 | 1228 | "{verb} INTO \"{table}\" ({}) VALUES ({})", |
|
| 1229 | 1229 | names.iter().map(|n| format!("\"{n}\"")).collect::<Vec<_>>().join(", "), |
|
| 1230 | - | vec!["?"; names.len()].join(", ") |
|
| 1230 | + | (1..=names.len()).map(|i| format!("${i}")).collect::<Vec<_>>().join(", ") |
|
| 1231 | 1231 | ); |
|
| 1232 | 1232 | let mut q = sqlx::query(&sql); |
|
| 1233 | 1233 | for (v, blob) in values { |
|
| … | 12 unchanged lines |
||
| 1246 | 1246 | // Search finds what came back. |
|
| 1247 | 1247 | for (kind, table) in [("issue", "issues"), ("proposal", "proposals")] { |
|
| 1248 | 1248 | let found: Vec<(i64, i64, String, String)> = |
|
| 1249 | - | sqlx::query_as(&format!("SELECT id, number, title, body FROM {table} WHERE repo_id = ?")).bind(repo).fetch_all(pool).await?; |
|
| 1249 | + | sqlx::query_as(&format!("SELECT id, number, title, body FROM {table} WHERE repo_id = $1")).bind(repo).fetch_all(pool).await?; |
|
| 1250 | 1250 | for (id, number, title, body) in found { |
|
| 1251 | 1251 | db::notify::index(pool, kind, repo, id, number, &title, &body).await?; |
|
| 1252 | 1252 | } |
|
| 1253 | 1253 | } |
|
| 1254 | 1254 | // Webhook secrets were never written: they are entered again. |
|
| 1255 | 1255 | let hooks: Vec<String> = |
|
| 1256 | - | sqlx::query_scalar("SELECT url FROM hooks WHERE repo_id = ? AND secret = ''").bind(repo).fetch_all(pool).await?; |
|
| 1256 | + | sqlx::query_scalar("SELECT url FROM hooks WHERE repo_id = $1 AND secret = ''").bind(repo).fetch_all(pool).await?; |
|
| 1257 | 1257 | for url in hooks { |
|
| 1258 | 1258 | notes.push(format!("webhook {url}: enter its secret again (Settings › Webhooks)")); |
|
| 1259 | 1259 | } |
|
| … | 44 unchanged lines |
||
| 1304 | 1304 | let s = setting(&app.db, owner).await?.with_context(|| format!("{owner} has no backup set up"))?; |
|
| 1305 | 1305 | let started = db::now_ms(); |
|
| 1306 | 1306 | let at = run_name(started); |
|
| 1307 | - | let id: i64 = |
|
| 1308 | - | sqlx::query_scalar("INSERT INTO backup_runs (owner, started_ms, status, at, trigger) VALUES (?, ?, 'running', ?, ?) RETURNING id") |
|
| 1309 | - | .bind(owner) |
|
| 1310 | - | .bind(started) |
|
| 1311 | - | .bind(&at) |
|
| 1312 | - | .bind(trigger) |
|
| 1313 | - | .fetch_one(&app.db) |
|
| 1314 | - | .await?; |
|
| 1307 | + | let id: i64 = sqlx::query_scalar( |
|
| 1308 | + | "INSERT INTO backup_runs (owner, started_ms, status, at, trigger) VALUES ($1, $2, 'running', $3, $4) RETURNING id", |
|
| 1309 | + | ) |
|
| 1310 | + | .bind(owner) |
|
| 1311 | + | .bind(started) |
|
| 1312 | + | .bind(&at) |
|
| 1313 | + | .bind(trigger) |
|
| 1314 | + | .fetch_one(&app.db) |
|
| 1315 | + | .await?; |
|
| 1315 | 1316 | let outcome = run_into(app, &s, &at, trigger == "now").await; |
|
| 1316 | 1317 | let (status, detail, sent) = match outcome { |
|
| 1317 | 1318 | Ok((sent, problems)) if problems.is_empty() => ("ok", String::new(), sent), |
|
| 1318 | 1319 | Ok((sent, problems)) => ("partial", problems.join("; "), sent), |
|
| 1319 | 1320 | Err(e) => ("failed", format!("{e:#}"), (0, Sent::default())), |
|
| 1320 | 1321 | }; |
|
| 1321 | - | sqlx::query("UPDATE backup_runs SET finished_ms = ?, status = ?, detail = ?, repos = ?, files = ?, sent = ?, bytes = ? WHERE id = ?") |
|
| 1322 | - | .bind(db::now_ms()) |
|
| 1323 | - | .bind(status) |
|
| 1324 | - | .bind(&detail) |
|
| 1325 | - | .bind(sent.0) |
|
| 1326 | - | .bind(sent.1.files) |
|
| 1327 | - | .bind(sent.1.sent) |
|
| 1328 | - | .bind(sent.1.bytes) |
|
| 1329 | - | .bind(id) |
|
| 1330 | - | .execute(&app.db) |
|
| 1331 | - | .await?; |
|
| 1332 | - | let r: Run = sqlx::query_as("SELECT * FROM backup_runs WHERE id = ?").bind(id).fetch_one(&app.db).await?; |
|
| 1322 | + | sqlx::query( |
|
| 1323 | + | "UPDATE backup_runs SET finished_ms = $1, status = $2, detail = $3, repos = $4, files = $5, sent = $6, bytes = $7 WHERE id = $8", |
|
| 1324 | + | ) |
|
| 1325 | + | .bind(db::now_ms()) |
|
| 1326 | + | .bind(status) |
|
| 1327 | + | .bind(&detail) |
|
| 1328 | + | .bind(sent.0) |
|
| 1329 | + | .bind(sent.1.files) |
|
| 1330 | + | .bind(sent.1.sent) |
|
| 1331 | + | .bind(sent.1.bytes) |
|
| 1332 | + | .bind(id) |
|
| 1333 | + | .execute(&app.db) |
|
| 1334 | + | .await?; |
|
| 1335 | + | let r: Run = sqlx::query_as("SELECT * FROM backup_runs WHERE id = $1").bind(id).fetch_one(&app.db).await?; |
|
| 1333 | 1336 | if status != "ok" { |
|
| 1334 | 1337 | notify_failure(app, &s, status, &detail).await; |
|
| 1335 | 1338 | } else { |
|
| … | 186 unchanged lines |
||
| 1522 | 1525 | ||
| 1523 | 1526 | /// The status of `owner`'s finished run before the newest. |
|
| 1524 | 1527 | async fn status_before(app: &Shared, owner: &str) -> Option<String> { |
|
| 1525 | - | sqlx::query_scalar("SELECT status FROM backup_runs WHERE owner = ? AND finished_ms IS NOT NULL ORDER BY id DESC LIMIT 1 OFFSET 1") |
|
| 1528 | + | sqlx::query_scalar("SELECT status FROM backup_runs WHERE owner = $1 AND finished_ms IS NOT NULL ORDER BY id DESC LIMIT 1 OFFSET 1") |
|
| 1526 | 1529 | .bind(owner) |
|
| 1527 | 1530 | .fetch_optional(&app.db) |
|
| 1528 | 1531 | .await |
|
| … | 40 unchanged lines |
||
| 1569 | 1572 | let settings: Vec<Setting> = sqlx::query_as("SELECT * FROM backup_settings WHERE updated_ms > 0").fetch_all(&app.db).await?; |
|
| 1570 | 1573 | let mut sent = 0; |
|
| 1571 | 1574 | for s in settings { |
|
| 1572 | - | let runs: Vec<Run> = sqlx::query_as("SELECT * FROM backup_runs WHERE owner = ? AND started_ms >= ? ORDER BY id") |
|
| 1575 | + | let runs: Vec<Run> = sqlx::query_as("SELECT * FROM backup_runs WHERE owner = $1 AND started_ms >= $2 ORDER BY id") |
|
| 1573 | 1576 | .bind(&s.owner) |
|
| 1574 | 1577 | .bind(now_ms - 86_400_000) |
|
| 1575 | 1578 | .fetch_all(&app.db) |
|
| … | 23 unchanged lines |
||
| 1599 | 1602 | /// "running" by a restart is marked failed first. |
|
| 1600 | 1603 | pub async fn scheduler(app: Shared) { |
|
| 1601 | 1604 | let _ = sqlx::query( |
|
| 1602 | - | "UPDATE backup_runs SET status = 'failed', detail = 'the hub restarted during the run', finished_ms = ? WHERE status = 'running'", |
|
| 1605 | + | "UPDATE backup_runs SET status = 'failed', detail = 'the hub restarted during the run', finished_ms = $1 WHERE status = 'running'", |
|
| 1603 | 1606 | ) |
|
| 1604 | 1607 | .bind(db::now_ms()) |
|
| 1605 | 1608 | .execute(&app.db) |
|
| … | 24 unchanged lines |
||
| 1630 | 1633 | let theirs: Vec<db::repos::RepoRow> = repos.iter().filter(|r| r.owner == s.owner).cloned().collect(); |
|
| 1631 | 1634 | let Some(period) = period_with(&s, &theirs) else { continue }; |
|
| 1632 | 1635 | let last: Option<i64> = |
|
| 1633 | - | sqlx::query_scalar("SELECT MAX(started_ms) FROM backup_runs WHERE owner = ?").bind(&s.owner).fetch_one(pool).await?; |
|
| 1636 | + | sqlx::query_scalar("SELECT MAX(started_ms) FROM backup_runs WHERE owner = $1").bind(&s.owner).fetch_one(pool).await?; |
|
| 1634 | 1637 | if last.is_none_or(|l| now - l >= period * 1000) { |
|
| 1635 | 1638 | out.push(s); |
|
| 1636 | 1639 | } |
|
| … | 72 unchanged lines |
||
| 1709 | 1712 | .collect(); |
|
| 1710 | 1713 | if !set.is_empty() { |
|
| 1711 | 1714 | let sql = format!( |
|
| 1712 | - | "UPDATE repositories SET {} WHERE id = ?", |
|
| 1713 | - | set.iter().map(|(c, _)| format!("\"{}\" = ?", c.name)).collect::<Vec<_>>().join(", ") |
|
| 1715 | + | "UPDATE repositories SET {} WHERE id = ${}", |
|
| 1716 | + | set.iter().enumerate().map(|(i, (c, _))| format!("\"{}\" = ${}", c.name, i + 1)).collect::<Vec<_>>().join(", "), |
|
| 1717 | + | set.len() + 1 |
|
| 1714 | 1718 | ); |
|
| 1715 | 1719 | let mut q = sqlx::query(&sql); |
|
| 1716 | 1720 | for (c, v) in set { |
|
| … | 90 unchanged lines |
modified crates/hub/src/coverage.rs +12 −12
| … | 464 unchanged lines |
||
| 465 | 465 | let sum = |f: &dyn Fn(&FileCoverage) -> u64| kept.iter().map(f).sum::<u64>() as i64; |
|
| 466 | 466 | let mut tx = crate::db::begin_write(&app.db).await?; |
|
| 467 | 467 | if let Some(job) = o.job_id { |
|
| 468 | - | sqlx::query("DELETE FROM coverage_reports WHERE job_id = ?").bind(job).execute(&mut *tx).await?; |
|
| 468 | + | sqlx::query("DELETE FROM coverage_reports WHERE job_id = $1").bind(job).execute(&mut *tx).await?; |
|
| 469 | 469 | } |
|
| 470 | 470 | let id: i64 = sqlx::query_scalar( |
|
| 471 | 471 | "INSERT INTO coverage_reports (repo_id, run_id, job_id, job, run_number, bookmark, state, heads, blob, format, \ |
|
| 472 | 472 | lines_found, lines_hit, functions_found, functions_hit, branches_found, branches_hit, hits, files, dropped, created_ms, uploader) \ |
|
| 473 | - | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id", |
|
| 473 | + | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) RETURNING id", |
|
| 474 | 474 | ) |
|
| 475 | 475 | .bind(o.repo_id) |
|
| 476 | 476 | .bind(o.run_id) |
|
| … | 21 unchanged lines |
||
| 498 | 498 | for f in &kept { |
|
| 499 | 499 | sqlx::query( |
|
| 500 | 500 | "INSERT INTO coverage_files (report_id, path, lines_found, lines_hit, functions_found, functions_hit, branches_found, branches_hit) \ |
|
| 501 | - | VALUES (?, ?, ?, ?, ?, ?, ?, ?)", |
|
| 501 | + | VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", |
|
| 502 | 502 | ) |
|
| 503 | 503 | .bind(id) |
|
| 504 | 504 | .bind(&f.path) |
|
| … | 12 unchanged lines |
||
| 517 | 517 | } |
|
| 518 | 518 | ||
| 519 | 519 | pub async fn report(pool: &Pool, repo_id: i64, id: i64) -> sqlx::Result<Option<Report>> { |
|
| 520 | - | sqlx::query_as("SELECT * FROM coverage_reports WHERE id = ? AND repo_id = ?").bind(id).bind(repo_id).fetch_optional(pool).await |
|
| 520 | + | sqlx::query_as("SELECT * FROM coverage_reports WHERE id = $1 AND repo_id = $2").bind(id).bind(repo_id).fetch_optional(pool).await |
|
| 521 | 521 | } |
|
| 522 | 522 | ||
| 523 | 523 | /// The newest reports on a bookmark, newest first. |
|
| 524 | 524 | pub async fn on_bookmark(pool: &Pool, repo_id: i64, bookmark: &str, limit: i64) -> sqlx::Result<Vec<Report>> { |
|
| 525 | - | sqlx::query_as("SELECT * FROM coverage_reports WHERE repo_id = ? AND bookmark = ? ORDER BY created_ms DESC, id DESC LIMIT ?") |
|
| 525 | + | sqlx::query_as("SELECT * FROM coverage_reports WHERE repo_id = $1 AND bookmark = $2 ORDER BY created_ms DESC, id DESC LIMIT $3") |
|
| 526 | 526 | .bind(repo_id) |
|
| 527 | 527 | .bind(bookmark) |
|
| 528 | 528 | .bind(limit) |
|
| … | 3 unchanged lines |
||
| 532 | 532 | ||
| 533 | 533 | /// When the repository's first report on any bookmark was kept. |
|
| 534 | 534 | pub async fn first_ms(pool: &Pool, repo_id: i64) -> sqlx::Result<Option<i64>> { |
|
| 535 | - | sqlx::query_scalar("SELECT MIN(created_ms) FROM coverage_reports WHERE repo_id = ?").bind(repo_id).fetch_one(pool).await |
|
| 535 | + | sqlx::query_scalar("SELECT MIN(created_ms) FROM coverage_reports WHERE repo_id = $1").bind(repo_id).fetch_one(pool).await |
|
| 536 | 536 | } |
|
| 537 | 537 | ||
| 538 | 538 | /// The bookmarks with reports, the most recent first. |
|
| 539 | 539 | pub async fn bookmarks(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<String>> { |
|
| 540 | - | sqlx::query_scalar("SELECT bookmark FROM coverage_reports WHERE repo_id = ? GROUP BY bookmark ORDER BY MAX(created_ms) DESC") |
|
| 540 | + | sqlx::query_scalar("SELECT bookmark FROM coverage_reports WHERE repo_id = $1 GROUP BY bookmark ORDER BY MAX(created_ms) DESC") |
|
| 541 | 541 | .bind(repo_id) |
|
| 542 | 542 | .fetch_all(pool) |
|
| 543 | 543 | .await |
|
| … | 10 unchanged lines |
||
| 554 | 554 | ||
| 555 | 555 | pub async fn rules(pool: &Pool, repo_id: i64) -> sqlx::Result<Rules> { |
|
| 556 | 556 | let row: Option<(Option<f64>, Option<f64>)> = |
|
| 557 | - | sqlx::query_as("SELECT min, max_drop FROM coverage_settings WHERE repo_id = ?").bind(repo_id).fetch_optional(pool).await?; |
|
| 557 | + | sqlx::query_as("SELECT min, max_drop FROM coverage_settings WHERE repo_id = $1").bind(repo_id).fetch_optional(pool).await?; |
|
| 558 | 558 | Ok(row.map(|(min, max_drop)| Rules { min, max_drop }).unwrap_or_default()) |
|
| 559 | 559 | } |
|
| 560 | 560 | ||
| 561 | 561 | pub async fn set_rules(pool: &Pool, repo_id: i64, r: Rules) -> sqlx::Result<()> { |
|
| 562 | 562 | sqlx::query( |
|
| 563 | - | "INSERT INTO coverage_settings (repo_id, min, max_drop) VALUES (?, ?, ?) \ |
|
| 563 | + | "INSERT INTO coverage_settings (repo_id, min, max_drop) VALUES ($1, $2, $3) \ |
|
| 564 | 564 | ON CONFLICT (repo_id) DO UPDATE SET min = excluded.min, max_drop = excluded.max_drop", |
|
| 565 | 565 | ) |
|
| 566 | 566 | .bind(repo_id) |
|
| … | 8 unchanged lines |
||
| 575 | 575 | /// before it there; on any other, the default bookmark's latest before it. |
|
| 576 | 576 | pub async fn baseline(pool: &Pool, r: &Report, default_bookmark: &str) -> sqlx::Result<Option<Report>> { |
|
| 577 | 577 | sqlx::query_as( |
|
| 578 | - | "SELECT * FROM coverage_reports WHERE repo_id = ? AND bookmark = ? AND id <> ? AND (created_ms < ? OR (created_ms = ? AND id < ?)) \ |
|
| 578 | + | "SELECT * FROM coverage_reports WHERE repo_id = $1 AND bookmark = $2 AND id <> $3 AND (created_ms < $4 OR (created_ms = $5 AND id < $6)) \ |
|
| 579 | 579 | ORDER BY created_ms DESC, id DESC LIMIT 1", |
|
| 580 | 580 | ) |
|
| 581 | 581 | .bind(r.repo_id) |
|
| … | 27 unchanged lines |
||
| 609 | 609 | } |
|
| 610 | 610 | ||
| 611 | 611 | pub async fn for_run(pool: &Pool, run_id: i64) -> sqlx::Result<Vec<Report>> { |
|
| 612 | - | sqlx::query_as("SELECT * FROM coverage_reports WHERE run_id = ? ORDER BY job").bind(run_id).fetch_all(pool).await |
|
| 612 | + | sqlx::query_as("SELECT * FROM coverage_reports WHERE run_id = $1 ORDER BY job").bind(run_id).fetch_all(pool).await |
|
| 613 | 613 | } |
|
| 614 | 614 | ||
| 615 | 615 | pub async fn files(pool: &Pool, report_id: i64) -> sqlx::Result<Vec<FileRow>> { |
|
| 616 | - | sqlx::query_as("SELECT * FROM coverage_files WHERE report_id = ? ORDER BY path").bind(report_id).fetch_all(pool).await |
|
| 616 | + | sqlx::query_as("SELECT * FROM coverage_files WHERE report_id = $1 ORDER BY path").bind(report_id).fetch_all(pool).await |
|
| 617 | 617 | } |
|
| 618 | 618 | ||
| 619 | 619 | #[cfg(test)] |
|
| … | 122 unchanged lines |
modified crates/hub/src/db/accounts.rs +94 −91
| … | 142 unchanged lines |
||
| 143 | 143 | // ---------------------------------------------------------------- accounts |
|
| 144 | 144 | ||
| 145 | 145 | pub async fn account_by_name(pool: &Pool, username: &str) -> sqlx::Result<Option<Account>> { |
|
| 146 | - | sqlx::query_as("SELECT * FROM accounts WHERE username = ?").bind(username).fetch_optional(pool).await |
|
| 146 | + | sqlx::query_as("SELECT * FROM accounts WHERE username = $1").bind(username).fetch_optional(pool).await |
|
| 147 | 147 | } |
|
| 148 | 148 | ||
| 149 | 149 | pub async fn account_by_id(pool: &Pool, id: i64) -> sqlx::Result<Option<Account>> { |
|
| 150 | - | sqlx::query_as("SELECT * FROM accounts WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 150 | + | sqlx::query_as("SELECT * FROM accounts WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 151 | 151 | } |
|
| 152 | 152 | ||
| 153 | 153 | pub async fn accounts(pool: &Pool) -> sqlx::Result<Vec<Account>> { |
|
| … | 13 unchanged lines |
||
| 167 | 167 | is_admin: bool, |
|
| 168 | 168 | ) -> sqlx::Result<Account> { |
|
| 169 | 169 | sqlx::query_as( |
|
| 170 | - | "INSERT INTO accounts (username, email, password_hash, session_salt, is_admin, created_ms) VALUES (?, ?, ?, ?, ?, ?) RETURNING *", |
|
| 170 | + | "INSERT INTO accounts (username, email, password_hash, session_salt, is_admin, created_ms) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *", |
|
| 171 | 171 | ) |
|
| 172 | 172 | .bind(username) |
|
| 173 | 173 | .bind(email) |
|
| … | 8 unchanged lines |
||
| 182 | 182 | pub async fn set_profile(pool: &Pool, id: i64, display_name: &str, email: &str) -> sqlx::Result<()> { |
|
| 183 | 183 | // A changed address is unverified until its own link is used. |
|
| 184 | 184 | sqlx::query( |
|
| 185 | - | "UPDATE accounts SET display_name = ?, email_verified = CASE WHEN email = ? THEN email_verified ELSE 0 END, email = ? WHERE id = ?", |
|
| 185 | + | "UPDATE accounts SET display_name = $1, email_verified = CASE WHEN email = $2 THEN email_verified ELSE 0 END, email = $3 WHERE id = $4", |
|
| 186 | 186 | ) |
|
| 187 | 187 | .bind(display_name) |
|
| 188 | 188 | .bind(email) |
|
| … | 7 unchanged lines |
||
| 196 | 196 | /// The public profile fields. |
|
| 197 | 197 | /// The accounts elsewhere a profile shows (#105), already normalised. |
|
| 198 | 198 | pub async fn set_links(pool: &Pool, id: i64, bluesky: &str, mastodon: &str, github: &str) -> sqlx::Result<()> { |
|
| 199 | - | sqlx::query("UPDATE accounts SET bluesky = ?, mastodon = ?, github = ? WHERE id = ?") |
|
| 199 | + | sqlx::query("UPDATE accounts SET bluesky = $1, mastodon = $2, github = $3 WHERE id = $4") |
|
| 200 | 200 | .bind(bluesky) |
|
| 201 | 201 | .bind(mastodon) |
|
| 202 | 202 | .bind(github) |
|
| … | 4 unchanged lines |
||
| 207 | 207 | } |
|
| 208 | 208 | ||
| 209 | 209 | pub async fn set_bio(pool: &Pool, id: i64, bio: &str, website: &str, location: &str) -> sqlx::Result<()> { |
|
| 210 | - | sqlx::query("UPDATE accounts SET bio = ?, website = ?, location = ? WHERE id = ?") |
|
| 210 | + | sqlx::query("UPDATE accounts SET bio = $1, website = $2, location = $3 WHERE id = $4") |
|
| 211 | 211 | .bind(bio) |
|
| 212 | 212 | .bind(website) |
|
| 213 | 213 | .bind(location) |
|
| … | 4 unchanged lines |
||
| 218 | 218 | } |
|
| 219 | 219 | ||
| 220 | 220 | pub async fn set_password(pool: &Pool, id: i64, password_hash: &str, session_salt: &str) -> sqlx::Result<()> { |
|
| 221 | - | sqlx::query("UPDATE accounts SET password_hash = ?, session_salt = ? WHERE id = ?") |
|
| 221 | + | sqlx::query("UPDATE accounts SET password_hash = $1, session_salt = $2 WHERE id = $3") |
|
| 222 | 222 | .bind(password_hash) |
|
| 223 | 223 | .bind(session_salt) |
|
| 224 | 224 | .bind(id) |
|
| … | 3 unchanged lines |
||
| 228 | 228 | } |
|
| 229 | 229 | ||
| 230 | 230 | pub async fn set_session_salt(pool: &Pool, id: i64, session_salt: &str) -> sqlx::Result<()> { |
|
| 231 | - | sqlx::query("UPDATE accounts SET session_salt = ? WHERE id = ?").bind(session_salt).bind(id).execute(pool).await?; |
|
| 231 | + | sqlx::query("UPDATE accounts SET session_salt = $1 WHERE id = $2").bind(session_salt).bind(id).execute(pool).await?; |
|
| 232 | 232 | Ok(()) |
|
| 233 | 233 | } |
|
| 234 | 234 | ||
| 235 | 235 | pub async fn set_totp(pool: &Pool, id: i64, secret: Option<&str>, recovery_codes: &str) -> sqlx::Result<()> { |
|
| 236 | - | sqlx::query("UPDATE accounts SET totp_secret = ?, recovery_codes = ? WHERE id = ?") |
|
| 236 | + | sqlx::query("UPDATE accounts SET totp_secret = $1, recovery_codes = $2 WHERE id = $3") |
|
| 237 | 237 | .bind(secret) |
|
| 238 | 238 | .bind(recovery_codes) |
|
| 239 | 239 | .bind(id) |
|
| … | 3 unchanged lines |
||
| 243 | 243 | } |
|
| 244 | 244 | ||
| 245 | 245 | pub async fn set_admin(pool: &Pool, id: i64, is_admin: bool) -> sqlx::Result<()> { |
|
| 246 | - | sqlx::query("UPDATE accounts SET is_admin = ? WHERE id = ?").bind(is_admin).bind(id).execute(pool).await?; |
|
| 246 | + | sqlx::query("UPDATE accounts SET is_admin = $1 WHERE id = $2").bind(is_admin).bind(id).execute(pool).await?; |
|
| 247 | 247 | Ok(()) |
|
| 248 | 248 | } |
|
| 249 | 249 | ||
| 250 | 250 | pub async fn set_banned(pool: &Pool, id: i64, banned: bool) -> sqlx::Result<()> { |
|
| 251 | - | sqlx::query("UPDATE accounts SET banned = ? WHERE id = ?").bind(banned).bind(id).execute(pool).await?; |
|
| 251 | + | sqlx::query("UPDATE accounts SET banned = $1 WHERE id = $2").bind(banned).bind(id).execute(pool).await?; |
|
| 252 | 252 | Ok(()) |
|
| 253 | 253 | } |
|
| 254 | 254 | ||
| 255 | 255 | pub async fn set_disabled(pool: &Pool, id: i64, disabled: bool) -> sqlx::Result<()> { |
|
| 256 | - | sqlx::query("UPDATE accounts SET disabled = ? WHERE id = ?").bind(disabled).bind(id).execute(pool).await?; |
|
| 256 | + | sqlx::query("UPDATE accounts SET disabled = $1 WHERE id = $2").bind(disabled).bind(id).execute(pool).await?; |
|
| 257 | 257 | Ok(()) |
|
| 258 | 258 | } |
|
| 259 | 259 | ||
| … | 3 unchanged lines |
||
| 263 | 263 | /// is retired so nobody can register it and pass for the account. |
|
| 264 | 264 | pub async fn delete_account(pool: &Pool, id: i64, username: &str) -> sqlx::Result<()> { |
|
| 265 | 265 | let mut tx = super::begin_write(pool).await?; |
|
| 266 | - | sqlx::query("INSERT OR IGNORE INTO retired_usernames (username, retired_ms) VALUES (?, ?)") |
|
| 266 | + | sqlx::query("INSERT OR IGNORE INTO retired_usernames (username, retired_ms) VALUES ($1, $2)") |
|
| 267 | 267 | .bind(username) |
|
| 268 | 268 | .bind(now_ms()) |
|
| 269 | 269 | .execute(&mut *tx) |
|
| 270 | 270 | .await?; |
|
| 271 | - | sqlx::query("DELETE FROM accounts WHERE id = ?").bind(id).execute(&mut *tx).await?; |
|
| 271 | + | sqlx::query("DELETE FROM accounts WHERE id = $1").bind(id).execute(&mut *tx).await?; |
|
| 272 | 272 | tx.commit().await |
|
| 273 | 273 | } |
|
| 274 | 274 | ||
| … | 2 unchanged lines |
||
| 277 | 277 | pub async fn orgs_owned_only_by(pool: &Pool, account_id: i64) -> sqlx::Result<Vec<String>> { |
|
| 278 | 278 | sqlx::query_scalar( |
|
| 279 | 279 | "SELECT o.name FROM organisations o JOIN org_members m ON m.org_id = o.id \ |
|
| 280 | - | WHERE m.account_id = ? AND m.role = 'owner' \ |
|
| 280 | + | WHERE m.account_id = $1 AND m.role = 'owner' \ |
|
| 281 | 281 | AND (SELECT COUNT(*) FROM org_members x WHERE x.org_id = o.id AND x.role = 'owner') = 1 \ |
|
| 282 | 282 | ORDER BY o.name", |
|
| 283 | 283 | ) |
|
| … | 4 unchanged lines |
||
| 288 | 288 | ||
| 289 | 289 | /// Whether `name` belonged to a deleted account. |
|
| 290 | 290 | pub async fn username_retired(pool: &Pool, name: &str) -> sqlx::Result<bool> { |
|
| 291 | - | Ok(sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM retired_usernames WHERE username = ?").bind(name).fetch_one(pool).await? > 0) |
|
| 291 | + | Ok(sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM retired_usernames WHERE username = $1").bind(name).fetch_one(pool).await? > 0) |
|
| 292 | 292 | } |
|
| 293 | 293 | ||
| 294 | 294 | pub async fn touch_seen(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 295 | - | sqlx::query("UPDATE accounts SET last_seen_ms = ? WHERE id = ?").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 295 | + | sqlx::query("UPDATE accounts SET last_seen_ms = $1 WHERE id = $2").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 296 | 296 | Ok(()) |
|
| 297 | 297 | } |
|
| 298 | 298 | ||
| 299 | 299 | // ----------------------------------------------------------- organisations |
|
| 300 | 300 | ||
| 301 | 301 | pub async fn org_by_name(pool: &Pool, name: &str) -> sqlx::Result<Option<Organisation>> { |
|
| 302 | - | sqlx::query_as("SELECT * FROM organisations WHERE name = ?").bind(name).fetch_optional(pool).await |
|
| 302 | + | sqlx::query_as("SELECT * FROM organisations WHERE name = $1").bind(name).fetch_optional(pool).await |
|
| 303 | 303 | } |
|
| 304 | 304 | ||
| 305 | 305 | pub async fn orgs(pool: &Pool) -> sqlx::Result<Vec<Organisation>> { |
|
| … | 10 unchanged lines |
||
| 316 | 316 | ||
| 317 | 317 | pub async fn orgs_of(pool: &Pool, account_id: i64) -> sqlx::Result<Vec<(Organisation, String)>> { |
|
| 318 | 318 | let rows: Vec<Membership> = sqlx::query_as( |
|
| 319 | - | "SELECT o.*, m.role FROM organisations o JOIN org_members m ON m.org_id = o.id WHERE m.account_id = ? ORDER BY o.name", |
|
| 319 | + | "SELECT o.*, m.role FROM organisations o JOIN org_members m ON m.org_id = o.id WHERE m.account_id = $1 ORDER BY o.name", |
|
| 320 | 320 | ) |
|
| 321 | 321 | .bind(account_id) |
|
| 322 | 322 | .fetch_all(pool) |
|
| … | 3 unchanged lines |
||
| 326 | 326 | ||
| 327 | 327 | pub async fn create_org(pool: &Pool, name: &str, display_name: &str, owner: i64) -> sqlx::Result<Organisation> { |
|
| 328 | 328 | let mut tx = super::begin_write(pool).await?; |
|
| 329 | - | let org: Organisation = sqlx::query_as("INSERT INTO organisations (name, display_name, created_ms) VALUES (?, ?, ?) RETURNING *") |
|
| 329 | + | let org: Organisation = sqlx::query_as("INSERT INTO organisations (name, display_name, created_ms) VALUES ($1, $2, $3) RETURNING *") |
|
| 330 | 330 | .bind(name) |
|
| 331 | 331 | .bind(display_name) |
|
| 332 | 332 | .bind(now_ms()) |
|
| 333 | 333 | .fetch_one(&mut *tx) |
|
| 334 | 334 | .await?; |
|
| 335 | - | sqlx::query("INSERT INTO org_members (org_id, account_id, role) VALUES (?, ?, 'owner')") |
|
| 335 | + | sqlx::query("INSERT INTO org_members (org_id, account_id, role) VALUES ($1, $2, 'owner')") |
|
| 336 | 336 | .bind(org.id) |
|
| 337 | 337 | .bind(owner) |
|
| 338 | 338 | .execute(&mut *tx) |
|
| … | 3 unchanged lines |
||
| 342 | 342 | } |
|
| 343 | 343 | ||
| 344 | 344 | pub async fn set_org(pool: &Pool, id: i64, display_name: &str, description: &str, default_role: &str) -> sqlx::Result<()> { |
|
| 345 | - | sqlx::query("UPDATE organisations SET display_name = ?, description = ?, default_role = ? WHERE id = ?") |
|
| 345 | + | sqlx::query("UPDATE organisations SET display_name = $1, description = $2, default_role = $3 WHERE id = $4") |
|
| 346 | 346 | .bind(display_name) |
|
| 347 | 347 | .bind(description) |
|
| 348 | 348 | .bind(default_role) |
|
| … | 6 unchanged lines |
||
| 355 | 355 | /// `(account, role)` for every member. |
|
| 356 | 356 | pub async fn org_members(pool: &Pool, org_id: i64) -> sqlx::Result<Vec<(Account, String)>> { |
|
| 357 | 357 | let rows: Vec<(i64, String)> = |
|
| 358 | - | sqlx::query_as("SELECT account_id, role FROM org_members WHERE org_id = ?").bind(org_id).fetch_all(pool).await?; |
|
| 358 | + | sqlx::query_as("SELECT account_id, role FROM org_members WHERE org_id = $1").bind(org_id).fetch_all(pool).await?; |
|
| 359 | 359 | let mut out = Vec::new(); |
|
| 360 | 360 | for (id, role) in rows { |
|
| 361 | 361 | if let Some(a) = account_by_id(pool, id).await? { |
|
| … | 5 unchanged lines |
||
| 367 | 367 | } |
|
| 368 | 368 | ||
| 369 | 369 | pub async fn org_role(pool: &Pool, org_id: i64, account_id: i64) -> sqlx::Result<Option<String>> { |
|
| 370 | - | sqlx::query_scalar("SELECT role FROM org_members WHERE org_id = ? AND account_id = ?") |
|
| 370 | + | sqlx::query_scalar("SELECT role FROM org_members WHERE org_id = $1 AND account_id = $2") |
|
| 371 | 371 | .bind(org_id) |
|
| 372 | 372 | .bind(account_id) |
|
| 373 | 373 | .fetch_optional(pool) |
|
| … | 1 unchanged lines |
||
| 375 | 375 | } |
|
| 376 | 376 | ||
| 377 | 377 | pub async fn set_org_member(pool: &Pool, org_id: i64, account_id: i64, role: &str) -> sqlx::Result<()> { |
|
| 378 | - | sqlx::query("INSERT INTO org_members (org_id, account_id, role) VALUES (?, ?, ?) ON CONFLICT (org_id, account_id) DO UPDATE SET role = excluded.role") |
|
| 378 | + | sqlx::query("INSERT INTO org_members (org_id, account_id, role) VALUES ($1, $2, $3) ON CONFLICT (org_id, account_id) DO UPDATE SET role = excluded.role") |
|
| 379 | 379 | .bind(org_id) |
|
| 380 | 380 | .bind(account_id) |
|
| 381 | 381 | .bind(role) |
|
| … | 3 unchanged lines |
||
| 385 | 385 | } |
|
| 386 | 386 | ||
| 387 | 387 | pub async fn remove_org_member(pool: &Pool, org_id: i64, account_id: i64) -> sqlx::Result<()> { |
|
| 388 | - | sqlx::query("DELETE FROM org_members WHERE org_id = ? AND account_id = ?").bind(org_id).bind(account_id).execute(pool).await?; |
|
| 388 | + | sqlx::query("DELETE FROM org_members WHERE org_id = $1 AND account_id = $2").bind(org_id).bind(account_id).execute(pool).await?; |
|
| 389 | 389 | Ok(()) |
|
| 390 | 390 | } |
|
| 391 | 391 | ||
| 392 | 392 | pub async fn teams(pool: &Pool, org_id: i64) -> sqlx::Result<Vec<Team>> { |
|
| 393 | - | sqlx::query_as("SELECT * FROM teams WHERE org_id = ? ORDER BY name").bind(org_id).fetch_all(pool).await |
|
| 393 | + | sqlx::query_as("SELECT * FROM teams WHERE org_id = $1 ORDER BY name").bind(org_id).fetch_all(pool).await |
|
| 394 | 394 | } |
|
| 395 | 395 | ||
| 396 | 396 | pub async fn team_by_name(pool: &Pool, org_id: i64, name: &str) -> sqlx::Result<Option<Team>> { |
|
| 397 | - | sqlx::query_as("SELECT * FROM teams WHERE org_id = ? AND name = ?").bind(org_id).bind(name).fetch_optional(pool).await |
|
| 397 | + | sqlx::query_as("SELECT * FROM teams WHERE org_id = $1 AND name = $2").bind(org_id).bind(name).fetch_optional(pool).await |
|
| 398 | 398 | } |
|
| 399 | 399 | ||
| 400 | 400 | pub async fn create_team(pool: &Pool, org_id: i64, name: &str) -> sqlx::Result<Team> { |
|
| 401 | - | sqlx::query_as("INSERT INTO teams (org_id, name) VALUES (?, ?) RETURNING *").bind(org_id).bind(name).fetch_one(pool).await |
|
| 401 | + | sqlx::query_as("INSERT INTO teams (org_id, name) VALUES ($1, $2) RETURNING *").bind(org_id).bind(name).fetch_one(pool).await |
|
| 402 | 402 | } |
|
| 403 | 403 | ||
| 404 | 404 | pub async fn delete_team(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 405 | - | sqlx::query("DELETE FROM teams WHERE id = ?").bind(id).execute(pool).await?; |
|
| 405 | + | sqlx::query("DELETE FROM teams WHERE id = $1").bind(id).execute(pool).await?; |
|
| 406 | 406 | Ok(()) |
|
| 407 | 407 | } |
|
| 408 | 408 | ||
| 409 | 409 | pub async fn team_members(pool: &Pool, team_id: i64) -> sqlx::Result<Vec<Account>> { |
|
| 410 | - | sqlx::query_as("SELECT a.* FROM accounts a JOIN team_members t ON t.account_id = a.id WHERE t.team_id = ? ORDER BY a.username") |
|
| 410 | + | sqlx::query_as("SELECT a.* FROM accounts a JOIN team_members t ON t.account_id = a.id WHERE t.team_id = $1 ORDER BY a.username") |
|
| 411 | 411 | .bind(team_id) |
|
| 412 | 412 | .fetch_all(pool) |
|
| 413 | 413 | .await |
|
| 414 | 414 | } |
|
| 415 | 415 | ||
| 416 | 416 | pub async fn add_team_member(pool: &Pool, team_id: i64, account_id: i64) -> sqlx::Result<()> { |
|
| 417 | - | sqlx::query("INSERT OR IGNORE INTO team_members (team_id, account_id) VALUES (?, ?)") |
|
| 417 | + | sqlx::query("INSERT OR IGNORE INTO team_members (team_id, account_id) VALUES ($1, $2)") |
|
| 418 | 418 | .bind(team_id) |
|
| 419 | 419 | .bind(account_id) |
|
| 420 | 420 | .execute(pool) |
|
| … | 2 unchanged lines |
||
| 423 | 423 | } |
|
| 424 | 424 | ||
| 425 | 425 | pub async fn remove_team_member(pool: &Pool, team_id: i64, account_id: i64) -> sqlx::Result<()> { |
|
| 426 | - | sqlx::query("DELETE FROM team_members WHERE team_id = ? AND account_id = ?").bind(team_id).bind(account_id).execute(pool).await?; |
|
| 426 | + | sqlx::query("DELETE FROM team_members WHERE team_id = $1 AND account_id = $2").bind(team_id).bind(account_id).execute(pool).await?; |
|
| 427 | 427 | Ok(()) |
|
| 428 | 428 | } |
|
| 429 | 429 | ||
| … | 6 unchanged lines |
||
| 436 | 436 | pub async fn assignable(pool: &Pool, repo_id: i64, owner: &str) -> sqlx::Result<Vec<String>> { |
|
| 437 | 437 | sqlx::query_scalar( |
|
| 438 | 438 | "SELECT username FROM accounts WHERE banned = 0 AND disabled = 0 AND id IN (\ |
|
| 439 | - | SELECT id FROM accounts WHERE username = ?1 \ |
|
| 440 | - | UNION SELECT account_id FROM collaborators WHERE repo_id = ?2 AND account_id IS NOT NULL \ |
|
| 441 | - | UNION SELECT tm.account_id FROM team_members tm JOIN collaborators c ON c.team_id = tm.team_id WHERE c.repo_id = ?2 \ |
|
| 442 | - | UNION SELECT m.account_id FROM org_members m JOIN organisations o ON o.id = m.org_id WHERE o.name = ?1) \ |
|
| 439 | + | SELECT id FROM accounts WHERE username = $1 \ |
|
| 440 | + | UNION SELECT account_id FROM collaborators WHERE repo_id = $2 AND account_id IS NOT NULL \ |
|
| 441 | + | UNION SELECT tm.account_id FROM team_members tm JOIN collaborators c ON c.team_id = tm.team_id WHERE c.repo_id = $2 \ |
|
| 442 | + | UNION SELECT m.account_id FROM org_members m JOIN organisations o ON o.id = m.org_id WHERE o.name = $1) \ |
|
| 443 | 443 | ORDER BY username COLLATE NOCASE", |
|
| 444 | 444 | ) |
|
| 445 | 445 | .bind(owner) |
|
| … | 11 unchanged lines |
||
| 457 | 457 | ||
| 458 | 458 | pub async fn collaborators(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<Collaborator>> { |
|
| 459 | 459 | let rows: Vec<(Option<i64>, Option<i64>, String)> = |
|
| 460 | - | sqlx::query_as("SELECT account_id, team_id, role FROM collaborators WHERE repo_id = ?").bind(repo_id).fetch_all(pool).await?; |
|
| 460 | + | sqlx::query_as("SELECT account_id, team_id, role FROM collaborators WHERE repo_id = $1").bind(repo_id).fetch_all(pool).await?; |
|
| 461 | 461 | let mut out = Vec::new(); |
|
| 462 | 462 | for (account_id, team_id, role) in rows { |
|
| 463 | 463 | let account = match account_id { |
|
| … | 1 unchanged lines |
||
| 465 | 465 | None => None, |
|
| 466 | 466 | }; |
|
| 467 | 467 | let team = match team_id { |
|
| 468 | - | Some(id) => sqlx::query_as("SELECT * FROM teams WHERE id = ?").bind(id).fetch_optional(pool).await?, |
|
| 468 | + | Some(id) => sqlx::query_as("SELECT * FROM teams WHERE id = $1").bind(id).fetch_optional(pool).await?, |
|
| 469 | 469 | None => None, |
|
| 470 | 470 | }; |
|
| 471 | 471 | out.push(Collaborator { account, team, role }); |
|
| … | 3 unchanged lines |
||
| 475 | 475 | ||
| 476 | 476 | pub async fn set_collaborator(pool: &Pool, repo_id: i64, account_id: Option<i64>, team_id: Option<i64>, role: &str) -> sqlx::Result<()> { |
|
| 477 | 477 | if let Some(a) = account_id { |
|
| 478 | - | sqlx::query("INSERT INTO collaborators (repo_id, account_id, role) VALUES (?, ?, ?) ON CONFLICT (repo_id, account_id) WHERE account_id IS NOT NULL DO UPDATE SET role = excluded.role") |
|
| 478 | + | sqlx::query("INSERT INTO collaborators (repo_id, account_id, role) VALUES ($1, $2, $3) ON CONFLICT (repo_id, account_id) WHERE account_id IS NOT NULL DO UPDATE SET role = excluded.role") |
|
| 479 | 479 | .bind(repo_id) |
|
| 480 | 480 | .bind(a) |
|
| 481 | 481 | .bind(role) |
|
| 482 | 482 | .execute(pool) |
|
| 483 | 483 | .await?; |
|
| 484 | 484 | } else if let Some(t) = team_id { |
|
| 485 | - | sqlx::query("INSERT INTO collaborators (repo_id, team_id, role) VALUES (?, ?, ?) ON CONFLICT (repo_id, team_id) WHERE team_id IS NOT NULL DO UPDATE SET role = excluded.role") |
|
| 485 | + | sqlx::query("INSERT INTO collaborators (repo_id, team_id, role) VALUES ($1, $2, $3) ON CONFLICT (repo_id, team_id) WHERE team_id IS NOT NULL DO UPDATE SET role = excluded.role") |
|
| 486 | 486 | .bind(repo_id) |
|
| 487 | 487 | .bind(t) |
|
| 488 | 488 | .bind(role) |
|
| … | 4 unchanged lines |
||
| 493 | 493 | } |
|
| 494 | 494 | ||
| 495 | 495 | pub async fn remove_collaborator(pool: &Pool, repo_id: i64, account_id: Option<i64>, team_id: Option<i64>) -> sqlx::Result<()> { |
|
| 496 | - | sqlx::query("DELETE FROM collaborators WHERE repo_id = ? AND ((account_id IS NOT NULL AND account_id = ?) OR (team_id IS NOT NULL AND team_id = ?))") |
|
| 496 | + | sqlx::query("DELETE FROM collaborators WHERE repo_id = $1 AND ((account_id IS NOT NULL AND account_id = $2) OR (team_id IS NOT NULL AND team_id = $3))") |
|
| 497 | 497 | .bind(repo_id) |
|
| 498 | 498 | .bind(account_id) |
|
| 499 | 499 | .bind(team_id) |
|
| … | 5 unchanged lines |
||
| 505 | 505 | /// The direct or team role an account holds on a repository, the highest. |
|
| 506 | 506 | pub async fn collaborator_role(pool: &Pool, repo_id: i64, account_id: i64) -> sqlx::Result<Option<String>> { |
|
| 507 | 507 | let rows: Vec<String> = sqlx::query_scalar( |
|
| 508 | - | "SELECT role FROM collaborators WHERE repo_id = ? AND (account_id = ? OR team_id IN (SELECT team_id FROM team_members WHERE account_id = ?))", |
|
| 508 | + | "SELECT role FROM collaborators WHERE repo_id = $1 AND (account_id = $2 OR team_id IN (SELECT team_id FROM team_members WHERE account_id = $3))", |
|
| 509 | 509 | ) |
|
| 510 | 510 | .bind(repo_id) |
|
| 511 | 511 | .bind(account_id) |
|
| … | 6 unchanged lines |
||
| 518 | 518 | // ------------------------------------------------------------------ tokens |
|
| 519 | 519 | ||
| 520 | 520 | pub async fn tokens_of(pool: &Pool, account_id: i64) -> sqlx::Result<Vec<Token>> { |
|
| 521 | - | sqlx::query_as("SELECT * FROM tokens WHERE account_id = ? ORDER BY created_ms DESC").bind(account_id).fetch_all(pool).await |
|
| 521 | + | sqlx::query_as("SELECT * FROM tokens WHERE account_id = $1 ORDER BY created_ms DESC").bind(account_id).fetch_all(pool).await |
|
| 522 | 522 | } |
|
| 523 | 523 | ||
| 524 | 524 | /// Mark one of `account_id`'s tokens as an agent's, or not. |
|
| 525 | 525 | pub async fn set_token_agent(pool: &Pool, account_id: i64, id: i64, agent: bool) -> sqlx::Result<bool> { |
|
| 526 | - | let r = sqlx::query("UPDATE tokens SET agent = ? WHERE id = ? AND account_id = ?") |
|
| 526 | + | let r = sqlx::query("UPDATE tokens SET agent = $1 WHERE id = $2 AND account_id = $3") |
|
| 527 | 527 | .bind(agent) |
|
| 528 | 528 | .bind(id) |
|
| 529 | 529 | .bind(account_id) |
|
| … | 3 unchanged lines |
||
| 533 | 533 | } |
|
| 534 | 534 | ||
| 535 | 535 | pub async fn token_by_hash(pool: &Pool, hash: &str) -> sqlx::Result<Option<Token>> { |
|
| 536 | - | sqlx::query_as("SELECT * FROM tokens WHERE hash = ?").bind(hash).fetch_optional(pool).await |
|
| 536 | + | sqlx::query_as("SELECT * FROM tokens WHERE hash = $1").bind(hash).fetch_optional(pool).await |
|
| 537 | 537 | } |
|
| 538 | 538 | ||
| 539 | 539 | pub async fn create_token( |
|
| … | 6 unchanged lines |
||
| 546 | 546 | expires_ms: Option<i64>, |
|
| 547 | 547 | ) -> sqlx::Result<Token> { |
|
| 548 | 548 | sqlx::query_as( |
|
| 549 | - | "INSERT INTO tokens (account_id, name, hash, scopes, repos, created_ms, expires_ms) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *", |
|
| 549 | + | "INSERT INTO tokens (account_id, name, hash, scopes, repos, created_ms, expires_ms) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", |
|
| 550 | 550 | ) |
|
| 551 | 551 | .bind(account_id) |
|
| 552 | 552 | .bind(name) |
|
| … | 7 unchanged lines |
||
| 560 | 560 | } |
|
| 561 | 561 | ||
| 562 | 562 | pub async fn delete_token(pool: &Pool, account_id: i64, id: i64) -> sqlx::Result<()> { |
|
| 563 | - | sqlx::query("DELETE FROM tokens WHERE id = ? AND account_id = ?").bind(id).bind(account_id).execute(pool).await?; |
|
| 563 | + | sqlx::query("DELETE FROM tokens WHERE id = $1 AND account_id = $2").bind(id).bind(account_id).execute(pool).await?; |
|
| 564 | 564 | Ok(()) |
|
| 565 | 565 | } |
|
| 566 | 566 | ||
| 567 | 567 | /// Record a use; a renewing token's expiry moves to now plus its lifetime. |
|
| 568 | 568 | pub async fn touch_token(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 569 | 569 | sqlx::query( |
|
| 570 | - | "UPDATE tokens SET last_used_ms = ?1, \ |
|
| 571 | - | expires_ms = CASE WHEN renew_ms IS NULL THEN expires_ms ELSE MAX(COALESCE(expires_ms, 0), ?1 + renew_ms) END \ |
|
| 572 | - | WHERE id = ?2", |
|
| 570 | + | "UPDATE tokens SET last_used_ms = $1, \ |
|
| 571 | + | expires_ms = CASE WHEN renew_ms IS NULL THEN expires_ms ELSE MAX(COALESCE(expires_ms, 0), $1 + renew_ms) END \ |
|
| 572 | + | WHERE id = $2", |
|
| 573 | 573 | ) |
|
| 574 | 574 | .bind(now_ms()) |
|
| 575 | 575 | .bind(id) |
|
| … | 4 unchanged lines |
||
| 580 | 580 | ||
| 581 | 581 | /// Make a token renewing: it lasts `renew_ms` past its last use. |
|
| 582 | 582 | pub async fn set_token_renewal(pool: &Pool, id: i64, renew_ms: i64) -> sqlx::Result<()> { |
|
| 583 | - | sqlx::query("UPDATE tokens SET renew_ms = ? WHERE id = ?").bind(renew_ms).bind(id).execute(pool).await?; |
|
| 583 | + | sqlx::query("UPDATE tokens SET renew_ms = $1 WHERE id = $2").bind(renew_ms).bind(id).execute(pool).await?; |
|
| 584 | 584 | Ok(()) |
|
| 585 | 585 | } |
|
| 586 | 586 | ||
| … | 34 unchanged lines |
||
| 621 | 621 | requester: &str, |
|
| 622 | 622 | ) -> sqlx::Result<DeviceCode> { |
|
| 623 | 623 | // Expired codes go whenever a new one is made. |
|
| 624 | - | sqlx::query("DELETE FROM device_codes WHERE expires_ms < ?").bind(now_ms()).execute(pool).await?; |
|
| 624 | + | sqlx::query("DELETE FROM device_codes WHERE expires_ms < $1").bind(now_ms()).execute(pool).await?; |
|
| 625 | 625 | sqlx::query_as( |
|
| 626 | - | "INSERT INTO device_codes (device_hash, user_code, client, scopes, created_ms, expires_ms, requester) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *", |
|
| 626 | + | "INSERT INTO device_codes (device_hash, user_code, client, scopes, created_ms, expires_ms, requester) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", |
|
| 627 | 627 | ) |
|
| 628 | 628 | .bind(device_hash) |
|
| 629 | 629 | .bind(user_code) |
|
| … | 7 unchanged lines |
||
| 637 | 637 | } |
|
| 638 | 638 | ||
| 639 | 639 | pub async fn device_by_user_code(pool: &Pool, user_code: &str) -> sqlx::Result<Option<DeviceCode>> { |
|
| 640 | - | sqlx::query_as("SELECT * FROM device_codes WHERE user_code = ?").bind(user_code).fetch_optional(pool).await |
|
| 640 | + | sqlx::query_as("SELECT * FROM device_codes WHERE user_code = $1").bind(user_code).fetch_optional(pool).await |
|
| 641 | 641 | } |
|
| 642 | 642 | ||
| 643 | 643 | pub async fn device_by_hash(pool: &Pool, device_hash: &str) -> sqlx::Result<Option<DeviceCode>> { |
|
| 644 | - | sqlx::query_as("SELECT * FROM device_codes WHERE device_hash = ?").bind(device_hash).fetch_optional(pool).await |
|
| 644 | + | sqlx::query_as("SELECT * FROM device_codes WHERE device_hash = $1").bind(device_hash).fetch_optional(pool).await |
|
| 645 | 645 | } |
|
| 646 | 646 | ||
| 647 | 647 | /// Approve a pending code for an account with these scopes; false when it |
|
| 648 | 648 | /// was no longer pending. |
|
| 649 | 649 | pub async fn approve_device(pool: &Pool, id: i64, account_id: i64, scopes: &str) -> sqlx::Result<bool> { |
|
| 650 | 650 | let r = sqlx::query( |
|
| 651 | - | "UPDATE device_codes SET account_id = ?, approved_scopes = ? WHERE id = ? AND account_id IS NULL AND denied = 0 AND expires_ms >= ?", |
|
| 651 | + | "UPDATE device_codes SET account_id = $1, approved_scopes = $2 WHERE id = $3 AND account_id IS NULL AND denied = 0 AND expires_ms >= $4", |
|
| 652 | 652 | ) |
|
| 653 | 653 | .bind(account_id) |
|
| 654 | 654 | .bind(scopes) |
|
| … | 5 unchanged lines |
||
| 660 | 660 | } |
|
| 661 | 661 | ||
| 662 | 662 | pub async fn deny_device(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 663 | - | sqlx::query("UPDATE device_codes SET denied = 1 WHERE id = ? AND account_id IS NULL").bind(id).execute(pool).await?; |
|
| 663 | + | sqlx::query("UPDATE device_codes SET denied = 1 WHERE id = $1 AND account_id IS NULL").bind(id).execute(pool).await?; |
|
| 664 | 664 | Ok(()) |
|
| 665 | 665 | } |
|
| 666 | 666 | ||
| 667 | 667 | pub async fn device_polled(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 668 | - | sqlx::query("UPDATE device_codes SET last_poll_ms = ? WHERE id = ?").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 668 | + | sqlx::query("UPDATE device_codes SET last_poll_ms = $1 WHERE id = $2").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 669 | 669 | Ok(()) |
|
| 670 | 670 | } |
|
| 671 | 671 | ||
| 672 | 672 | /// Remove a code; true when this call removed it (so one poll gets the |
|
| 673 | 673 | /// token, however many race). |
|
| 674 | 674 | pub async fn delete_device(pool: &Pool, id: i64) -> sqlx::Result<bool> { |
|
| 675 | - | let r = sqlx::query("DELETE FROM device_codes WHERE id = ?").bind(id).execute(pool).await?; |
|
| 675 | + | let r = sqlx::query("DELETE FROM device_codes WHERE id = $1").bind(id).execute(pool).await?; |
|
| 676 | 676 | Ok(r.rows_affected() == 1) |
|
| 677 | 677 | } |
|
| 678 | 678 | ||
| 679 | 679 | // -------------------------------------------------------------- identities |
|
| 680 | 680 | ||
| 681 | 681 | pub async fn identities_of(pool: &Pool, account_id: i64) -> sqlx::Result<Vec<LinkedIdentity>> { |
|
| 682 | - | sqlx::query_as("SELECT * FROM identities WHERE account_id = ? ORDER BY linked_ms DESC").bind(account_id).fetch_all(pool).await |
|
| 682 | + | sqlx::query_as("SELECT * FROM identities WHERE account_id = $1 ORDER BY linked_ms DESC").bind(account_id).fetch_all(pool).await |
|
| 683 | 683 | } |
|
| 684 | 684 | ||
| 685 | 685 | pub async fn identity_by_key(pool: &Pool, key: &str) -> sqlx::Result<Option<LinkedIdentity>> { |
|
| 686 | - | sqlx::query_as("SELECT * FROM identities WHERE key = ?").bind(key).fetch_optional(pool).await |
|
| 686 | + | sqlx::query_as("SELECT * FROM identities WHERE key = $1").bind(key).fetch_optional(pool).await |
|
| 687 | 687 | } |
|
| 688 | 688 | ||
| 689 | 689 | /// Every linked key with its account's username, for attribution. |
|
| … | 2 unchanged lines |
||
| 692 | 692 | } |
|
| 693 | 693 | ||
| 694 | 694 | pub async fn link_identity(pool: &Pool, account_id: i64, key: &str, name: &str, email: &str, object: &[u8]) -> sqlx::Result<()> { |
|
| 695 | - | sqlx::query("INSERT INTO identities (key, account_id, name, email, object, linked_ms) VALUES (?, ?, ?, ?, ?, ?)") |
|
| 695 | + | sqlx::query("INSERT INTO identities (key, account_id, name, email, object, linked_ms) VALUES ($1, $2, $3, $4, $5, $6)") |
|
| 696 | 696 | .bind(key) |
|
| 697 | 697 | .bind(account_id) |
|
| 698 | 698 | .bind(name) |
|
| … | 6 unchanged lines |
||
| 705 | 705 | } |
|
| 706 | 706 | ||
| 707 | 707 | pub async fn unlink_identity(pool: &Pool, account_id: i64, key: &str) -> sqlx::Result<()> { |
|
| 708 | - | sqlx::query("DELETE FROM identities WHERE key = ? AND account_id = ?").bind(key).bind(account_id).execute(pool).await?; |
|
| 708 | + | sqlx::query("DELETE FROM identities WHERE key = $1 AND account_id = $2").bind(key).bind(account_id).execute(pool).await?; |
|
| 709 | 709 | Ok(()) |
|
| 710 | 710 | } |
|
| 711 | 711 | ||
| 712 | 712 | pub async fn create_challenge(pool: &Pool, account_id: i64, bytes_hex: &str) -> sqlx::Result<i64> { |
|
| 713 | - | sqlx::query("DELETE FROM challenges WHERE created_ms < ?").bind(now_ms() - 10 * 60 * 1000).execute(pool).await?; |
|
| 714 | - | let id: i64 = sqlx::query_scalar("INSERT INTO challenges (account_id, bytes, created_ms) VALUES (?, ?, ?) RETURNING id") |
|
| 713 | + | sqlx::query("DELETE FROM challenges WHERE created_ms < $1").bind(now_ms() - 10 * 60 * 1000).execute(pool).await?; |
|
| 714 | + | let id: i64 = sqlx::query_scalar("INSERT INTO challenges (account_id, bytes, created_ms) VALUES ($1, $2, $3) RETURNING id") |
|
| 715 | 715 | .bind(account_id) |
|
| 716 | 716 | .bind(bytes_hex) |
|
| 717 | 717 | .bind(now_ms()) |
|
| … | 4 unchanged lines |
||
| 722 | 722 | ||
| 723 | 723 | /// Take (and delete) a pending challenge of this account. |
|
| 724 | 724 | pub async fn take_challenge(pool: &Pool, account_id: i64, id: i64) -> sqlx::Result<Option<String>> { |
|
| 725 | - | let bytes: Option<String> = sqlx::query_scalar("SELECT bytes FROM challenges WHERE id = ? AND account_id = ? AND created_ms >= ?") |
|
| 725 | + | let bytes: Option<String> = sqlx::query_scalar("SELECT bytes FROM challenges WHERE id = $1 AND account_id = $2 AND created_ms >= $3") |
|
| 726 | 726 | .bind(id) |
|
| 727 | 727 | .bind(account_id) |
|
| 728 | 728 | .bind(now_ms() - 10 * 60 * 1000) |
|
| 729 | 729 | .fetch_optional(pool) |
|
| 730 | 730 | .await?; |
|
| 731 | - | sqlx::query("DELETE FROM challenges WHERE id = ?").bind(id).execute(pool).await?; |
|
| 731 | + | sqlx::query("DELETE FROM challenges WHERE id = $1").bind(id).execute(pool).await?; |
|
| 732 | 732 | Ok(bytes) |
|
| 733 | 733 | } |
|
| 734 | 734 | ||
| 735 | 735 | // ------------------------------------------------------------- invitations |
|
| 736 | 736 | ||
| 737 | 737 | pub async fn create_invitation(pool: &Pool, code: &str, created_by: Option<i64>) -> sqlx::Result<()> { |
|
| 738 | - | sqlx::query("INSERT INTO invitations (code, created_by, created_ms) VALUES (?, ?, ?)") |
|
| 738 | + | sqlx::query("INSERT INTO invitations (code, created_by, created_ms) VALUES ($1, $2, $3)") |
|
| 739 | 739 | .bind(code) |
|
| 740 | 740 | .bind(created_by) |
|
| 741 | 741 | .bind(now_ms()) |
|
| … | 4 unchanged lines |
||
| 746 | 746 | ||
| 747 | 747 | pub async fn invitation_unused(pool: &Pool, code: &str) -> sqlx::Result<bool> { |
|
| 748 | 748 | let n: i64 = |
|
| 749 | - | sqlx::query_scalar("SELECT COUNT(*) FROM invitations WHERE code = ? AND used_by IS NULL").bind(code).fetch_one(pool).await?; |
|
| 749 | + | sqlx::query_scalar("SELECT COUNT(*) FROM invitations WHERE code = $1 AND used_by IS NULL").bind(code).fetch_one(pool).await?; |
|
| 750 | 750 | Ok(n > 0) |
|
| 751 | 751 | } |
|
| 752 | 752 | ||
| 753 | 753 | /// Use an invitation up if it is still unused; whether it was. |
|
| 754 | 754 | pub async fn claim_invitation(pool: &Pool, code: &str, account_id: i64) -> sqlx::Result<bool> { |
|
| 755 | - | let r = sqlx::query("UPDATE invitations SET used_by = ? WHERE code = ? AND used_by IS NULL") |
|
| 755 | + | let r = sqlx::query("UPDATE invitations SET used_by = $1 WHERE code = $2 AND used_by IS NULL") |
|
| 756 | 756 | .bind(account_id) |
|
| 757 | 757 | .bind(code) |
|
| 758 | 758 | .execute(pool) |
|
| … | 2 unchanged lines |
||
| 761 | 761 | } |
|
| 762 | 762 | ||
| 763 | 763 | pub async fn use_invitation(pool: &Pool, code: &str, account_id: i64) -> sqlx::Result<()> { |
|
| 764 | - | sqlx::query("UPDATE invitations SET used_by = ? WHERE code = ?").bind(account_id).bind(code).execute(pool).await?; |
|
| 764 | + | sqlx::query("UPDATE invitations SET used_by = $1 WHERE code = $2").bind(account_id).bind(code).execute(pool).await?; |
|
| 765 | 765 | Ok(()) |
|
| 766 | 766 | } |
|
| 767 | 767 | ||
| … | 4 unchanged lines |
||
| 772 | 772 | // ---------------------------------------------------------------- settings |
|
| 773 | 773 | ||
| 774 | 774 | pub async fn setting(pool: &Pool, key: &str) -> sqlx::Result<Option<String>> { |
|
| 775 | - | sqlx::query_scalar("SELECT value FROM settings WHERE key = ?").bind(key).fetch_optional(pool).await |
|
| 775 | + | sqlx::query_scalar("SELECT value FROM settings WHERE key = $1").bind(key).fetch_optional(pool).await |
|
| 776 | 776 | } |
|
| 777 | 777 | ||
| 778 | 778 | pub async fn set_setting(pool: &Pool, key: &str, value: &str) -> sqlx::Result<()> { |
|
| 779 | - | sqlx::query("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value") |
|
| 779 | + | sqlx::query("INSERT INTO settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = excluded.value") |
|
| 780 | 780 | .bind(key) |
|
| 781 | 781 | .bind(value) |
|
| 782 | 782 | .execute(pool) |
|
| … | 4 unchanged lines |
||
| 787 | 787 | // ------------------------------------------------------------------- audit |
|
| 788 | 788 | ||
| 789 | 789 | pub async fn audit(pool: &Pool, actor: Option<&Account>, action: &str, target: &str, detail: &str) -> sqlx::Result<()> { |
|
| 790 | - | sqlx::query("INSERT INTO audit (at_ms, actor_id, actor, action, target, detail) VALUES (?, ?, ?, ?, ?, ?)") |
|
| 790 | + | sqlx::query("INSERT INTO audit (at_ms, actor_id, actor, action, target, detail) VALUES ($1, $2, $3, $4, $5, $6)") |
|
| 791 | 791 | .bind(now_ms()) |
|
| 792 | 792 | .bind(actor.map(|a| a.id)) |
|
| 793 | 793 | .bind(actor.map(|a| a.username.as_str()).unwrap_or("")) |
|
| … | 7 unchanged lines |
||
| 801 | 801 | ||
| 802 | 802 | /// A page of the audit log, newest first, older than `before` (an id) when given. |
|
| 803 | 803 | pub async fn audit_page(pool: &Pool, before: Option<i64>, limit: i64) -> sqlx::Result<Vec<AuditRow>> { |
|
| 804 | - | sqlx::query_as("SELECT id, at_ms, actor, action, target, detail FROM audit WHERE (? IS NULL OR id < ?) ORDER BY id DESC LIMIT ?") |
|
| 804 | + | sqlx::query_as("SELECT id, at_ms, actor, action, target, detail FROM audit WHERE ($1 IS NULL OR id < $2) ORDER BY id DESC LIMIT $3") |
|
| 805 | 805 | .bind(before) |
|
| 806 | 806 | .bind(before) |
|
| 807 | 807 | .bind(limit) |
|
| … | 2 unchanged lines |
||
| 810 | 810 | } |
|
| 811 | 811 | ||
| 812 | 812 | pub async fn audit_rows(pool: &Pool, limit: i64) -> sqlx::Result<Vec<AuditRow>> { |
|
| 813 | - | sqlx::query_as("SELECT id, at_ms, actor, action, target, detail FROM audit ORDER BY at_ms DESC, id DESC LIMIT ?") |
|
| 813 | + | sqlx::query_as("SELECT id, at_ms, actor, action, target, detail FROM audit ORDER BY at_ms DESC, id DESC LIMIT $1") |
|
| 814 | 814 | .bind(limit) |
|
| 815 | 815 | .fetch_all(pool) |
|
| 816 | 816 | .await |
|
| … | 3 unchanged lines |
||
| 820 | 820 | /// its organisations', and those it collaborates on. |
|
| 821 | 821 | pub async fn visible_repo_ids(pool: &Pool, account_id: i64) -> sqlx::Result<Vec<i64>> { |
|
| 822 | 822 | sqlx::query_scalar( |
|
| 823 | - | "SELECT r.id FROM repositories r WHERE r.owner IN (SELECT username FROM accounts WHERE id = ?) \ |
|
| 824 | - | OR r.owner IN (SELECT o.name FROM organisations o JOIN org_members m ON m.org_id = o.id WHERE m.account_id = ?) \ |
|
| 825 | - | OR r.id IN (SELECT repo_id FROM collaborators WHERE account_id = ? OR team_id IN (SELECT team_id FROM team_members WHERE account_id = ?))", |
|
| 823 | + | "SELECT r.id FROM repositories r WHERE r.owner IN (SELECT username FROM accounts WHERE id = $1) \ |
|
| 824 | + | OR r.owner IN (SELECT o.name FROM organisations o JOIN org_members m ON m.org_id = o.id WHERE m.account_id = $2) \ |
|
| 825 | + | OR r.id IN (SELECT repo_id FROM collaborators WHERE account_id = $3 OR team_id IN (SELECT team_id FROM team_members WHERE account_id = $4))", |
|
| 826 | 826 | ) |
|
| 827 | 827 | .bind(account_id) |
|
| 828 | 828 | .bind(account_id) |
|
| … | 31 unchanged lines |
||
| 860 | 860 | let secret = crate::auth::random_hex(32); |
|
| 861 | 861 | let now = now_ms(); |
|
| 862 | 862 | sqlx::query( |
|
| 863 | - | "INSERT INTO account_links (account_id, kind, hash, email, created_by, created_ms, expires_ms) VALUES (?, ?, ?, ?, ?, ?, ?)", |
|
| 863 | + | "INSERT INTO account_links (account_id, kind, hash, email, created_by, created_ms, expires_ms) VALUES ($1, $2, $3, $4, $5, $6, $7)", |
|
| 864 | 864 | ) |
|
| 865 | 865 | .bind(account_id) |
|
| 866 | 866 | .bind(kind) |
|
| … | 9 unchanged lines |
||
| 876 | 876 | ||
| 877 | 877 | /// An unused, unexpired link of this kind with this secret. |
|
| 878 | 878 | pub async fn live_link(pool: &Pool, kind: &str, secret: &str) -> sqlx::Result<Option<AccountLink>> { |
|
| 879 | - | sqlx::query_as("SELECT * FROM account_links WHERE kind = ? AND hash = ? AND used_ms IS NULL AND expires_ms > ?") |
|
| 879 | + | sqlx::query_as("SELECT * FROM account_links WHERE kind = $1 AND hash = $2 AND used_ms IS NULL AND expires_ms > $3") |
|
| 880 | 880 | .bind(kind) |
|
| 881 | 881 | .bind(crate::auth::token_hash(secret)) |
|
| 882 | 882 | .bind(now_ms()) |
|
| … | 3 unchanged lines |
||
| 886 | 886 | ||
| 887 | 887 | /// Consume a link; false when it was already used (a race lost). |
|
| 888 | 888 | pub async fn use_link(pool: &Pool, id: i64) -> sqlx::Result<bool> { |
|
| 889 | - | let r = |
|
| 890 | - | sqlx::query("UPDATE account_links SET used_ms = ? WHERE id = ? AND used_ms IS NULL").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 889 | + | let r = sqlx::query("UPDATE account_links SET used_ms = $1 WHERE id = $2 AND used_ms IS NULL") |
|
| 890 | + | .bind(now_ms()) |
|
| 891 | + | .bind(id) |
|
| 892 | + | .execute(pool) |
|
| 893 | + | .await?; |
|
| 891 | 894 | Ok(r.rows_affected() == 1) |
|
| 892 | 895 | } |
|
| 893 | 896 | ||
| 894 | 897 | /// Void every unused link of a kind for an account. |
|
| 895 | 898 | pub async fn void_links(pool: &Pool, account_id: i64, kind: &str) -> sqlx::Result<()> { |
|
| 896 | - | sqlx::query("UPDATE account_links SET used_ms = ? WHERE account_id = ? AND kind = ? AND used_ms IS NULL") |
|
| 899 | + | sqlx::query("UPDATE account_links SET used_ms = $1 WHERE account_id = $2 AND kind = $3 AND used_ms IS NULL") |
|
| 897 | 900 | .bind(now_ms()) |
|
| 898 | 901 | .bind(account_id) |
|
| 899 | 902 | .bind(kind) |
|
| … | 6 unchanged lines |
||
| 906 | 909 | pub async fn last_link_ms(pool: &Pool, account_id: i64, kind: &str, email: Option<&str>) -> sqlx::Result<Option<i64>> { |
|
| 907 | 910 | match email { |
|
| 908 | 911 | Some(e) => { |
|
| 909 | - | sqlx::query_scalar("SELECT MAX(created_ms) FROM account_links WHERE account_id = ? AND kind = ? AND email = ?") |
|
| 912 | + | sqlx::query_scalar("SELECT MAX(created_ms) FROM account_links WHERE account_id = $1 AND kind = $2 AND email = $3") |
|
| 910 | 913 | .bind(account_id) |
|
| 911 | 914 | .bind(kind) |
|
| 912 | 915 | .bind(e) |
|
| … | 1 unchanged lines |
||
| 914 | 917 | .await |
|
| 915 | 918 | } |
|
| 916 | 919 | None => { |
|
| 917 | - | sqlx::query_scalar("SELECT MAX(created_ms) FROM account_links WHERE account_id = ? AND kind = ?") |
|
| 920 | + | sqlx::query_scalar("SELECT MAX(created_ms) FROM account_links WHERE account_id = $1 AND kind = $2") |
|
| 918 | 921 | .bind(account_id) |
|
| 919 | 922 | .bind(kind) |
|
| 920 | 923 | .fetch_one(pool) |
|
| … | 4 unchanged lines |
||
| 925 | 928 | ||
| 926 | 929 | /// How many links of a kind the account got since `since_ms`. |
|
| 927 | 930 | pub async fn links_since(pool: &Pool, account_id: i64, kind: &str, since_ms: i64) -> sqlx::Result<i64> { |
|
| 928 | - | sqlx::query_scalar("SELECT COUNT(*) FROM account_links WHERE account_id = ? AND kind = ? AND created_ms >= ?") |
|
| 931 | + | sqlx::query_scalar("SELECT COUNT(*) FROM account_links WHERE account_id = $1 AND kind = $2 AND created_ms >= $3") |
|
| 929 | 932 | .bind(account_id) |
|
| 930 | 933 | .bind(kind) |
|
| 931 | 934 | .bind(since_ms) |
|
| … | 2 unchanged lines |
||
| 934 | 937 | } |
|
| 935 | 938 | ||
| 936 | 939 | pub async fn set_email_verified(pool: &Pool, id: i64, verified: bool) -> sqlx::Result<()> { |
|
| 937 | - | sqlx::query("UPDATE accounts SET email_verified = ? WHERE id = ?").bind(verified).bind(id).execute(pool).await?; |
|
| 940 | + | sqlx::query("UPDATE accounts SET email_verified = $1 WHERE id = $2").bind(verified).bind(id).execute(pool).await?; |
|
| 938 | 941 | Ok(()) |
|
| 939 | 942 | } |
|
| 940 | 943 | ||
| 941 | 944 | /// The account with this verified email (case-insensitive), if exactly one. |
|
| 942 | 945 | pub async fn account_by_verified_email(pool: &Pool, email: &str) -> sqlx::Result<Option<Account>> { |
|
| 943 | - | let found: Vec<Account> = sqlx::query_as("SELECT * FROM accounts WHERE email_verified = 1 AND lower(email) = lower(?) LIMIT 2") |
|
| 946 | + | let found: Vec<Account> = sqlx::query_as("SELECT * FROM accounts WHERE email_verified = 1 AND lower(email) = lower($1) LIMIT 2") |
|
| 944 | 947 | .bind(email) |
|
| 945 | 948 | .fetch_all(pool) |
|
| 946 | 949 | .await?; |
|
| … | 3 unchanged lines |
||
| 950 | 953 | /// Set or clear the picture of an account (`account`) or organisation (`org`). |
|
| 951 | 954 | pub async fn set_avatar(pool: &Pool, kind: &str, id: i64, value: &str) -> sqlx::Result<()> { |
|
| 952 | 955 | let table = if kind == "org" { "organisations" } else { "accounts" }; |
|
| 953 | - | sqlx::query(&format!("UPDATE {table} SET avatar = ? WHERE id = ?")).bind(value).bind(id).execute(pool).await?; |
|
| 956 | + | sqlx::query(&format!("UPDATE {table} SET avatar = $1 WHERE id = $2")).bind(value).bind(id).execute(pool).await?; |
|
| 954 | 957 | Ok(()) |
|
| 955 | 958 | } |
modified crates/hub/src/db/activity.rs +15 −15
| … | 47 unchanged lines |
||
| 48 | 48 | /// Like `feed`, only what `actor` did when one is given (profile pages). |
|
| 49 | 49 | pub async fn feed_by(pool: &Pool, repo_ids: &[i64], actor: Option<&str>, limit: i64) -> sqlx::Result<Vec<Activity>> { |
|
| 50 | 50 | sqlx::query_as( |
|
| 51 | - | "WITH seen(id) AS (SELECT value FROM json_each(?)), ev AS ( \ |
|
| 51 | + | "WITH seen(id) AS (SELECT value FROM json_each($1)), ev AS ( \ |
|
| 52 | 52 | SELECT repo_id, at_ms, pusher AS actor, 'push' AS kind, reference AS event, '' AS title, 0 AS number FROM pushes WHERE ok = 1 \ |
|
| 53 | 53 | UNION ALL SELECT repo_id, created_ms, author, 'release', 'published', tag, 0 FROM releases \ |
|
| 54 | 54 | UNION ALL SELECT p.repo_id, e.at_ms, e.actor, 'proposal', e.event, p.title, p.number FROM proposal_events e JOIN proposals p ON p.id = e.proposal_id \ |
|
| 55 | 55 | UNION ALL SELECT i.repo_id, e.at_ms, e.actor, 'issue', e.event, i.title, i.number FROM issue_events e JOIN issues i ON i.id = e.issue_id) \ |
|
| 56 | 56 | SELECT r.owner, r.name, ev.at_ms, ev.actor, ev.kind, ev.event, ev.title, ev.number \ |
|
| 57 | 57 | FROM ev JOIN repositories r ON r.id = ev.repo_id WHERE ev.repo_id IN (SELECT id FROM seen) \ |
|
| 58 | - | AND (? IS NULL OR ev.actor = ? COLLATE NOCASE) \ |
|
| 59 | - | ORDER BY ev.at_ms DESC LIMIT ?", |
|
| 58 | + | AND ($2 IS NULL OR ev.actor = $3 COLLATE NOCASE) \ |
|
| 59 | + | ORDER BY ev.at_ms DESC LIMIT $4", |
|
| 60 | 60 | ) |
|
| 61 | 61 | .bind(ids_json(repo_ids)) |
|
| 62 | 62 | .bind(actor) |
|
| … | 22 unchanged lines |
||
| 85 | 85 | sqlx::query_as( |
|
| 86 | 86 | "SELECT r.owner, r.name, p.number, p.title, p.author, p.state, p.conflicted, p.updated_ms \ |
|
| 87 | 87 | FROM proposals p JOIN repositories r ON r.id = p.repo_id \ |
|
| 88 | - | WHERE p.repo_id IN (SELECT value FROM json_each(?)) AND p.state = 'open' \ |
|
| 89 | - | AND (p.author_id IS NULL OR p.author_id != ?) \ |
|
| 90 | - | AND NOT EXISTS (SELECT 1 FROM proposal_reviews v WHERE v.proposal_id = p.id AND v.author_id = ?) \ |
|
| 88 | + | WHERE p.repo_id IN (SELECT value FROM json_each($1)) AND p.state = 'open' \ |
|
| 89 | + | AND (p.author_id IS NULL OR p.author_id != $2) \ |
|
| 90 | + | AND NOT EXISTS (SELECT 1 FROM proposal_reviews v WHERE v.proposal_id = p.id AND v.author_id = $3) \ |
|
| 91 | 91 | ORDER BY p.updated_ms DESC", |
|
| 92 | 92 | ) |
|
| 93 | 93 | .bind(ids_json(repo_ids)) |
|
| … | 9 unchanged lines |
||
| 103 | 103 | sqlx::query_as( |
|
| 104 | 104 | "SELECT r.owner, r.name, p.number, p.title, p.author, p.state, p.conflicted, p.updated_ms \ |
|
| 105 | 105 | FROM proposals p JOIN repositories r ON r.id = p.repo_id \ |
|
| 106 | - | WHERE p.author_id = ? AND p.state IN ('open', 'draft') AND p.repo_id IN (SELECT value FROM json_each(?)) \ |
|
| 106 | + | WHERE p.author_id = $1 AND p.state IN ('open', 'draft') AND p.repo_id IN (SELECT value FROM json_each($2)) \ |
|
| 107 | 107 | ORDER BY p.updated_ms DESC", |
|
| 108 | 108 | ) |
|
| 109 | 109 | .bind(account_id) |
|
| … | 17 unchanged lines |
||
| 127 | 127 | sqlx::query_as( |
|
| 128 | 128 | "SELECT r.owner, r.name, i.number, i.title, i.updated_ms \ |
|
| 129 | 129 | FROM issues i JOIN issue_assignees a ON a.issue_id = i.id JOIN repositories r ON r.id = i.repo_id \ |
|
| 130 | - | WHERE a.account_id = ? AND i.state = 'open' AND i.repo_id IN (SELECT value FROM json_each(?)) \ |
|
| 130 | + | WHERE a.account_id = $1 AND i.state = 'open' AND i.repo_id IN (SELECT value FROM json_each($2)) \ |
|
| 131 | 131 | ORDER BY i.updated_ms DESC", |
|
| 132 | 132 | ) |
|
| 133 | 133 | .bind(account_id) |
|
| … | 16 unchanged lines |
||
| 150 | 150 | /// The events since `since_ms` in `repo_ids`, newest first, at most `limit`. |
|
| 151 | 151 | pub async fn sky(pool: &Pool, repo_ids: &[i64], since_ms: i64, limit: i64) -> sqlx::Result<Vec<SkyEvent>> { |
|
| 152 | 152 | sqlx::query_as( |
|
| 153 | - | "WITH seen(id) AS (SELECT value FROM json_each(?)), ev AS ( \ |
|
| 153 | + | "WITH seen(id) AS (SELECT value FROM json_each($1)), ev AS ( \ |
|
| 154 | 154 | SELECT repo_id, at_ms, 0 AS kind, MAX(changes, 1) AS n FROM pushes WHERE ok = 1 \ |
|
| 155 | 155 | UNION ALL SELECT p.repo_id, e.at_ms, 1, 1 FROM proposal_events e JOIN proposals p ON p.id = e.proposal_id \ |
|
| 156 | 156 | UNION ALL SELECT i.repo_id, e.at_ms, 2, 1 FROM issue_events e JOIN issues i ON i.id = e.issue_id \ |
|
| 157 | 157 | UNION ALL SELECT repo_id, created_ms, 3, 1 FROM releases \ |
|
| 158 | 158 | UNION ALL SELECT repo_id, created_ms, 4, 1 FROM flow_runs) \ |
|
| 159 | - | SELECT repo_id, at_ms, kind, n FROM ev WHERE repo_id IN (SELECT id FROM seen) AND at_ms >= ? \ |
|
| 160 | - | ORDER BY at_ms DESC LIMIT ?", |
|
| 159 | + | SELECT repo_id, at_ms, kind, n FROM ev WHERE repo_id IN (SELECT id FROM seen) AND at_ms >= $2 \ |
|
| 160 | + | ORDER BY at_ms DESC LIMIT $3", |
|
| 161 | 161 | ) |
|
| 162 | 162 | .bind(ids_json(repo_ids)) |
|
| 163 | 163 | .bind(since_ms) |
|
| … | 10 unchanged lines |
||
| 174 | 174 | let ids = serde_json::to_string(repo_ids).unwrap_or_else(|_| "[]".into()); |
|
| 175 | 175 | sqlx::query_as( |
|
| 176 | 176 | "SELECT repo_id, at_ms, changes, via FROM pushes \ |
|
| 177 | - | WHERE account_id = ? AND ok = 1 AND changes > 0 AND at_ms >= ? AND reason NOT LIKE 'landed%' \ |
|
| 178 | - | AND repo_id IN (SELECT value FROM json_each(?)) ORDER BY at_ms", |
|
| 177 | + | WHERE account_id = $1 AND ok = 1 AND changes > 0 AND at_ms >= $2 AND reason NOT LIKE 'landed%' \ |
|
| 178 | + | AND repo_id IN (SELECT value FROM json_each($3)) ORDER BY at_ms", |
|
| 179 | 179 | ) |
|
| 180 | 180 | .bind(account_id) |
|
| 181 | 181 | .bind(since_ms) |
|
| … | 7 unchanged lines |
||
| 189 | 189 | pub async fn releases_by(pool: &Pool, account_id: i64, repo_ids: &[i64], since_ms: i64) -> sqlx::Result<Vec<(i64, String, i64)>> { |
|
| 190 | 190 | let ids = serde_json::to_string(repo_ids).unwrap_or_else(|_| "[]".into()); |
|
| 191 | 191 | sqlx::query_as( |
|
| 192 | - | "SELECT repo_id, tag, created_ms FROM releases WHERE author_id = ? AND created_ms >= ? \ |
|
| 193 | - | AND repo_id IN (SELECT value FROM json_each(?)) ORDER BY created_ms", |
|
| 192 | + | "SELECT repo_id, tag, created_ms FROM releases WHERE author_id = $1 AND created_ms >= $2 \ |
|
| 193 | + | AND repo_id IN (SELECT value FROM json_each($3)) ORDER BY created_ms", |
|
| 194 | 194 | ) |
|
| 195 | 195 | .bind(account_id) |
|
| 196 | 196 | .bind(since_ms) |
|
| … | 4 unchanged lines |
modified crates/hub/src/db/flow.rs +76 −74
| … | 146 unchanged lines |
||
| 147 | 147 | pub async fn learned_disk_need(pool: &Pool, repo_id: i64, name: &str) -> sqlx::Result<Option<i64>> { |
|
| 148 | 148 | let peak: Option<i64> = sqlx::query_scalar( |
|
| 149 | 149 | "SELECT MAX(peak_disk_mib) FROM (SELECT j.peak_disk_mib FROM flow_jobs j JOIN flow_runs r ON r.id = j.run_id \ |
|
| 150 | - | WHERE r.repo_id = ? AND j.name = ? AND j.peak_disk_mib IS NOT NULL ORDER BY j.id DESC LIMIT ?)", |
|
| 150 | + | WHERE r.repo_id = $1 AND j.name = $2 AND j.peak_disk_mib IS NOT NULL ORDER BY j.id DESC LIMIT $3)", |
|
| 151 | 151 | ) |
|
| 152 | 152 | .bind(repo_id) |
|
| 153 | 153 | .bind(name) |
|
| … | 5 unchanged lines |
||
| 159 | 159 | ||
| 160 | 160 | /// Record why job `job_id` ran where it did. |
|
| 161 | 161 | pub async fn set_placed(pool: &Pool, job_id: i64, placed: &str) -> sqlx::Result<()> { |
|
| 162 | - | sqlx::query("UPDATE flow_jobs SET placed = ? WHERE id = ?").bind(placed).bind(job_id).execute(pool).await?; |
|
| 162 | + | sqlx::query("UPDATE flow_jobs SET placed = $1 WHERE id = $2").bind(placed).bind(job_id).execute(pool).await?; |
|
| 163 | 163 | Ok(()) |
|
| 164 | 164 | } |
|
| 165 | 165 | ||
| 166 | 166 | /// Keep what a job took, as its runner reported. |
|
| 167 | 167 | pub async fn set_peaks(pool: &Pool, job_id: i64, disk_mib: Option<u64>, memory_mib: Option<u64>) -> sqlx::Result<()> { |
|
| 168 | - | sqlx::query("UPDATE flow_jobs SET peak_disk_mib = ?, peak_memory_mib = ? WHERE id = ?") |
|
| 168 | + | sqlx::query("UPDATE flow_jobs SET peak_disk_mib = $1, peak_memory_mib = $2 WHERE id = $3") |
|
| 169 | 169 | .bind(disk_mib.map(|v| v.min(i64::MAX as u64) as i64)) |
|
| 170 | 170 | .bind(memory_mib.map(|v| v.min(i64::MAX as u64) as i64)) |
|
| 171 | 171 | .bind(job_id) |
|
| … | 21 unchanged lines |
||
| 193 | 193 | let status = if r.error.is_some() { "error" } else { "queued" }; |
|
| 194 | 194 | let id: i64 = sqlx::query_scalar( |
|
| 195 | 195 | "INSERT INTO flow_runs (repo_id, number, bookmark, state, heads, pipeline_hash, status, error, actor, trigger, created_ms, finished_ms) \ |
|
| 196 | - | VALUES (?, (SELECT COALESCE(MAX(number), 0) + 1 FROM flow_runs WHERE repo_id = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id", |
|
| 196 | + | VALUES ($1, (SELECT COALESCE(MAX(number), 0) + 1 FROM flow_runs WHERE repo_id = $2), $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id", |
|
| 197 | 197 | ) |
|
| 198 | 198 | .bind(r.repo_id) |
|
| 199 | 199 | .bind(r.repo_id) |
|
| … | 12 unchanged lines |
||
| 212 | 212 | for j in jobs { |
|
| 213 | 213 | sqlx::query( |
|
| 214 | 214 | "INSERT INTO flow_jobs (run_id, name, depth, depends, spec, sandbox, runtime, status, need_disk_mib, approval, environment) \ |
|
| 215 | - | VALUES (?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?, ?)", |
|
| 215 | + | VALUES ($1, $2, $3, $4, $5, $6, $7, 'queued', $8, $9, $10)", |
|
| 216 | 216 | ) |
|
| 217 | 217 | .bind(id) |
|
| 218 | 218 | .bind(&j.name) |
|
| … | 97 unchanged lines |
||
| 316 | 316 | ||
| 317 | 317 | /// A repository's runs, newest first, numbered below `before`. |
|
| 318 | 318 | pub async fn runs_by_number(pool: &Pool, repo_id: i64, limit: i64, before: Option<i64>) -> sqlx::Result<Vec<Run>> { |
|
| 319 | - | sqlx::query_as("SELECT * FROM flow_runs WHERE repo_id = ? AND number < ? ORDER BY number DESC LIMIT ?") |
|
| 319 | + | sqlx::query_as("SELECT * FROM flow_runs WHERE repo_id = $1 AND number < $2 ORDER BY number DESC LIMIT $3") |
|
| 320 | 320 | .bind(repo_id) |
|
| 321 | 321 | .bind(before.unwrap_or(i64::MAX)) |
|
| 322 | 322 | .bind(limit) |
|
| … | 8 unchanged lines |
||
| 331 | 331 | /// that what a cancelled run would have checked is checked by the next. |
|
| 332 | 332 | pub async fn latest_on(pool: &Pool, repo_id: i64, bookmark: &str) -> sqlx::Result<Option<Run>> { |
|
| 333 | 333 | sqlx::query_as( |
|
| 334 | - | "SELECT * FROM flow_runs WHERE repo_id = ? AND bookmark = ? AND heads != '' AND status != 'cancelled' |
|
| 334 | + | "SELECT * FROM flow_runs WHERE repo_id = $1 AND bookmark = $2 AND heads != '' AND status != 'cancelled' |
|
| 335 | 335 | AND NOT (trigger = 'push' AND status IN ('queued', 'running')) ORDER BY id DESC LIMIT 1", |
|
| 336 | 336 | ) |
|
| 337 | 337 | .bind(repo_id) |
|
| … | 3 unchanged lines |
||
| 341 | 341 | } |
|
| 342 | 342 | ||
| 343 | 343 | pub async fn runs(pool: &Pool, repo_id: i64, limit: i64, before: Option<i64>) -> sqlx::Result<Vec<Run>> { |
|
| 344 | - | sqlx::query_as("SELECT * FROM flow_runs WHERE repo_id = ? AND id < ? ORDER BY id DESC LIMIT ?") |
|
| 344 | + | sqlx::query_as("SELECT * FROM flow_runs WHERE repo_id = $1 AND id < $2 ORDER BY id DESC LIMIT $3") |
|
| 345 | 345 | .bind(repo_id) |
|
| 346 | 346 | .bind(before.unwrap_or(i64::MAX)) |
|
| 347 | 347 | .bind(limit) |
|
| … | 3 unchanged lines |
||
| 351 | 351 | ||
| 352 | 352 | /// A repository's run by its number there. |
|
| 353 | 353 | pub async fn run(pool: &Pool, repo_id: i64, number: i64) -> sqlx::Result<Option<Run>> { |
|
| 354 | - | sqlx::query_as("SELECT * FROM flow_runs WHERE repo_id = ? AND number = ?").bind(repo_id).bind(number).fetch_optional(pool).await |
|
| 354 | + | sqlx::query_as("SELECT * FROM flow_runs WHERE repo_id = $1 AND number = $2").bind(repo_id).bind(number).fetch_optional(pool).await |
|
| 355 | 355 | } |
|
| 356 | 356 | ||
| 357 | 357 | pub async fn run_by_id(pool: &Pool, id: i64) -> sqlx::Result<Option<Run>> { |
|
| 358 | - | sqlx::query_as("SELECT * FROM flow_runs WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 358 | + | sqlx::query_as("SELECT * FROM flow_runs WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 359 | 359 | } |
|
| 360 | 360 | ||
| 361 | 361 | pub async fn jobs(pool: &Pool, run_id: i64) -> sqlx::Result<Vec<Job>> { |
|
| 362 | - | sqlx::query_as("SELECT * FROM flow_jobs WHERE run_id = ? ORDER BY depth, name").bind(run_id).fetch_all(pool).await |
|
| 362 | + | sqlx::query_as("SELECT * FROM flow_jobs WHERE run_id = $1 ORDER BY depth, name").bind(run_id).fetch_all(pool).await |
|
| 363 | 363 | } |
|
| 364 | 364 | ||
| 365 | 365 | pub async fn job(pool: &Pool, id: i64) -> sqlx::Result<Option<Job>> { |
|
| 366 | - | sqlx::query_as("SELECT * FROM flow_jobs WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 366 | + | sqlx::query_as("SELECT * FROM flow_jobs WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 367 | 367 | } |
|
| 368 | 368 | ||
| 369 | 369 | /// The running job a job token belongs to, while it may still be used. |
|
| 370 | 370 | pub async fn job_by_token(pool: &Pool, token_hash: &str, max_age_ms: i64) -> sqlx::Result<Option<Job>> { |
|
| 371 | - | sqlx::query_as("SELECT * FROM flow_jobs WHERE token_hash = ? AND status = 'running' AND started_ms >= ?") |
|
| 371 | + | sqlx::query_as("SELECT * FROM flow_jobs WHERE token_hash = $1 AND status = 'running' AND started_ms >= $2") |
|
| 372 | 372 | .bind(token_hash) |
|
| 373 | 373 | .bind(now_ms() - max_age_ms) |
|
| 374 | 374 | .fetch_optional(pool) |
|
| … | 5 unchanged lines |
||
| 380 | 380 | /// Let a job's token also read these repositories (its pinned subrepos). |
|
| 381 | 381 | pub async fn set_also_read(pool: &Pool, job_id: i64, repo_ids: &[i64]) -> sqlx::Result<()> { |
|
| 382 | 382 | let list: Vec<String> = repo_ids.iter().map(|i| i.to_string()).collect(); |
|
| 383 | - | sqlx::query("UPDATE flow_jobs SET also_read = ? WHERE id = ?").bind(list.join(",")).bind(job_id).execute(pool).await?; |
|
| 383 | + | sqlx::query("UPDATE flow_jobs SET also_read = $1 WHERE id = $2").bind(list.join(",")).bind(job_id).execute(pool).await?; |
|
| 384 | 384 | Ok(()) |
|
| 385 | 385 | } |
|
| 386 | 386 | ||
| … | 59 unchanged lines |
||
| 446 | 446 | if hash.len() != 64 || !flow_core::arch_ok(arch) { |
|
| 447 | 447 | continue; |
|
| 448 | 448 | } |
|
| 449 | - | sqlx::query("INSERT INTO flow_images (hash, arch, seen_ms) VALUES (?, ?, ?) ON CONFLICT (hash) DO UPDATE SET arch = excluded.arch, seen_ms = excluded.seen_ms") |
|
| 449 | + | sqlx::query("INSERT INTO flow_images (hash, arch, seen_ms) VALUES ($1, $2, $3) ON CONFLICT (hash) DO UPDATE SET arch = excluded.arch, seen_ms = excluded.seen_ms") |
|
| 450 | 450 | .bind(hash.to_ascii_lowercase()) |
|
| 451 | 451 | .bind(arch) |
|
| 452 | 452 | .bind(now_ms()) |
|
| … | 9 unchanged lines |
||
| 462 | 462 | pub async fn learn_archs_from_runs(pool: &Pool) -> sqlx::Result<u64> { |
|
| 463 | 463 | let r = sqlx::query( |
|
| 464 | 464 | "INSERT INTO flow_images (hash, arch, seen_ms) |
|
| 465 | - | SELECT lower(json_extract(j.sandbox, '$.rootfs')), MIN(r.arch), ? FROM flow_jobs j JOIN flow_runners r ON r.id = j.runner_id |
|
| 465 | + | SELECT lower(json_extract(j.sandbox, '$.rootfs')), MIN(r.arch), $1 FROM flow_jobs j JOIN flow_runners r ON r.id = j.runner_id |
|
| 466 | 466 | WHERE j.status = 'passed' AND r.arch != '' AND length(json_extract(j.sandbox, '$.rootfs')) = 64 |
|
| 467 | 467 | GROUP BY lower(json_extract(j.sandbox, '$.rootfs')) |
|
| 468 | 468 | ON CONFLICT (hash) DO NOTHING", |
|
| … | 47 unchanged lines |
||
| 516 | 516 | let queued: Vec<Job> = sqlx::query_as( |
|
| 517 | 517 | "SELECT j.* FROM flow_jobs j JOIN flow_runs r ON r.id = j.run_id JOIN repositories rep ON rep.id = r.repo_id \ |
|
| 518 | 518 | WHERE j.status = 'queued' AND j.approval != 'waiting' AND r.status IN ('queued', 'running') \ |
|
| 519 | - | AND (? = '' OR rep.owner = ?) AND (? IS NULL OR r.repo_id = ?) \ |
|
| 520 | - | AND NOT (? = '' AND ? IS NULL AND rep.flow = 'own') \ |
|
| 519 | + | AND ($1 = '' OR rep.owner = $2) AND ($3 IS NULL OR r.repo_id = $4) \ |
|
| 520 | + | AND NOT ($5 = '' AND $6 IS NULL AND rep.flow = 'own') \ |
|
| 521 | 521 | ORDER BY j.run_id, j.depth, j.id LIMIT 200", |
|
| 522 | 522 | ) |
|
| 523 | 523 | .bind(&scope.owner) |
|
| … | 21 unchanged lines |
||
| 545 | 545 | if !deps.is_empty() { |
|
| 546 | 546 | let mut passed = 0i64; |
|
| 547 | 547 | for d in &deps { |
|
| 548 | - | let st: Option<String> = sqlx::query_scalar("SELECT status FROM flow_jobs WHERE run_id = ? AND name = ?") |
|
| 548 | + | let st: Option<String> = sqlx::query_scalar("SELECT status FROM flow_jobs WHERE run_id = $1 AND name = $2") |
|
| 549 | 549 | .bind(j.run_id) |
|
| 550 | 550 | .bind(d) |
|
| 551 | 551 | .fetch_optional(&mut *tx) |
|
| … | 11 unchanged lines |
||
| 563 | 563 | } |
|
| 564 | 564 | let now = now_ms(); |
|
| 565 | 565 | let done = sqlx::query( |
|
| 566 | - | "UPDATE flow_jobs SET status = 'running', runner = ?, token_hash = ?, started_ms = ?, waiting = '' WHERE id = ? AND status = 'queued'", |
|
| 566 | + | "UPDATE flow_jobs SET status = 'running', runner = $1, token_hash = $2, started_ms = $3, waiting = '' WHERE id = $4 AND status = 'queued'", |
|
| 567 | 567 | ) |
|
| 568 | 568 | .bind(runner) |
|
| 569 | 569 | .bind(token_hash) |
|
| … | 2 unchanged lines |
||
| 572 | 572 | .execute(&mut *tx) |
|
| 573 | 573 | .await?; |
|
| 574 | 574 | if done.rows_affected() == 1 { |
|
| 575 | - | sqlx::query("UPDATE flow_runs SET status = 'running' WHERE id = ? AND status = 'queued'") |
|
| 575 | + | sqlx::query("UPDATE flow_runs SET status = 'running' WHERE id = $1 AND status = 'queued'") |
|
| 576 | 576 | .bind(j.run_id) |
|
| 577 | 577 | .execute(&mut *tx) |
|
| 578 | 578 | .await?; |
|
| … | 11 unchanged lines |
||
| 590 | 590 | ||
| 591 | 591 | /// The jobs runner `runner_id` declined with this profile, recently. |
|
| 592 | 592 | pub async fn declined_by(pool: &Pool, runner_id: i64, profile: &str) -> sqlx::Result<std::collections::HashSet<i64>> { |
|
| 593 | - | let rows: Vec<(i64,)> = sqlx::query_as("SELECT job_id FROM flow_declines WHERE runner_id = ? AND profile = ? AND ms > ?") |
|
| 593 | + | let rows: Vec<(i64,)> = sqlx::query_as("SELECT job_id FROM flow_declines WHERE runner_id = $1 AND profile = $2 AND ms > $3") |
|
| 594 | 594 | .bind(runner_id) |
|
| 595 | 595 | .bind(profile) |
|
| 596 | 596 | .bind(now_ms() - DECLINE_HOLDS_MS) |
|
| … | 27 unchanged lines |
||
| 624 | 624 | let mut tx = super::begin_write(pool).await?; |
|
| 625 | 625 | let back = sqlx::query( |
|
| 626 | 626 | "UPDATE flow_jobs SET status = 'queued', runner = '', token_hash = '', started_ms = NULL, runner_id = NULL \ |
|
| 627 | - | WHERE id = ? AND status = 'running' AND runner_id = ?", |
|
| 627 | + | WHERE id = $1 AND status = 'running' AND runner_id = $2", |
|
| 628 | 628 | ) |
|
| 629 | 629 | .bind(job_id) |
|
| 630 | 630 | .bind(runner_id) |
|
| … | 4 unchanged lines |
||
| 635 | 635 | return Ok(None); |
|
| 636 | 636 | } |
|
| 637 | 637 | sqlx::query( |
|
| 638 | - | "INSERT INTO flow_declines (job_id, runner_id, runner, profile, reason, ms) VALUES (?, ?, ?, ?, ?, ?) \ |
|
| 638 | + | "INSERT INTO flow_declines (job_id, runner_id, runner, profile, reason, ms) VALUES ($1, $2, $3, $4, $5, $6) \ |
|
| 639 | 639 | ON CONFLICT (job_id, runner_id) DO UPDATE SET runner = excluded.runner, profile = excluded.profile, reason = excluded.reason, ms = excluded.ms", |
|
| 640 | 640 | ) |
|
| 641 | 641 | .bind(job_id) |
|
| … | 5 unchanged lines |
||
| 647 | 647 | .execute(&mut *tx) |
|
| 648 | 648 | .await?; |
|
| 649 | 649 | let all: Vec<DeclineRow> = |
|
| 650 | - | sqlx::query_as("SELECT job_id, runner_id, runner, reason, ms FROM flow_declines WHERE job_id = ? ORDER BY ms") |
|
| 650 | + | sqlx::query_as("SELECT job_id, runner_id, runner, reason, ms FROM flow_declines WHERE job_id = $1 ORDER BY ms") |
|
| 651 | 651 | .bind(job_id) |
|
| 652 | 652 | .fetch_all(&mut *tx) |
|
| 653 | 653 | .await?; |
|
| … | 3 unchanged lines |
||
| 657 | 657 | ||
| 658 | 658 | /// The declines of job `job_id`, oldest first. |
|
| 659 | 659 | pub async fn declines_of_job(pool: &Pool, job_id: i64) -> sqlx::Result<Vec<DeclineRow>> { |
|
| 660 | - | sqlx::query_as("SELECT job_id, runner_id, runner, reason, ms FROM flow_declines WHERE job_id = ? ORDER BY ms") |
|
| 660 | + | sqlx::query_as("SELECT job_id, runner_id, runner, reason, ms FROM flow_declines WHERE job_id = $1 ORDER BY ms") |
|
| 661 | 661 | .bind(job_id) |
|
| 662 | 662 | .fetch_all(pool) |
|
| 663 | 663 | .await |
|
| … | 15 unchanged lines |
||
| 679 | 679 | sqlx::query_as( |
|
| 680 | 680 | "SELECT d.job_id, j.name AS job, r.number AS run_number, rep.owner, rep.name, d.reason, d.ms \ |
|
| 681 | 681 | FROM flow_declines d JOIN flow_jobs j ON j.id = d.job_id JOIN flow_runs r ON r.id = j.run_id \ |
|
| 682 | - | JOIN repositories rep ON rep.id = r.repo_id WHERE d.runner_id = ? ORDER BY d.ms DESC LIMIT ?", |
|
| 682 | + | JOIN repositories rep ON rep.id = r.repo_id WHERE d.runner_id = $1 ORDER BY d.ms DESC LIMIT $2", |
|
| 683 | 683 | ) |
|
| 684 | 684 | .bind(runner_id) |
|
| 685 | 685 | .bind(limit) |
|
| … | 14 unchanged lines |
||
| 700 | 700 | for j in queued { |
|
| 701 | 701 | let mut ready = true; |
|
| 702 | 702 | for d in j.depends_on() { |
|
| 703 | - | let st: Option<String> = sqlx::query_scalar("SELECT status FROM flow_jobs WHERE run_id = ? AND name = ?") |
|
| 703 | + | let st: Option<String> = sqlx::query_scalar("SELECT status FROM flow_jobs WHERE run_id = $1 AND name = $2") |
|
| 704 | 704 | .bind(j.run_id) |
|
| 705 | 705 | .bind(&d) |
|
| 706 | 706 | .fetch_optional(pool) |
|
| … | 7 unchanged lines |
||
| 714 | 714 | continue; |
|
| 715 | 715 | } |
|
| 716 | 716 | let repo: Option<(String, i64)> = |
|
| 717 | - | sqlx::query_as("SELECT rep.owner, rep.id FROM flow_runs r JOIN repositories rep ON rep.id = r.repo_id WHERE r.id = ?") |
|
| 717 | + | sqlx::query_as("SELECT rep.owner, rep.id FROM flow_runs r JOIN repositories rep ON rep.id = r.repo_id WHERE r.id = $1") |
|
| 718 | 718 | .bind(j.run_id) |
|
| 719 | 719 | .fetch_optional(pool) |
|
| 720 | 720 | .await?; |
|
| … | 5 unchanged lines |
||
| 726 | 726 | } |
|
| 727 | 727 | ||
| 728 | 728 | pub async fn set_waiting(pool: &Pool, job_id: i64, waiting: &str) -> sqlx::Result<()> { |
|
| 729 | - | sqlx::query("UPDATE flow_jobs SET waiting = ? WHERE id = ? AND waiting <> ?") |
|
| 729 | + | sqlx::query("UPDATE flow_jobs SET waiting = $1 WHERE id = $2 AND waiting <> $3") |
|
| 730 | 730 | .bind(waiting) |
|
| 731 | 731 | .bind(job_id) |
|
| 732 | 732 | .bind(waiting) |
|
| … | 5 unchanged lines |
||
| 738 | 738 | /// Append records; returns how many were kept (the rest exceed the cap). |
|
| 739 | 739 | pub async fn append_log(pool: &Pool, job_id: i64, records: &[flow_core::api::Record], cap: i64) -> sqlx::Result<usize> { |
|
| 740 | 740 | let mut tx = super::begin_write(pool).await?; |
|
| 741 | - | let count: i64 = sqlx::query_scalar("SELECT log_count FROM flow_jobs WHERE id = ?").bind(job_id).fetch_one(&mut *tx).await?; |
|
| 741 | + | let count: i64 = sqlx::query_scalar("SELECT log_count FROM flow_jobs WHERE id = $1").bind(job_id).fetch_one(&mut *tx).await?; |
|
| 742 | 742 | let room = (cap - count).max(0) as usize; |
|
| 743 | 743 | let keep = &records[..records.len().min(room)]; |
|
| 744 | 744 | for (i, r) in keep.iter().enumerate() { |
|
| 745 | - | sqlx::query("INSERT INTO flow_logs (job_id, seq, t, stream, text) VALUES (?, ?, ?, ?, ?)") |
|
| 745 | + | sqlx::query("INSERT INTO flow_logs (job_id, seq, t, stream, text) VALUES ($1, $2, $3, $4, $5)") |
|
| 746 | 746 | .bind(job_id) |
|
| 747 | 747 | .bind(count + i as i64 + 1) |
|
| 748 | 748 | .bind(r.t as i64) |
|
| … | 2 unchanged lines |
||
| 751 | 751 | .execute(&mut *tx) |
|
| 752 | 752 | .await?; |
|
| 753 | 753 | } |
|
| 754 | - | sqlx::query("UPDATE flow_jobs SET log_count = log_count + ? WHERE id = ?") |
|
| 754 | + | sqlx::query("UPDATE flow_jobs SET log_count = log_count + $1 WHERE id = $2") |
|
| 755 | 755 | .bind(keep.len() as i64) |
|
| 756 | 756 | .bind(job_id) |
|
| 757 | 757 | .execute(&mut *tx) |
|
| … | 3 unchanged lines |
||
| 761 | 761 | } |
|
| 762 | 762 | ||
| 763 | 763 | pub async fn log(pool: &Pool, job_id: i64, after: i64, limit: i64) -> sqlx::Result<Vec<LogLine>> { |
|
| 764 | - | sqlx::query_as("SELECT seq, t, stream, text FROM flow_logs WHERE job_id = ? AND seq > ? ORDER BY seq LIMIT ?") |
|
| 764 | + | sqlx::query_as("SELECT seq, t, stream, text FROM flow_logs WHERE job_id = $1 AND seq > $2 ORDER BY seq LIMIT $3") |
|
| 765 | 765 | .bind(job_id) |
|
| 766 | 766 | .bind(after) |
|
| 767 | 767 | .bind(limit) |
|
| … | 7 unchanged lines |
||
| 775 | 775 | pub async fn running_past_limit(pool: &Pool, now: i64, default_s: u64, grace_ms: i64) -> sqlx::Result<Vec<Job>> { |
|
| 776 | 776 | sqlx::query_as( |
|
| 777 | 777 | "SELECT * FROM flow_jobs WHERE status = 'running' \ |
|
| 778 | - | AND COALESCE(started_ms, 0) + (CASE WHEN timeout_s > 0 THEN timeout_s ELSE ? END) * 1000 + ? < ? ORDER BY started_ms", |
|
| 778 | + | AND COALESCE(started_ms, 0) + (CASE WHEN timeout_s > 0 THEN timeout_s ELSE $1 END) * 1000 + $2 < $3 ORDER BY started_ms", |
|
| 779 | 779 | ) |
|
| 780 | 780 | .bind(default_s as i64) |
|
| 781 | 781 | .bind(grace_ms) |
|
| … | 4 unchanged lines |
||
| 786 | 786 | ||
| 787 | 787 | /// Record the limit a claimed job runs under. |
|
| 788 | 788 | pub async fn set_job_timeout(pool: &Pool, job_id: i64, timeout_s: u64) -> sqlx::Result<()> { |
|
| 789 | - | sqlx::query("UPDATE flow_jobs SET timeout_s = ? WHERE id = ?").bind(timeout_s as i64).bind(job_id).execute(pool).await?; |
|
| 789 | + | sqlx::query("UPDATE flow_jobs SET timeout_s = $1 WHERE id = $2").bind(timeout_s as i64).bind(job_id).execute(pool).await?; |
|
| 790 | 790 | Ok(()) |
|
| 791 | 791 | } |
|
| 792 | 792 | ||
| 793 | 793 | pub async fn running_since_before(pool: &Pool, ms: i64) -> sqlx::Result<Vec<Job>> { |
|
| 794 | - | sqlx::query_as("SELECT * FROM flow_jobs WHERE status = 'running' AND COALESCE(started_ms, 0) < ? ORDER BY started_ms") |
|
| 794 | + | sqlx::query_as("SELECT * FROM flow_jobs WHERE status = 'running' AND COALESCE(started_ms, 0) < $1 ORDER BY started_ms") |
|
| 795 | 795 | .bind(ms) |
|
| 796 | 796 | .fetch_all(pool) |
|
| 797 | 797 | .await |
|
| … | 1 unchanged lines |
||
| 799 | 799 | ||
| 800 | 800 | /// The jobs running on the runner with this id. |
|
| 801 | 801 | pub async fn running_on(pool: &Pool, runner_id: i64) -> sqlx::Result<Vec<Job>> { |
|
| 802 | - | sqlx::query_as("SELECT * FROM flow_jobs WHERE status = 'running' AND runner_id = ? ORDER BY id").bind(runner_id).fetch_all(pool).await |
|
| 802 | + | sqlx::query_as("SELECT * FROM flow_jobs WHERE status = 'running' AND runner_id = $1 ORDER BY id").bind(runner_id).fetch_all(pool).await |
|
| 803 | 803 | } |
|
| 804 | 804 | ||
| 805 | 805 | pub struct Outcome<'a> { |
|
| … | 9 unchanged lines |
||
| 815 | 815 | pub async fn finish(pool: &Pool, job_id: i64, o: Outcome<'_>) -> sqlx::Result<Vec<Job>> { |
|
| 816 | 816 | let mut tx = super::begin_write(pool).await?; |
|
| 817 | 817 | let now = now_ms(); |
|
| 818 | - | let j: Job = sqlx::query_as("SELECT * FROM flow_jobs WHERE id = ?").bind(job_id).fetch_one(&mut *tx).await?; |
|
| 819 | - | sqlx::query("UPDATE flow_jobs SET status = ?, exit_code = ?, error = ?, receipt = ?, receipt_hash = ?, finished_ms = ?, token_hash = '' WHERE id = ?") |
|
| 818 | + | let j: Job = sqlx::query_as("SELECT * FROM flow_jobs WHERE id = $1").bind(job_id).fetch_one(&mut *tx).await?; |
|
| 819 | + | sqlx::query("UPDATE flow_jobs SET status = $1, exit_code = $2, error = $3, receipt = $4, receipt_hash = $5, finished_ms = $6, token_hash = '' WHERE id = $7") |
|
| 820 | 820 | .bind(o.status) |
|
| 821 | 821 | .bind(o.exit_code) |
|
| 822 | 822 | .bind(o.error) |
|
| … | 12 unchanged lines |
||
| 835 | 835 | .unwrap_or_default(); |
|
| 836 | 836 | if rootfs.len() == 64 { |
|
| 837 | 837 | sqlx::query( |
|
| 838 | - | "INSERT INTO flow_images (hash, arch, seen_ms) SELECT ?, r.arch, ? FROM flow_jobs j JOIN flow_runners r ON r.id = j.runner_id \ |
|
| 839 | - | WHERE j.id = ? AND r.arch != '' ON CONFLICT (hash) DO NOTHING", |
|
| 838 | + | "INSERT INTO flow_images (hash, arch, seen_ms) SELECT $1, r.arch, $2 FROM flow_jobs j JOIN flow_runners r ON r.id = j.runner_id \ |
|
| 839 | + | WHERE j.id = $3 AND r.arch != '' ON CONFLICT (hash) DO NOTHING", |
|
| 840 | 840 | ) |
|
| 841 | 841 | .bind(&rootfs) |
|
| 842 | 842 | .bind(now) |
|
| … | 5 unchanged lines |
||
| 848 | 848 | let mut changed = vec![job_id]; |
|
| 849 | 849 | // Dependents of anything that did not pass are skipped, transitively. |
|
| 850 | 850 | loop { |
|
| 851 | - | let all: Vec<Job> = sqlx::query_as("SELECT * FROM flow_jobs WHERE run_id = ?").bind(j.run_id).fetch_all(&mut *tx).await?; |
|
| 851 | + | let all: Vec<Job> = sqlx::query_as("SELECT * FROM flow_jobs WHERE run_id = $1").bind(j.run_id).fetch_all(&mut *tx).await?; |
|
| 852 | 852 | let blocked: Vec<i64> = all |
|
| 853 | 853 | .iter() |
|
| 854 | 854 | .filter(|x| x.status == "queued") |
|
| … | 8 unchanged lines |
||
| 863 | 863 | break; |
|
| 864 | 864 | } |
|
| 865 | 865 | for id in blocked { |
|
| 866 | - | sqlx::query("UPDATE flow_jobs SET status = 'skipped', finished_ms = ? WHERE id = ?") |
|
| 866 | + | sqlx::query("UPDATE flow_jobs SET status = 'skipped', finished_ms = $1 WHERE id = $2") |
|
| 867 | 867 | .bind(now) |
|
| 868 | 868 | .bind(id) |
|
| 869 | 869 | .execute(&mut *tx) |
|
| … | 1 unchanged lines |
||
| 871 | 871 | changed.push(id); |
|
| 872 | 872 | } |
|
| 873 | 873 | } |
|
| 874 | - | let all: Vec<Job> = sqlx::query_as("SELECT * FROM flow_jobs WHERE run_id = ?").bind(j.run_id).fetch_all(&mut *tx).await?; |
|
| 874 | + | let all: Vec<Job> = sqlx::query_as("SELECT * FROM flow_jobs WHERE run_id = $1").bind(j.run_id).fetch_all(&mut *tx).await?; |
|
| 875 | 875 | if all.iter().all(Job::done) { |
|
| 876 | 876 | let status = if all.iter().all(|x| x.status == "passed") { "passed" } else { "failed" }; |
|
| 877 | - | sqlx::query("UPDATE flow_runs SET status = ?, finished_ms = ? WHERE id = ?") |
|
| 877 | + | sqlx::query("UPDATE flow_runs SET status = $1, finished_ms = $2 WHERE id = $3") |
|
| 878 | 878 | .bind(status) |
|
| 879 | 879 | .bind(now) |
|
| 880 | 880 | .bind(j.run_id) |
|
| … | 16 unchanged lines |
||
| 897 | 897 | pub async fn cancel_run(pool: &Pool, run_id: i64, why: &str) -> sqlx::Result<Vec<Job>> { |
|
| 898 | 898 | let now = now_ms(); |
|
| 899 | 899 | let mut tx = pool.begin().await?; |
|
| 900 | - | let ids: Vec<(i64,)> = sqlx::query_as("SELECT id FROM flow_jobs WHERE run_id = ? AND status IN ('queued', 'running')") |
|
| 900 | + | let ids: Vec<(i64,)> = sqlx::query_as("SELECT id FROM flow_jobs WHERE run_id = $1 AND status IN ('queued', 'running')") |
|
| 901 | 901 | .bind(run_id) |
|
| 902 | 902 | .fetch_all(&mut *tx) |
|
| 903 | 903 | .await?; |
|
| 904 | 904 | sqlx::query( |
|
| 905 | - | "UPDATE flow_jobs SET status = 'cancelled', error = ?, finished_ms = ?, token_hash = '' |
|
| 906 | - | WHERE run_id = ? AND status IN ('queued', 'running')", |
|
| 905 | + | "UPDATE flow_jobs SET status = 'cancelled', error = $1, finished_ms = $2, token_hash = '' |
|
| 906 | + | WHERE run_id = $3 AND status IN ('queued', 'running')", |
|
| 907 | 907 | ) |
|
| 908 | 908 | .bind(why) |
|
| 909 | 909 | .bind(now) |
|
| 910 | 910 | .bind(run_id) |
|
| 911 | 911 | .execute(&mut *tx) |
|
| 912 | 912 | .await?; |
|
| 913 | - | sqlx::query("UPDATE flow_runs SET status = 'cancelled', finished_ms = ? WHERE id = ? AND status IN ('queued', 'running')") |
|
| 913 | + | sqlx::query("UPDATE flow_runs SET status = 'cancelled', finished_ms = $1 WHERE id = $2 AND status IN ('queued', 'running')") |
|
| 914 | 914 | .bind(now) |
|
| 915 | 915 | .bind(run_id) |
|
| 916 | 916 | .execute(&mut *tx) |
|
| … | 14 unchanged lines |
||
| 931 | 931 | /// depend on keep their results. The jobs reset; none when there is no |
|
| 932 | 932 | /// such job. |
|
| 933 | 933 | pub async fn rerun_job(pool: &Pool, run_id: i64, name: &str) -> sqlx::Result<Vec<Job>> { |
|
| 934 | - | let all: Vec<Job> = sqlx::query_as("SELECT * FROM flow_jobs WHERE run_id = ?").bind(run_id).fetch_all(pool).await?; |
|
| 934 | + | let all: Vec<Job> = sqlx::query_as("SELECT * FROM flow_jobs WHERE run_id = $1").bind(run_id).fetch_all(pool).await?; |
|
| 935 | 935 | if !all.iter().any(|j| j.name == name) { |
|
| 936 | 936 | return Ok(Vec::new()); |
|
| 937 | 937 | } |
|
| … | 13 unchanged lines |
||
| 951 | 951 | let mut tx = pool.begin().await?; |
|
| 952 | 952 | for id in &ids { |
|
| 953 | 953 | for table in ["flow_logs", "flow_artifacts", "flow_declines", "test_reports", "coverage_reports", "reports"] { |
|
| 954 | - | sqlx::query(&format!("DELETE FROM {table} WHERE job_id = ?")).bind(id).execute(&mut *tx).await?; |
|
| 954 | + | sqlx::query(&format!("DELETE FROM {table} WHERE job_id = $1")).bind(id).execute(&mut *tx).await?; |
|
| 955 | 955 | } |
|
| 956 | 956 | sqlx::query( |
|
| 957 | 957 | "UPDATE flow_jobs SET status = 'queued', runner = '', token_hash = '', started_ms = NULL, finished_ms = NULL, |
|
| 958 | 958 | exit_code = NULL, error = '', receipt = '', receipt_hash = '', log_count = 0, also_read = '', receipt_attest = '', |
|
| 959 | 959 | receipt_signer = '', receipt_note = '', runner_id = NULL, waiting = '', placed = '', peak_disk_mib = NULL, |
|
| 960 | 960 | peak_memory_mib = NULL, approval = CASE WHEN approval = '' THEN '' ELSE 'waiting' END, approved_by = '', |
|
| 961 | - | approved_ms = NULL, approval_seal = '' WHERE id = ?", |
|
| 961 | + | approved_ms = NULL, approval_seal = '' WHERE id = $1", |
|
| 962 | 962 | ) |
|
| 963 | 963 | .bind(id) |
|
| 964 | 964 | .execute(&mut *tx) |
|
| 965 | 965 | .await?; |
|
| 966 | 966 | } |
|
| 967 | - | sqlx::query("UPDATE flow_runs SET status = 'running', finished_ms = NULL WHERE id = ?").bind(run_id).execute(&mut *tx).await?; |
|
| 967 | + | sqlx::query("UPDATE flow_runs SET status = 'running', finished_ms = NULL WHERE id = $1").bind(run_id).execute(&mut *tx).await?; |
|
| 968 | 968 | tx.commit().await?; |
|
| 969 | 969 | let mut out = Vec::new(); |
|
| 970 | 970 | for id in ids { |
|
| … | 7 unchanged lines |
||
| 978 | 978 | /// The push runs of `bookmark` still going, older than run `number`. |
|
| 979 | 979 | pub async fn live_push_runs_before(pool: &Pool, repo_id: i64, bookmark: &str, number: i64) -> sqlx::Result<Vec<Run>> { |
|
| 980 | 980 | sqlx::query_as( |
|
| 981 | - | "SELECT * FROM flow_runs WHERE repo_id = ? AND bookmark = ? AND number < ? AND trigger = 'push' |
|
| 981 | + | "SELECT * FROM flow_runs WHERE repo_id = $1 AND bookmark = $2 AND number < $3 AND trigger = 'push' |
|
| 982 | 982 | AND status IN ('queued', 'running') ORDER BY id", |
|
| 983 | 983 | ) |
|
| 984 | 984 | .bind(repo_id) |
|
| … | 5 unchanged lines |
||
| 990 | 990 | ||
| 991 | 991 | /// The newest run of exactly this state (heads joined with `+`, sorted). |
|
| 992 | 992 | pub async fn latest_for_heads(pool: &Pool, repo_id: i64, heads: &str) -> sqlx::Result<Option<Run>> { |
|
| 993 | - | sqlx::query_as("SELECT * FROM flow_runs WHERE repo_id = ? AND heads = ? ORDER BY id DESC LIMIT 1") |
|
| 993 | + | sqlx::query_as("SELECT * FROM flow_runs WHERE repo_id = $1 AND heads = $2 ORDER BY id DESC LIMIT 1") |
|
| 994 | 994 | .bind(repo_id) |
|
| 995 | 995 | .bind(heads) |
|
| 996 | 996 | .fetch_optional(pool) |
|
| … | 2 unchanged lines |
||
| 999 | 999 | ||
| 1000 | 1000 | /// Record what became of job `job_id`'s signed receipt. |
|
| 1001 | 1001 | pub async fn set_receipt(pool: &Pool, job_id: i64, attest: &str, signer: &str, note: &str) -> sqlx::Result<()> { |
|
| 1002 | - | sqlx::query("UPDATE flow_jobs SET receipt_attest = ?, receipt_signer = ?, receipt_note = ? WHERE id = ?") |
|
| 1002 | + | sqlx::query("UPDATE flow_jobs SET receipt_attest = $1, receipt_signer = $2, receipt_note = $3 WHERE id = $4") |
|
| 1003 | 1003 | .bind(attest) |
|
| 1004 | 1004 | .bind(signer) |
|
| 1005 | 1005 | .bind(note) |
|
| … | 17 unchanged lines |
||
| 1023 | 1023 | } |
|
| 1024 | 1024 | ||
| 1025 | 1025 | pub async fn add_artifact(pool: &Pool, a: &Artifact) -> sqlx::Result<()> { |
|
| 1026 | - | sqlx::query("INSERT OR REPLACE INTO flow_artifacts (job_id, name, path, blob, size, content, created_ms) VALUES (?, ?, ?, ?, ?, ?, ?)") |
|
| 1027 | - | .bind(a.job_id) |
|
| 1028 | - | .bind(&a.name) |
|
| 1029 | - | .bind(&a.path) |
|
| 1030 | - | .bind(&a.blob) |
|
| 1031 | - | .bind(a.size) |
|
| 1032 | - | .bind(&a.content) |
|
| 1033 | - | .bind(a.created_ms) |
|
| 1034 | - | .execute(pool) |
|
| 1035 | - | .await?; |
|
| 1026 | + | sqlx::query( |
|
| 1027 | + | "INSERT OR REPLACE INTO flow_artifacts (job_id, name, path, blob, size, content, created_ms) VALUES ($1, $2, $3, $4, $5, $6, $7)", |
|
| 1028 | + | ) |
|
| 1029 | + | .bind(a.job_id) |
|
| 1030 | + | .bind(&a.name) |
|
| 1031 | + | .bind(&a.path) |
|
| 1032 | + | .bind(&a.blob) |
|
| 1033 | + | .bind(a.size) |
|
| 1034 | + | .bind(&a.content) |
|
| 1035 | + | .bind(a.created_ms) |
|
| 1036 | + | .execute(pool) |
|
| 1037 | + | .await?; |
|
| 1036 | 1038 | Ok(()) |
|
| 1037 | 1039 | } |
|
| 1038 | 1040 | ||
| … | 1 unchanged lines |
||
| 1040 | 1042 | /// keep too (#126): the database is the only thing that names them. |
|
| 1041 | 1043 | pub async fn artifact_blobs(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<String>> { |
|
| 1042 | 1044 | sqlx::query_scalar( |
|
| 1043 | - | "SELECT DISTINCT a.blob FROM flow_artifacts a JOIN flow_jobs j ON j.id = a.job_id JOIN flow_runs r ON r.id = j.run_id WHERE r.repo_id = ?", |
|
| 1045 | + | "SELECT DISTINCT a.blob FROM flow_artifacts a JOIN flow_jobs j ON j.id = a.job_id JOIN flow_runs r ON r.id = j.run_id WHERE r.repo_id = $1", |
|
| 1044 | 1046 | ) |
|
| 1045 | 1047 | .bind(repo_id) |
|
| 1046 | 1048 | .fetch_all(pool) |
|
| … | 2 unchanged lines |
||
| 1049 | 1051 | ||
| 1050 | 1052 | /// Job `job_id`'s artifacts, by name. |
|
| 1051 | 1053 | pub async fn artifacts(pool: &Pool, job_id: i64) -> sqlx::Result<Vec<Artifact>> { |
|
| 1052 | - | sqlx::query_as("SELECT * FROM flow_artifacts WHERE job_id = ? ORDER BY name").bind(job_id).fetch_all(pool).await |
|
| 1054 | + | sqlx::query_as("SELECT * FROM flow_artifacts WHERE job_id = $1 ORDER BY name").bind(job_id).fetch_all(pool).await |
|
| 1053 | 1055 | } |
|
| 1054 | 1056 | ||
| 1055 | 1057 | /// Where an artifact with given content came from: the run and job that |
|
| … | 14 unchanged lines |
||
| 1070 | 1072 | sqlx::query_as( |
|
| 1071 | 1073 | "SELECT r.number AS run_number, j.name AS job, a.name AS name, j.receipt_attest, j.receipt_signer \ |
|
| 1072 | 1074 | FROM flow_artifacts a JOIN flow_jobs j ON j.id = a.job_id JOIN flow_runs r ON r.id = j.run_id \ |
|
| 1073 | - | WHERE r.repo_id = ? AND a.content = ? AND j.status = 'passed' \ |
|
| 1075 | + | WHERE r.repo_id = $1 AND a.content = $2 AND j.status = 'passed' \ |
|
| 1074 | 1076 | ORDER BY (j.receipt_attest != '') DESC, r.number DESC LIMIT 1", |
|
| 1075 | 1077 | ) |
|
| 1076 | 1078 | .bind(repo_id) |
|
| … | 6 unchanged lines |
||
| 1083 | 1085 | /// whether it was. |
|
| 1084 | 1086 | pub async fn approve_job(pool: &Pool, id: i64, who: &str, seal: &str) -> sqlx::Result<bool> { |
|
| 1085 | 1087 | let r = sqlx::query( |
|
| 1086 | - | "UPDATE flow_jobs SET approval = 'approved', approved_by = ?, approved_ms = ?, approval_seal = ? \ |
|
| 1087 | - | WHERE id = ? AND status = 'queued' AND approval = 'waiting'", |
|
| 1088 | + | "UPDATE flow_jobs SET approval = 'approved', approved_by = $1, approved_ms = $2, approval_seal = $3 \ |
|
| 1089 | + | WHERE id = $4 AND status = 'queued' AND approval = 'waiting'", |
|
| 1088 | 1090 | ) |
|
| 1089 | 1091 | .bind(who) |
|
| 1090 | 1092 | .bind(now_ms()) |
|
| … | 6 unchanged lines |
||
| 1097 | 1099 | ||
| 1098 | 1100 | /// Note who rejected job `id` (it is then finished as cancelled). |
|
| 1099 | 1101 | pub async fn note_rejection(pool: &Pool, id: i64, who: &str) -> sqlx::Result<()> { |
|
| 1100 | - | sqlx::query("UPDATE flow_jobs SET approved_by = ?, approved_ms = ? WHERE id = ?") |
|
| 1102 | + | sqlx::query("UPDATE flow_jobs SET approved_by = $1, approved_ms = $2 WHERE id = $3") |
|
| 1101 | 1103 | .bind(who) |
|
| 1102 | 1104 | .bind(now_ms()) |
|
| 1103 | 1105 | .bind(id) |
|
| … | 30 unchanged lines |
||
| 1134 | 1136 | sqlx::query_as( |
|
| 1135 | 1137 | "SELECT j.environment, j.name AS job, r.number AS run_number, r.state, r.bookmark, r.actor, j.approved_by, \ |
|
| 1136 | 1138 | COALESCE(j.finished_ms, 0) AS finished_ms FROM flow_jobs j JOIN flow_runs r ON r.id = j.run_id \ |
|
| 1137 | - | WHERE r.repo_id = ? AND j.environment != '' AND j.status = 'passed' ORDER BY j.finished_ms DESC LIMIT ?", |
|
| 1139 | + | WHERE r.repo_id = $1 AND j.environment != '' AND j.status = 'passed' ORDER BY j.finished_ms DESC LIMIT $2", |
|
| 1138 | 1140 | ) |
|
| 1139 | 1141 | .bind(repo_id) |
|
| 1140 | 1142 | .bind(limit) |
|
| … | 3 unchanged lines |
modified crates/hub/src/db/hooks.rs +42 −38
| … | 80 unchanged lines |
||
| 81 | 81 | } |
|
| 82 | 82 | ||
| 83 | 83 | pub async fn hooks_for_repo(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<Hook>> { |
|
| 84 | - | sqlx::query_as("SELECT * FROM hooks WHERE repo_id = ? ORDER BY id").bind(repo_id).fetch_all(pool).await |
|
| 84 | + | sqlx::query_as("SELECT * FROM hooks WHERE repo_id = $1 ORDER BY id").bind(repo_id).fetch_all(pool).await |
|
| 85 | 85 | } |
|
| 86 | 86 | ||
| 87 | 87 | pub async fn hooks_for_org(pool: &Pool, org_id: i64) -> sqlx::Result<Vec<Hook>> { |
|
| 88 | - | sqlx::query_as("SELECT * FROM hooks WHERE org_id = ? ORDER BY id").bind(org_id).fetch_all(pool).await |
|
| 88 | + | sqlx::query_as("SELECT * FROM hooks WHERE org_id = $1 ORDER BY id").bind(org_id).fetch_all(pool).await |
|
| 89 | 89 | } |
|
| 90 | 90 | ||
| 91 | 91 | /// Every hook that should hear about an event in a repository: its own |
|
| … | 7 unchanged lines |
||
| 99 | 99 | } |
|
| 100 | 100 | ||
| 101 | 101 | pub async fn hook(pool: &Pool, id: i64) -> sqlx::Result<Option<Hook>> { |
|
| 102 | - | sqlx::query_as("SELECT * FROM hooks WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 102 | + | sqlx::query_as("SELECT * FROM hooks WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 103 | 103 | } |
|
| 104 | 104 | ||
| 105 | 105 | pub async fn create_hook( |
|
| … | 4 unchanged lines |
||
| 110 | 110 | secret: &str, |
|
| 111 | 111 | events: &str, |
|
| 112 | 112 | ) -> sqlx::Result<Hook> { |
|
| 113 | - | sqlx::query_as("INSERT INTO hooks (repo_id, org_id, url, secret, events, created_ms, format) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *") |
|
| 114 | - | .bind(repo_id) |
|
| 115 | - | .bind(org_id) |
|
| 116 | - | .bind(url) |
|
| 117 | - | .bind(secret) |
|
| 118 | - | .bind(events) |
|
| 119 | - | .bind(now_ms()) |
|
| 120 | - | .bind(format_for_url(url)) |
|
| 121 | - | .fetch_one(pool) |
|
| 122 | - | .await |
|
| 113 | + | sqlx::query_as( |
|
| 114 | + | "INSERT INTO hooks (repo_id, org_id, url, secret, events, created_ms, format) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", |
|
| 115 | + | ) |
|
| 116 | + | .bind(repo_id) |
|
| 117 | + | .bind(org_id) |
|
| 118 | + | .bind(url) |
|
| 119 | + | .bind(secret) |
|
| 120 | + | .bind(events) |
|
| 121 | + | .bind(now_ms()) |
|
| 122 | + | .bind(format_for_url(url)) |
|
| 123 | + | .fetch_one(pool) |
|
| 124 | + | .await |
|
| 123 | 125 | } |
|
| 124 | 126 | ||
| 125 | 127 | /// Change a hook; `None` keeps the URL or secret as it is. |
|
| … | 6 unchanged lines |
||
| 132 | 134 | format: &str, |
|
| 133 | 135 | active: bool, |
|
| 134 | 136 | ) -> sqlx::Result<()> { |
|
| 135 | - | sqlx::query("UPDATE hooks SET url = COALESCE(?, url), secret = COALESCE(?, secret), events = ?, format = ?, active = ? WHERE id = ?") |
|
| 136 | - | .bind(url) |
|
| 137 | - | .bind(secret) |
|
| 138 | - | .bind(events) |
|
| 139 | - | .bind(format) |
|
| 140 | - | .bind(active) |
|
| 141 | - | .bind(id) |
|
| 142 | - | .execute(pool) |
|
| 143 | - | .await?; |
|
| 137 | + | sqlx::query( |
|
| 138 | + | "UPDATE hooks SET url = COALESCE($1, url), secret = COALESCE($2, secret), events = $3, format = $4, active = $5 WHERE id = $6", |
|
| 139 | + | ) |
|
| 140 | + | .bind(url) |
|
| 141 | + | .bind(secret) |
|
| 142 | + | .bind(events) |
|
| 143 | + | .bind(format) |
|
| 144 | + | .bind(active) |
|
| 145 | + | .bind(id) |
|
| 146 | + | .execute(pool) |
|
| 147 | + | .await?; |
|
| 144 | 148 | Ok(()) |
|
| 145 | 149 | } |
|
| 146 | 150 | ||
| 147 | 151 | pub async fn set_hook_format(pool: &Pool, id: i64, format: &str) -> sqlx::Result<()> { |
|
| 148 | - | sqlx::query("UPDATE hooks SET format = ? WHERE id = ?").bind(format).bind(id).execute(pool).await?; |
|
| 152 | + | sqlx::query("UPDATE hooks SET format = $1 WHERE id = $2").bind(format).bind(id).execute(pool).await?; |
|
| 149 | 153 | Ok(()) |
|
| 150 | 154 | } |
|
| 151 | 155 | ||
| 152 | 156 | /// The newest delivery that was attempted, for the settings table. |
|
| 153 | 157 | pub async fn last_attempt(pool: &Pool, hook_id: i64) -> sqlx::Result<Option<Delivery>> { |
|
| 154 | - | sqlx::query_as("SELECT * FROM deliveries WHERE hook_id = ? AND attempts > 0 ORDER BY id DESC LIMIT 1") |
|
| 158 | + | sqlx::query_as("SELECT * FROM deliveries WHERE hook_id = $1 AND attempts > 0 ORDER BY id DESC LIMIT 1") |
|
| 155 | 159 | .bind(hook_id) |
|
| 156 | 160 | .fetch_optional(pool) |
|
| 157 | 161 | .await |
|
| 158 | 162 | } |
|
| 159 | 163 | ||
| 160 | 164 | pub async fn set_hook_active(pool: &Pool, id: i64, active: bool) -> sqlx::Result<()> { |
|
| 161 | - | sqlx::query("UPDATE hooks SET active = ? WHERE id = ?").bind(active).bind(id).execute(pool).await?; |
|
| 165 | + | sqlx::query("UPDATE hooks SET active = $1 WHERE id = $2").bind(active).bind(id).execute(pool).await?; |
|
| 162 | 166 | Ok(()) |
|
| 163 | 167 | } |
|
| 164 | 168 | ||
| 165 | 169 | pub async fn delete_hook(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 166 | - | sqlx::query("DELETE FROM hooks WHERE id = ?").bind(id).execute(pool).await?; |
|
| 170 | + | sqlx::query("DELETE FROM hooks WHERE id = $1").bind(id).execute(pool).await?; |
|
| 167 | 171 | Ok(()) |
|
| 168 | 172 | } |
|
| 169 | 173 | ||
| … | 4 unchanged lines |
||
| 174 | 178 | /// Queue a delivery; `Ok(None)` when the hook already has |
|
| 175 | 179 | /// [`MAX_WAITING`] deliveries waiting, so nothing was queued. |
|
| 176 | 180 | pub async fn queue_delivery(pool: &Pool, hook_id: i64, delivery: &str, event: &str, payload: &str) -> sqlx::Result<Option<i64>> { |
|
| 177 | - | let waiting: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM deliveries WHERE hook_id = ? AND delivered_ms IS NULL AND next_ms < ?") |
|
| 181 | + | let waiting: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM deliveries WHERE hook_id = $1 AND delivered_ms IS NULL AND next_ms < $2") |
|
| 178 | 182 | .bind(hook_id) |
|
| 179 | 183 | .bind(i64::MAX / 4) |
|
| 180 | 184 | .fetch_one(pool) |
|
| … | 3 unchanged lines |
||
| 184 | 188 | } |
|
| 185 | 189 | let now = now_ms(); |
|
| 186 | 190 | sqlx::query_scalar( |
|
| 187 | - | "INSERT INTO deliveries (hook_id, delivery, event, payload, created_ms, next_ms) VALUES (?, ?, ?, ?, ?, ?) RETURNING id", |
|
| 191 | + | "INSERT INTO deliveries (hook_id, delivery, event, payload, created_ms, next_ms) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", |
|
| 188 | 192 | ) |
|
| 189 | 193 | .bind(hook_id) |
|
| 190 | 194 | .bind(delivery) |
|
| … | 7 unchanged lines |
||
| 198 | 202 | } |
|
| 199 | 203 | ||
| 200 | 204 | pub async fn due_deliveries(pool: &Pool, limit: i64) -> sqlx::Result<Vec<Delivery>> { |
|
| 201 | - | sqlx::query_as("SELECT * FROM deliveries WHERE delivered_ms IS NULL AND next_ms <= ? ORDER BY next_ms LIMIT ?") |
|
| 205 | + | sqlx::query_as("SELECT * FROM deliveries WHERE delivered_ms IS NULL AND next_ms <= $1 ORDER BY next_ms LIMIT $2") |
|
| 202 | 206 | .bind(now_ms()) |
|
| 203 | 207 | .bind(limit) |
|
| 204 | 208 | .fetch_all(pool) |
|
| … | 1 unchanged lines |
||
| 206 | 210 | } |
|
| 207 | 211 | ||
| 208 | 212 | pub async fn deliveries_for_hook(pool: &Pool, hook_id: i64, limit: i64) -> sqlx::Result<Vec<Delivery>> { |
|
| 209 | - | sqlx::query_as("SELECT * FROM deliveries WHERE hook_id = ? ORDER BY created_ms DESC, id DESC LIMIT ?") |
|
| 213 | + | sqlx::query_as("SELECT * FROM deliveries WHERE hook_id = $1 ORDER BY created_ms DESC, id DESC LIMIT $2") |
|
| 210 | 214 | .bind(hook_id) |
|
| 211 | 215 | .bind(limit) |
|
| 212 | 216 | .fetch_all(pool) |
|
| … | 1 unchanged lines |
||
| 214 | 218 | } |
|
| 215 | 219 | ||
| 216 | 220 | pub async fn delivery(pool: &Pool, id: i64) -> sqlx::Result<Option<Delivery>> { |
|
| 217 | - | sqlx::query_as("SELECT * FROM deliveries WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 221 | + | sqlx::query_as("SELECT * FROM deliveries WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 218 | 222 | } |
|
| 219 | 223 | ||
| 220 | 224 | /// Record an attempt: done on a 2xx, else retried with backoff up to a day. |
|
| … | 4 unchanged lines |
||
| 225 | 229 | // 1 min, 5 min, 30 min, 2 h, 6 h, then give up after a day's worth. |
|
| 226 | 230 | let backoff_ms = [60_000i64, 300_000, 1_800_000, 7_200_000, 21_600_000]; |
|
| 227 | 231 | let next = if ok || attempts as usize >= backoff_ms.len() { i64::MAX / 4 } else { now + backoff_ms[attempts as usize] }; |
|
| 228 | - | sqlx::query("UPDATE deliveries SET attempts = ?, status = ?, response = ?, delivered_ms = ?, next_ms = ? WHERE id = ?") |
|
| 232 | + | sqlx::query("UPDATE deliveries SET attempts = $1, status = $2, response = $3, delivered_ms = $4, next_ms = $5 WHERE id = $6") |
|
| 229 | 233 | .bind(attempts + 1) |
|
| 230 | 234 | .bind(status) |
|
| 231 | 235 | .bind(&response.chars().take(2000).collect::<String>()) |
|
| … | 7 unchanged lines |
||
| 239 | 243 | ||
| 240 | 244 | /// Requeue a delivery for an immediate retry. |
|
| 241 | 245 | pub async fn redeliver(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 242 | - | sqlx::query("UPDATE deliveries SET delivered_ms = NULL, next_ms = ? WHERE id = ?").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 246 | + | sqlx::query("UPDATE deliveries SET delivered_ms = NULL, next_ms = $1 WHERE id = $2").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 243 | 247 | Ok(()) |
|
| 244 | 248 | } |
|
| 245 | 249 | ||
| 246 | 250 | pub async fn prune_deliveries(pool: &Pool, older_than_ms: i64) -> sqlx::Result<()> { |
|
| 247 | - | sqlx::query("DELETE FROM deliveries WHERE created_ms < ?").bind(older_than_ms).execute(pool).await?; |
|
| 251 | + | sqlx::query("DELETE FROM deliveries WHERE created_ms < $1").bind(older_than_ms).execute(pool).await?; |
|
| 248 | 252 | Ok(()) |
|
| 249 | 253 | } |
|
| 250 | 254 | ||
| … | 22 unchanged lines |
||
| 273 | 277 | } |
|
| 274 | 278 | ||
| 275 | 279 | pub async fn checks_for_state(pool: &Pool, repo_id: i64, state: &str) -> sqlx::Result<Vec<Check>> { |
|
| 276 | - | sqlx::query_as("SELECT * FROM checks WHERE repo_id = ? AND state = ? ORDER BY name").bind(repo_id).bind(state).fetch_all(pool).await |
|
| 280 | + | sqlx::query_as("SELECT * FROM checks WHERE repo_id = $1 AND state = $2 ORDER BY name").bind(repo_id).bind(state).fetch_all(pool).await |
|
| 277 | 281 | } |
|
| 278 | 282 | ||
| 279 | 283 | /// Create or update a check by `(state, name)`. |
|
| … | 11 unchanged lines |
||
| 291 | 295 | let now = now_ms(); |
|
| 292 | 296 | let finished: Option<i64> = if matches!(status, "passed" | "failed" | "cancelled" | "skipped") { Some(now) } else { None }; |
|
| 293 | 297 | sqlx::query_as( |
|
| 294 | - | "INSERT INTO checks (repo_id, state, name, status, summary, url, reporter_id, reporter, seal, started_ms, finished_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) \ |
|
| 298 | + | "INSERT INTO checks (repo_id, state, name, status, summary, url, reporter_id, reporter, seal, started_ms, finished_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) \ |
|
| 295 | 299 | ON CONFLICT (repo_id, state, name) DO UPDATE SET status = excluded.status, summary = excluded.summary, url = excluded.url, reporter_id = excluded.reporter_id, reporter = excluded.reporter, seal = CASE WHEN excluded.seal = '' THEN checks.seal ELSE excluded.seal END, finished_ms = excluded.finished_ms RETURNING *", |
|
| 296 | 300 | ) |
|
| 297 | 301 | .bind(repo_id) |
|
| … | 12 unchanged lines |
||
| 310 | 314 | } |
|
| 311 | 315 | ||
| 312 | 316 | pub async fn required_checks(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<String>> { |
|
| 313 | - | let s: String = sqlx::query_scalar("SELECT required_checks FROM repositories WHERE id = ?").bind(repo_id).fetch_one(pool).await?; |
|
| 317 | + | let s: String = sqlx::query_scalar("SELECT required_checks FROM repositories WHERE id = $1").bind(repo_id).fetch_one(pool).await?; |
|
| 314 | 318 | Ok(s.split(',').map(str::trim).filter(|x| !x.is_empty()).map(String::from).collect()) |
|
| 315 | 319 | } |
|
| 316 | 320 | ||
| 317 | 321 | pub async fn set_required_checks(pool: &Pool, repo_id: i64, names: &[String]) -> sqlx::Result<()> { |
|
| 318 | - | sqlx::query("UPDATE repositories SET required_checks = ? WHERE id = ?").bind(names.join(",")).bind(repo_id).execute(pool).await?; |
|
| 322 | + | sqlx::query("UPDATE repositories SET required_checks = $1 WHERE id = $2").bind(names.join(",")).bind(repo_id).execute(pool).await?; |
|
| 319 | 323 | Ok(()) |
|
| 320 | 324 | } |
modified crates/hub/src/db/issues.rs +51 −50
| … | 71 unchanged lines |
||
| 72 | 72 | pub async fn create(pool: &Pool, i: NewIssue<'_>) -> sqlx::Result<IssueRow> { |
|
| 73 | 73 | let number = super::proposals::next_number(pool, i.repo_id).await?; |
|
| 74 | 74 | let now = now_ms(); |
|
| 75 | - | sqlx::query_as("INSERT INTO issues (repo_id, number, title, body, author_id, author, created_ms, updated_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *") |
|
| 75 | + | sqlx::query_as("INSERT INTO issues (repo_id, number, title, body, author_id, author, created_ms, updated_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *") |
|
| 76 | 76 | .bind(i.repo_id) |
|
| 77 | 77 | .bind(number) |
|
| 78 | 78 | .bind(i.title) |
|
| … | 7 unchanged lines |
||
| 86 | 86 | } |
|
| 87 | 87 | ||
| 88 | 88 | pub async fn get(pool: &Pool, repo_id: i64, number: i64) -> sqlx::Result<Option<IssueRow>> { |
|
| 89 | - | sqlx::query_as("SELECT * FROM issues WHERE repo_id = ? AND number = ?").bind(repo_id).bind(number).fetch_optional(pool).await |
|
| 89 | + | sqlx::query_as("SELECT * FROM issues WHERE repo_id = $1 AND number = $2").bind(repo_id).bind(number).fetch_optional(pool).await |
|
| 90 | 90 | } |
|
| 91 | 91 | ||
| 92 | 92 | pub async fn get_by_id(pool: &Pool, id: i64) -> sqlx::Result<Option<IssueRow>> { |
|
| 93 | - | sqlx::query_as("SELECT * FROM issues WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 93 | + | sqlx::query_as("SELECT * FROM issues WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 94 | 94 | } |
|
| 95 | 95 | ||
| 96 | 96 | pub async fn list(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<IssueRow>> { |
|
| 97 | - | sqlx::query_as("SELECT * FROM issues WHERE repo_id = ? ORDER BY pinned DESC, number DESC").bind(repo_id).fetch_all(pool).await |
|
| 97 | + | sqlx::query_as("SELECT * FROM issues WHERE repo_id = $1 ORDER BY pinned DESC, number DESC").bind(repo_id).fetch_all(pool).await |
|
| 98 | 98 | } |
|
| 99 | 99 | ||
| 100 | 100 | /// Numbers in use by issues and proposals, for cross references. |
|
| 101 | 101 | pub async fn numbers(pool: &Pool, repo_id: i64) -> sqlx::Result<(Vec<i64>, Vec<i64>)> { |
|
| 102 | - | let issues: Vec<i64> = sqlx::query_scalar("SELECT number FROM issues WHERE repo_id = ?").bind(repo_id).fetch_all(pool).await?; |
|
| 103 | - | let proposals: Vec<i64> = sqlx::query_scalar("SELECT number FROM proposals WHERE repo_id = ?").bind(repo_id).fetch_all(pool).await?; |
|
| 102 | + | let issues: Vec<i64> = sqlx::query_scalar("SELECT number FROM issues WHERE repo_id = $1").bind(repo_id).fetch_all(pool).await?; |
|
| 103 | + | let proposals: Vec<i64> = sqlx::query_scalar("SELECT number FROM proposals WHERE repo_id = $1").bind(repo_id).fetch_all(pool).await?; |
|
| 104 | 104 | Ok((issues, proposals)) |
|
| 105 | 105 | } |
|
| 106 | 106 | ||
| 107 | 107 | pub async fn assigned_to(pool: &Pool, account_id: i64) -> sqlx::Result<Vec<IssueRow>> { |
|
| 108 | - | sqlx::query_as("SELECT i.* FROM issues i JOIN issue_assignees a ON a.issue_id = i.id WHERE a.account_id = ? AND i.state = 'open' ORDER BY i.updated_ms DESC") |
|
| 108 | + | sqlx::query_as("SELECT i.* FROM issues i JOIN issue_assignees a ON a.issue_id = i.id WHERE a.account_id = $1 AND i.state = 'open' ORDER BY i.updated_ms DESC") |
|
| 109 | 109 | .bind(account_id) |
|
| 110 | 110 | .fetch_all(pool) |
|
| 111 | 111 | .await |
|
| 112 | 112 | } |
|
| 113 | 113 | ||
| 114 | 114 | pub async fn edit(pool: &Pool, id: i64, title: &str, body: &str) -> sqlx::Result<()> { |
|
| 115 | - | sqlx::query("UPDATE issues SET title = ?, body = ?, updated_ms = ? WHERE id = ?") |
|
| 115 | + | sqlx::query("UPDATE issues SET title = $1, body = $2, updated_ms = $3 WHERE id = $4") |
|
| 116 | 116 | .bind(title) |
|
| 117 | 117 | .bind(body) |
|
| 118 | 118 | .bind(now_ms()) |
|
| … | 6 unchanged lines |
||
| 125 | 125 | pub async fn set_state(pool: &Pool, id: i64, state: &str, reason: &str) -> sqlx::Result<()> { |
|
| 126 | 126 | let now = now_ms(); |
|
| 127 | 127 | let closed: Option<i64> = if state == "closed" { Some(now) } else { None }; |
|
| 128 | - | sqlx::query("UPDATE issues SET state = ?, reason = ?, closed_ms = ?, updated_ms = ? WHERE id = ?") |
|
| 128 | + | sqlx::query("UPDATE issues SET state = $1, reason = $2, closed_ms = $3, updated_ms = $4 WHERE id = $5") |
|
| 129 | 129 | .bind(state) |
|
| 130 | 130 | .bind(reason) |
|
| 131 | 131 | .bind(closed) |
|
| … | 6 unchanged lines |
||
| 138 | 138 | ||
| 139 | 139 | pub async fn set_flags(pool: &Pool, id: i64, locked: Option<bool>, pinned: Option<bool>) -> sqlx::Result<()> { |
|
| 140 | 140 | if let Some(l) = locked { |
|
| 141 | - | sqlx::query("UPDATE issues SET locked = ? WHERE id = ?").bind(l).bind(id).execute(pool).await?; |
|
| 141 | + | sqlx::query("UPDATE issues SET locked = $1 WHERE id = $2").bind(l).bind(id).execute(pool).await?; |
|
| 142 | 142 | } |
|
| 143 | 143 | if let Some(p) = pinned { |
|
| 144 | - | sqlx::query("UPDATE issues SET pinned = ? WHERE id = ?").bind(p).bind(id).execute(pool).await?; |
|
| 144 | + | sqlx::query("UPDATE issues SET pinned = $1 WHERE id = $2").bind(p).bind(id).execute(pool).await?; |
|
| 145 | 145 | } |
|
| 146 | 146 | Ok(()) |
|
| 147 | 147 | } |
|
| 148 | 148 | ||
| 149 | 149 | pub async fn set_milestone(pool: &Pool, id: i64, milestone_id: Option<i64>) -> sqlx::Result<()> { |
|
| 150 | - | sqlx::query("UPDATE issues SET milestone_id = ?, updated_ms = ? WHERE id = ?") |
|
| 150 | + | sqlx::query("UPDATE issues SET milestone_id = $1, updated_ms = $2 WHERE id = $3") |
|
| 151 | 151 | .bind(milestone_id) |
|
| 152 | 152 | .bind(now_ms()) |
|
| 153 | 153 | .bind(id) |
|
| … | 3 unchanged lines |
||
| 157 | 157 | } |
|
| 158 | 158 | ||
| 159 | 159 | pub async fn touch(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 160 | - | sqlx::query("UPDATE issues SET updated_ms = ? WHERE id = ?").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 160 | + | sqlx::query("UPDATE issues SET updated_ms = $1 WHERE id = $2").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 161 | 161 | Ok(()) |
|
| 162 | 162 | } |
|
| 163 | 163 | ||
| 164 | 164 | pub async fn comments(pool: &Pool, issue_id: i64) -> sqlx::Result<Vec<IssueComment>> { |
|
| 165 | - | sqlx::query_as("SELECT * FROM issue_comments WHERE issue_id = ? ORDER BY created_ms, id").bind(issue_id).fetch_all(pool).await |
|
| 165 | + | sqlx::query_as("SELECT * FROM issue_comments WHERE issue_id = $1 ORDER BY created_ms, id").bind(issue_id).fetch_all(pool).await |
|
| 166 | 166 | } |
|
| 167 | 167 | ||
| 168 | 168 | pub async fn add_comment(pool: &Pool, issue_id: i64, author_id: Option<i64>, author: &str, body: &str) -> sqlx::Result<IssueComment> { |
|
| 169 | - | let row = |
|
| 170 | - | sqlx::query_as("INSERT INTO issue_comments (issue_id, author_id, author, body, created_ms) VALUES (?, ?, ?, ?, ?) RETURNING *") |
|
| 171 | - | .bind(issue_id) |
|
| 172 | - | .bind(author_id) |
|
| 173 | - | .bind(author) |
|
| 174 | - | .bind(body) |
|
| 175 | - | .bind(now_ms()) |
|
| 176 | - | .fetch_one(pool) |
|
| 177 | - | .await?; |
|
| 169 | + | let row = sqlx::query_as( |
|
| 170 | + | "INSERT INTO issue_comments (issue_id, author_id, author, body, created_ms) VALUES ($1, $2, $3, $4, $5) RETURNING *", |
|
| 171 | + | ) |
|
| 172 | + | .bind(issue_id) |
|
| 173 | + | .bind(author_id) |
|
| 174 | + | .bind(author) |
|
| 175 | + | .bind(body) |
|
| 176 | + | .bind(now_ms()) |
|
| 177 | + | .fetch_one(pool) |
|
| 178 | + | .await?; |
|
| 178 | 179 | touch(pool, issue_id).await?; |
|
| 179 | 180 | Ok(row) |
|
| 180 | 181 | } |
|
| 181 | 182 | ||
| 182 | 183 | pub async fn edit_comment(pool: &Pool, id: i64, author_id: i64, body: &str) -> sqlx::Result<bool> { |
|
| 183 | - | let n = sqlx::query("UPDATE issue_comments SET body = ?, edited_ms = ? WHERE id = ? AND author_id = ?") |
|
| 184 | + | let n = sqlx::query("UPDATE issue_comments SET body = $1, edited_ms = $2 WHERE id = $3 AND author_id = $4") |
|
| 184 | 185 | .bind(body) |
|
| 185 | 186 | .bind(now_ms()) |
|
| 186 | 187 | .bind(id) |
|
| … | 4 unchanged lines |
||
| 191 | 192 | } |
|
| 192 | 193 | ||
| 193 | 194 | pub async fn delete_comment(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 194 | - | sqlx::query("DELETE FROM issue_comments WHERE id = ?").bind(id).execute(pool).await?; |
|
| 195 | + | sqlx::query("DELETE FROM issue_comments WHERE id = $1").bind(id).execute(pool).await?; |
|
| 195 | 196 | Ok(()) |
|
| 196 | 197 | } |
|
| 197 | 198 | ||
| 198 | 199 | pub async fn events(pool: &Pool, issue_id: i64) -> sqlx::Result<Vec<IssueEvent>> { |
|
| 199 | - | sqlx::query_as("SELECT id, at_ms, actor, event, detail FROM issue_events WHERE issue_id = ? ORDER BY at_ms, id") |
|
| 200 | + | sqlx::query_as("SELECT id, at_ms, actor, event, detail FROM issue_events WHERE issue_id = $1 ORDER BY at_ms, id") |
|
| 200 | 201 | .bind(issue_id) |
|
| 201 | 202 | .fetch_all(pool) |
|
| 202 | 203 | .await |
|
| 203 | 204 | } |
|
| 204 | 205 | ||
| 205 | 206 | pub async fn add_event(pool: &Pool, issue_id: i64, actor: &str, event: &str, detail: &str) -> sqlx::Result<()> { |
|
| 206 | - | sqlx::query("INSERT INTO issue_events (issue_id, at_ms, actor, event, detail) VALUES (?, ?, ?, ?, ?)") |
|
| 207 | + | sqlx::query("INSERT INTO issue_events (issue_id, at_ms, actor, event, detail) VALUES ($1, $2, $3, $4, $5)") |
|
| 207 | 208 | .bind(issue_id) |
|
| 208 | 209 | .bind(now_ms()) |
|
| 209 | 210 | .bind(actor) |
|
| … | 7 unchanged lines |
||
| 217 | 218 | // ------------------------------------------------------------------ labels |
|
| 218 | 219 | ||
| 219 | 220 | pub async fn labels(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<Label>> { |
|
| 220 | - | sqlx::query_as("SELECT * FROM labels WHERE repo_id = ? ORDER BY name").bind(repo_id).fetch_all(pool).await |
|
| 221 | + | sqlx::query_as("SELECT * FROM labels WHERE repo_id = $1 ORDER BY name").bind(repo_id).fetch_all(pool).await |
|
| 221 | 222 | } |
|
| 222 | 223 | ||
| 223 | 224 | pub async fn label_by_name(pool: &Pool, repo_id: i64, name: &str) -> sqlx::Result<Option<Label>> { |
|
| 224 | - | sqlx::query_as("SELECT * FROM labels WHERE repo_id = ? AND name = ?").bind(repo_id).bind(name).fetch_optional(pool).await |
|
| 225 | + | sqlx::query_as("SELECT * FROM labels WHERE repo_id = $1 AND name = $2").bind(repo_id).bind(name).fetch_optional(pool).await |
|
| 225 | 226 | } |
|
| 226 | 227 | ||
| 227 | 228 | pub async fn upsert_label(pool: &Pool, repo_id: i64, name: &str, colour: &str, description: &str) -> sqlx::Result<Label> { |
|
| 228 | - | sqlx::query_as("INSERT INTO labels (repo_id, name, colour, description) VALUES (?, ?, ?, ?) ON CONFLICT (repo_id, name) DO UPDATE SET colour = excluded.colour, description = excluded.description RETURNING *") |
|
| 229 | + | sqlx::query_as("INSERT INTO labels (repo_id, name, colour, description) VALUES ($1, $2, $3, $4) ON CONFLICT (repo_id, name) DO UPDATE SET colour = excluded.colour, description = excluded.description RETURNING *") |
|
| 229 | 230 | .bind(repo_id) |
|
| 230 | 231 | .bind(name) |
|
| 231 | 232 | .bind(colour) |
|
| … | 3 unchanged lines |
||
| 235 | 236 | } |
|
| 236 | 237 | ||
| 237 | 238 | pub async fn delete_label(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 238 | - | sqlx::query("DELETE FROM labels WHERE id = ?").bind(id).execute(pool).await?; |
|
| 239 | + | sqlx::query("DELETE FROM labels WHERE id = $1").bind(id).execute(pool).await?; |
|
| 239 | 240 | Ok(()) |
|
| 240 | 241 | } |
|
| 241 | 242 | ||
| 242 | 243 | pub async fn issue_labels(pool: &Pool, issue_id: i64) -> sqlx::Result<Vec<Label>> { |
|
| 243 | - | sqlx::query_as("SELECT l.* FROM labels l JOIN issue_labels il ON il.label_id = l.id WHERE il.issue_id = ? ORDER BY l.name") |
|
| 244 | + | sqlx::query_as("SELECT l.* FROM labels l JOIN issue_labels il ON il.label_id = l.id WHERE il.issue_id = $1 ORDER BY l.name") |
|
| 244 | 245 | .bind(issue_id) |
|
| 245 | 246 | .fetch_all(pool) |
|
| 246 | 247 | .await |
|
| … | 1 unchanged lines |
||
| 248 | 249 | ||
| 249 | 250 | pub async fn set_issue_label(pool: &Pool, issue_id: i64, label_id: i64, on: bool) -> sqlx::Result<()> { |
|
| 250 | 251 | if on { |
|
| 251 | - | sqlx::query("INSERT OR IGNORE INTO issue_labels (issue_id, label_id) VALUES (?, ?)") |
|
| 252 | + | sqlx::query("INSERT OR IGNORE INTO issue_labels (issue_id, label_id) VALUES ($1, $2)") |
|
| 252 | 253 | .bind(issue_id) |
|
| 253 | 254 | .bind(label_id) |
|
| 254 | 255 | .execute(pool) |
|
| 255 | 256 | .await?; |
|
| 256 | 257 | } else { |
|
| 257 | - | sqlx::query("DELETE FROM issue_labels WHERE issue_id = ? AND label_id = ?").bind(issue_id).bind(label_id).execute(pool).await?; |
|
| 258 | + | sqlx::query("DELETE FROM issue_labels WHERE issue_id = $1 AND label_id = $2").bind(issue_id).bind(label_id).execute(pool).await?; |
|
| 258 | 259 | } |
|
| 259 | 260 | Ok(()) |
|
| 260 | 261 | } |
|
| … | 2 unchanged lines |
||
| 263 | 264 | /// Every proposal's labels in a repository, for filtering the list. |
|
| 264 | 265 | pub async fn labels_by_proposal(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<(i64, Label)>> { |
|
| 265 | 266 | let rows: Vec<(i64, i64, i64, String, String, String)> = sqlx::query_as( |
|
| 266 | - | "SELECT pl.proposal_id, l.id, l.repo_id, l.name, l.colour, l.description FROM proposal_labels pl JOIN labels l ON l.id = pl.label_id JOIN proposals p ON p.id = pl.proposal_id WHERE p.repo_id = ? ORDER BY l.name", |
|
| 267 | + | "SELECT pl.proposal_id, l.id, l.repo_id, l.name, l.colour, l.description FROM proposal_labels pl JOIN labels l ON l.id = pl.label_id JOIN proposals p ON p.id = pl.proposal_id WHERE p.repo_id = $1 ORDER BY l.name", |
|
| 267 | 268 | ) |
|
| 268 | 269 | .bind(repo_id) |
|
| 269 | 270 | .fetch_all(pool) |
|
| … | 6 unchanged lines |
||
| 276 | 277 | ||
| 277 | 278 | pub async fn labels_by_issue(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<(i64, Label)>> { |
|
| 278 | 279 | let rows: Vec<(i64, i64, i64, String, String, String)> = sqlx::query_as( |
|
| 279 | - | "SELECT il.issue_id, l.id, l.repo_id, l.name, l.colour, l.description FROM issue_labels il JOIN labels l ON l.id = il.label_id JOIN issues i ON i.id = il.issue_id WHERE i.repo_id = ? ORDER BY l.name", |
|
| 280 | + | "SELECT il.issue_id, l.id, l.repo_id, l.name, l.colour, l.description FROM issue_labels il JOIN labels l ON l.id = il.label_id JOIN issues i ON i.id = il.issue_id WHERE i.repo_id = $1 ORDER BY l.name", |
|
| 280 | 281 | ) |
|
| 281 | 282 | .bind(repo_id) |
|
| 282 | 283 | .fetch_all(pool) |
|
| … | 5 unchanged lines |
||
| 288 | 289 | } |
|
| 289 | 290 | ||
| 290 | 291 | pub async fn proposal_labels(pool: &Pool, proposal_id: i64) -> sqlx::Result<Vec<Label>> { |
|
| 291 | - | sqlx::query_as("SELECT l.* FROM labels l JOIN proposal_labels pl ON pl.label_id = l.id WHERE pl.proposal_id = ? ORDER BY l.name") |
|
| 292 | + | sqlx::query_as("SELECT l.* FROM labels l JOIN proposal_labels pl ON pl.label_id = l.id WHERE pl.proposal_id = $1 ORDER BY l.name") |
|
| 292 | 293 | .bind(proposal_id) |
|
| 293 | 294 | .fetch_all(pool) |
|
| 294 | 295 | .await |
|
| … | 1 unchanged lines |
||
| 296 | 297 | ||
| 297 | 298 | pub async fn set_proposal_label(pool: &Pool, proposal_id: i64, label_id: i64, on: bool) -> sqlx::Result<()> { |
|
| 298 | 299 | if on { |
|
| 299 | - | sqlx::query("INSERT OR IGNORE INTO proposal_labels (proposal_id, label_id) VALUES (?, ?)") |
|
| 300 | + | sqlx::query("INSERT OR IGNORE INTO proposal_labels (proposal_id, label_id) VALUES ($1, $2)") |
|
| 300 | 301 | .bind(proposal_id) |
|
| 301 | 302 | .bind(label_id) |
|
| 302 | 303 | .execute(pool) |
|
| 303 | 304 | .await?; |
|
| 304 | 305 | } else { |
|
| 305 | - | sqlx::query("DELETE FROM proposal_labels WHERE proposal_id = ? AND label_id = ?") |
|
| 306 | + | sqlx::query("DELETE FROM proposal_labels WHERE proposal_id = $1 AND label_id = $2") |
|
| 306 | 307 | .bind(proposal_id) |
|
| 307 | 308 | .bind(label_id) |
|
| 308 | 309 | .execute(pool) |
|
| … | 6 unchanged lines |
||
| 315 | 316 | ||
| 316 | 317 | pub async fn assignees(pool: &Pool, issue_id: i64) -> sqlx::Result<Vec<String>> { |
|
| 317 | 318 | sqlx::query_scalar( |
|
| 318 | - | "SELECT a.username FROM accounts a JOIN issue_assignees ia ON ia.account_id = a.id WHERE ia.issue_id = ? ORDER BY a.username", |
|
| 319 | + | "SELECT a.username FROM accounts a JOIN issue_assignees ia ON ia.account_id = a.id WHERE ia.issue_id = $1 ORDER BY a.username", |
|
| 319 | 320 | ) |
|
| 320 | 321 | .bind(issue_id) |
|
| 321 | 322 | .fetch_all(pool) |
|
| … | 2 unchanged lines |
||
| 324 | 325 | ||
| 325 | 326 | pub async fn set_assignee(pool: &Pool, issue_id: i64, account_id: i64, on: bool) -> sqlx::Result<()> { |
|
| 326 | 327 | if on { |
|
| 327 | - | sqlx::query("INSERT OR IGNORE INTO issue_assignees (issue_id, account_id) VALUES (?, ?)") |
|
| 328 | + | sqlx::query("INSERT OR IGNORE INTO issue_assignees (issue_id, account_id) VALUES ($1, $2)") |
|
| 328 | 329 | .bind(issue_id) |
|
| 329 | 330 | .bind(account_id) |
|
| 330 | 331 | .execute(pool) |
|
| 331 | 332 | .await?; |
|
| 332 | 333 | } else { |
|
| 333 | - | sqlx::query("DELETE FROM issue_assignees WHERE issue_id = ? AND account_id = ?") |
|
| 334 | + | sqlx::query("DELETE FROM issue_assignees WHERE issue_id = $1 AND account_id = $2") |
|
| 334 | 335 | .bind(issue_id) |
|
| 335 | 336 | .bind(account_id) |
|
| 336 | 337 | .execute(pool) |
|
| … | 4 unchanged lines |
||
| 341 | 342 | ||
| 342 | 343 | /// Assignees of every issue of a repository, for the list page. |
|
| 343 | 344 | pub async fn assignees_by_issue(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<(i64, String)>> { |
|
| 344 | - | sqlx::query_as("SELECT ia.issue_id, a.username FROM issue_assignees ia JOIN accounts a ON a.id = ia.account_id JOIN issues i ON i.id = ia.issue_id WHERE i.repo_id = ?") |
|
| 345 | + | sqlx::query_as("SELECT ia.issue_id, a.username FROM issue_assignees ia JOIN accounts a ON a.id = ia.account_id JOIN issues i ON i.id = ia.issue_id WHERE i.repo_id = $1") |
|
| 345 | 346 | .bind(repo_id) |
|
| 346 | 347 | .fetch_all(pool) |
|
| 347 | 348 | .await |
|
| … | 2 unchanged lines |
||
| 350 | 351 | // -------------------------------------------------------------- milestones |
|
| 351 | 352 | ||
| 352 | 353 | pub async fn milestones(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<Milestone>> { |
|
| 353 | - | sqlx::query_as("SELECT * FROM milestones WHERE repo_id = ? ORDER BY state, due_ms, title").bind(repo_id).fetch_all(pool).await |
|
| 354 | + | sqlx::query_as("SELECT * FROM milestones WHERE repo_id = $1 ORDER BY state, due_ms, title").bind(repo_id).fetch_all(pool).await |
|
| 354 | 355 | } |
|
| 355 | 356 | ||
| 356 | 357 | pub async fn milestone(pool: &Pool, id: i64) -> sqlx::Result<Option<Milestone>> { |
|
| 357 | - | sqlx::query_as("SELECT * FROM milestones WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 358 | + | sqlx::query_as("SELECT * FROM milestones WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 358 | 359 | } |
|
| 359 | 360 | ||
| 360 | 361 | pub async fn upsert_milestone(pool: &Pool, repo_id: i64, title: &str, description: &str, due_ms: Option<i64>) -> sqlx::Result<Milestone> { |
|
| 361 | - | sqlx::query_as("INSERT INTO milestones (repo_id, title, description, due_ms) VALUES (?, ?, ?, ?) ON CONFLICT (repo_id, title) DO UPDATE SET description = excluded.description, due_ms = excluded.due_ms RETURNING *") |
|
| 362 | + | sqlx::query_as("INSERT INTO milestones (repo_id, title, description, due_ms) VALUES ($1, $2, $3, $4) ON CONFLICT (repo_id, title) DO UPDATE SET description = excluded.description, due_ms = excluded.due_ms RETURNING *") |
|
| 362 | 363 | .bind(repo_id) |
|
| 363 | 364 | .bind(title) |
|
| 364 | 365 | .bind(description) |
|
| … | 4 unchanged lines |
||
| 369 | 370 | ||
| 370 | 371 | /// Change a milestone's title, description and due date. |
|
| 371 | 372 | pub async fn update_milestone(pool: &Pool, id: i64, title: &str, description: &str, due_ms: Option<i64>) -> sqlx::Result<()> { |
|
| 372 | - | sqlx::query("UPDATE milestones SET title = ?, description = ?, due_ms = ? WHERE id = ?") |
|
| 373 | + | sqlx::query("UPDATE milestones SET title = $1, description = $2, due_ms = $3 WHERE id = $4") |
|
| 373 | 374 | .bind(title) |
|
| 374 | 375 | .bind(description) |
|
| 375 | 376 | .bind(due_ms) |
|
| … | 4 unchanged lines |
||
| 380 | 381 | } |
|
| 381 | 382 | ||
| 382 | 383 | pub async fn set_milestone_state(pool: &Pool, id: i64, state: &str) -> sqlx::Result<()> { |
|
| 383 | - | sqlx::query("UPDATE milestones SET state = ? WHERE id = ?").bind(state).bind(id).execute(pool).await?; |
|
| 384 | + | sqlx::query("UPDATE milestones SET state = $1 WHERE id = $2").bind(state).bind(id).execute(pool).await?; |
|
| 384 | 385 | Ok(()) |
|
| 385 | 386 | } |
|
| 386 | 387 | ||
| 387 | 388 | /// `(open, closed)` issue counts per milestone. |
|
| 388 | 389 | pub async fn milestone_counts(pool: &Pool, milestone_id: i64) -> sqlx::Result<(i64, i64)> { |
|
| 389 | - | let open: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM issues WHERE milestone_id = ? AND state = 'open'") |
|
| 390 | + | let open: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM issues WHERE milestone_id = $1 AND state = 'open'") |
|
| 390 | 391 | .bind(milestone_id) |
|
| 391 | 392 | .fetch_one(pool) |
|
| 392 | 393 | .await?; |
|
| 393 | - | let closed: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM issues WHERE milestone_id = ? AND state = 'closed'") |
|
| 394 | + | let closed: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM issues WHERE milestone_id = $1 AND state = 'closed'") |
|
| 394 | 395 | .bind(milestone_id) |
|
| 395 | 396 | .fetch_one(pool) |
|
| 396 | 397 | .await?; |
|
| … | 2 unchanged lines |
modified crates/hub/src/db/notify.rs +33 −25
| … | 54 unchanged lines |
||
| 55 | 55 | pub async fn add(pool: &Pool, n: New<'_>) -> sqlx::Result<()> { |
|
| 56 | 56 | let New { account_id, repo_id, reason, kind, title, url, actor } = n; |
|
| 57 | 57 | // A new event brings a thread that was set aside back to the inbox. |
|
| 58 | - | sqlx::query("UPDATE notifications SET done = 0 WHERE account_id = ? AND thread = ? AND done = 1") |
|
| 58 | + | sqlx::query("UPDATE notifications SET done = 0 WHERE account_id = $1 AND thread = $2 AND done = 1") |
|
| 59 | 59 | .bind(account_id) |
|
| 60 | 60 | .bind(thread_of(url)) |
|
| 61 | 61 | .execute(pool) |
|
| 62 | 62 | .await?; |
|
| 63 | - | sqlx::query("INSERT INTO notifications (account_id, at_ms, kind, title, url, actor, repo_id, thread, reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") |
|
| 63 | + | sqlx::query("INSERT INTO notifications (account_id, at_ms, kind, title, url, actor, repo_id, thread, reason) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)") |
|
| 64 | 64 | .bind(account_id) |
|
| 65 | 65 | .bind(now_ms()) |
|
| 66 | 66 | .bind(kind) |
|
| … | 45 unchanged lines |
||
| 112 | 112 | "SELECT n.id, MAX(n.at_ms) AS at_ms, n.kind, n.title, n.url, n.actor, n.thread, n.repo_id, \ |
|
| 113 | 113 | (SELECT r.owner || '/' || r.name FROM repositories r WHERE r.id = n.repo_id) AS repo, \ |
|
| 114 | 114 | COUNT(*) AS count, SUM(n.read = 0) AS unread, SUM(n.reason = 'participating') > 0 AS participating, n.done \ |
|
| 115 | - | FROM notifications n WHERE n.account_id = ? AND n.done = ?", |
|
| 115 | + | FROM notifications n WHERE n.account_id = $1 AND n.done = $2", |
|
| 116 | 116 | ); |
|
| 117 | + | // Placeholders numbered as they are added: $1 and $2 above. |
|
| 118 | + | let mut next = 3; |
|
| 117 | 119 | if f.kind.is_some() { |
|
| 118 | - | sql.push_str(" AND (n.kind = ? OR n.kind LIKE ? || '.%')"); |
|
| 120 | + | sql.push_str(&format!(" AND (n.kind = ${} OR n.kind LIKE ${} || '.%')", next, next + 1)); |
|
| 121 | + | next += 2; |
|
| 119 | 122 | } |
|
| 120 | 123 | if f.repo_id.is_some() { |
|
| 121 | - | sql.push_str(" AND n.repo_id = ?"); |
|
| 124 | + | sql.push_str(&format!(" AND n.repo_id = ${next}")); |
|
| 125 | + | next += 1; |
|
| 122 | 126 | } |
|
| 123 | 127 | sql.push_str(" GROUP BY n.thread HAVING 1"); |
|
| 124 | 128 | if f.unread_only { |
|
| … | 4 unchanged lines |
||
| 129 | 133 | Some("watching") => sql.push_str(" AND SUM(n.reason = 'participating') = 0"), |
|
| 130 | 134 | _ => {} |
|
| 131 | 135 | } |
|
| 132 | - | sql.push_str(" ORDER BY at_ms DESC LIMIT ?"); |
|
| 136 | + | sql.push_str(&format!(" ORDER BY at_ms DESC LIMIT ${next}")); |
|
| 133 | 137 | let mut q = sqlx::query_as::<_, Thread>(&sql).bind(account_id).bind(f.done); |
|
| 134 | 138 | if let Some(k) = &f.kind { |
|
| 135 | 139 | q = q.bind(k).bind(k); |
|
| … | 8 unchanged lines |
||
| 144 | 148 | pub async fn set_thread(pool: &Pool, account_id: i64, thread: &str, read: Option<bool>, done: Option<bool>) -> sqlx::Result<()> { |
|
| 145 | 149 | if let Some(r) = read { |
|
| 146 | 150 | if r { |
|
| 147 | - | sqlx::query("UPDATE notifications SET read = 1 WHERE account_id = ? AND thread = ?") |
|
| 151 | + | sqlx::query("UPDATE notifications SET read = 1 WHERE account_id = $1 AND thread = $2") |
|
| 148 | 152 | .bind(account_id) |
|
| 149 | 153 | .bind(thread) |
|
| 150 | 154 | .execute(pool) |
|
| 151 | 155 | .await?; |
|
| 152 | 156 | } else { |
|
| 153 | 157 | // Unread: the newest notification of the thread only. |
|
| 154 | - | sqlx::query("UPDATE notifications SET read = 0 WHERE id = (SELECT id FROM notifications WHERE account_id = ? AND thread = ? ORDER BY at_ms DESC, id DESC LIMIT 1)") |
|
| 158 | + | sqlx::query("UPDATE notifications SET read = 0 WHERE id = (SELECT id FROM notifications WHERE account_id = $1 AND thread = $2 ORDER BY at_ms DESC, id DESC LIMIT 1)") |
|
| 155 | 159 | .bind(account_id) |
|
| 156 | 160 | .bind(thread) |
|
| 157 | 161 | .execute(pool) |
|
| … | 2 unchanged lines |
||
| 160 | 164 | } |
|
| 161 | 165 | if let Some(d) = done { |
|
| 162 | 166 | let read_too = if d { ", read = 1" } else { "" }; |
|
| 163 | - | sqlx::query(&format!("UPDATE notifications SET done = ?{read_too} WHERE account_id = ? AND thread = ?")) |
|
| 167 | + | sqlx::query(&format!("UPDATE notifications SET done = $1{read_too} WHERE account_id = $2 AND thread = $3")) |
|
| 164 | 168 | .bind(d) |
|
| 165 | 169 | .bind(account_id) |
|
| 166 | 170 | .bind(thread) |
|
| … | 5 unchanged lines |
||
| 172 | 176 | ||
| 173 | 177 | /// The notification with this id, if it is the account's. |
|
| 174 | 178 | pub async fn get(pool: &Pool, account_id: i64, id: i64) -> sqlx::Result<Option<Notification>> { |
|
| 175 | - | sqlx::query_as("SELECT * FROM notifications WHERE account_id = ? AND id = ?").bind(account_id).bind(id).fetch_optional(pool).await |
|
| 179 | + | sqlx::query_as("SELECT * FROM notifications WHERE account_id = $1 AND id = $2").bind(account_id).bind(id).fetch_optional(pool).await |
|
| 176 | 180 | } |
|
| 177 | 181 | ||
| 178 | 182 | pub async fn inbox(pool: &Pool, account_id: i64, limit: i64) -> sqlx::Result<Vec<Notification>> { |
|
| 179 | - | sqlx::query_as("SELECT * FROM notifications WHERE account_id = ? ORDER BY read, at_ms DESC, id DESC LIMIT ?") |
|
| 183 | + | sqlx::query_as("SELECT * FROM notifications WHERE account_id = $1 ORDER BY read, at_ms DESC, id DESC LIMIT $2") |
|
| 180 | 184 | .bind(account_id) |
|
| 181 | 185 | .bind(limit) |
|
| 182 | 186 | .fetch_all(pool) |
|
| … | 1 unchanged lines |
||
| 184 | 188 | } |
|
| 185 | 189 | ||
| 186 | 190 | pub async fn unread(pool: &Pool, account_id: i64) -> sqlx::Result<i64> { |
|
| 187 | - | sqlx::query_scalar("SELECT COUNT(*) FROM notifications WHERE account_id = ? AND read = 0 AND done = 0") |
|
| 191 | + | sqlx::query_scalar("SELECT COUNT(*) FROM notifications WHERE account_id = $1 AND read = 0 AND done = 0") |
|
| 188 | 192 | .bind(account_id) |
|
| 189 | 193 | .fetch_one(pool) |
|
| 190 | 194 | .await |
|
| … | 2 unchanged lines |
||
| 193 | 197 | pub async fn mark_read(pool: &Pool, account_id: i64, id: Option<i64>) -> sqlx::Result<()> { |
|
| 194 | 198 | match id { |
|
| 195 | 199 | Some(id) => { |
|
| 196 | - | sqlx::query("UPDATE notifications SET read = 1 WHERE account_id = ? AND id = ?").bind(account_id).bind(id).execute(pool).await? |
|
| 200 | + | sqlx::query("UPDATE notifications SET read = 1 WHERE account_id = $1 AND id = $2") |
|
| 201 | + | .bind(account_id) |
|
| 202 | + | .bind(id) |
|
| 203 | + | .execute(pool) |
|
| 204 | + | .await? |
|
| 197 | 205 | } |
|
| 198 | - | None => sqlx::query("UPDATE notifications SET read = 1 WHERE account_id = ?").bind(account_id).execute(pool).await?, |
|
| 206 | + | None => sqlx::query("UPDATE notifications SET read = 1 WHERE account_id = $1").bind(account_id).execute(pool).await?, |
|
| 199 | 207 | }; |
|
| 200 | 208 | Ok(()) |
|
| 201 | 209 | } |
|
| … | 5 unchanged lines |
||
| 207 | 215 | /// (oxygenhub#183). |
|
| 208 | 216 | pub async fn owners_of(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<i64>> { |
|
| 209 | 217 | let owner: Option<String> = |
|
| 210 | - | sqlx::query_scalar("SELECT owner FROM repositories WHERE id = ?").bind(repo_id).fetch_optional(pool).await?; |
|
| 218 | + | sqlx::query_scalar("SELECT owner FROM repositories WHERE id = $1").bind(repo_id).fetch_optional(pool).await?; |
|
| 211 | 219 | let Some(owner) = owner else { return Ok(Vec::new()) }; |
|
| 212 | 220 | if let Some(a) = super::accounts::account_by_name(pool, &owner).await? { |
|
| 213 | 221 | return Ok(vec![a.id]); |
|
| … | 9 unchanged lines |
||
| 223 | 231 | /// Whether `account_id` watches `repo_id`: a watch it chose, or, for an |
|
| 224 | 232 | /// owner, unless it muted it. |
|
| 225 | 233 | pub async fn watching(pool: &Pool, account_id: i64, repo_id: i64) -> sqlx::Result<bool> { |
|
| 226 | - | let muted: Option<bool> = sqlx::query_scalar("SELECT muted FROM watches WHERE account_id = ? AND repo_id = ?") |
|
| 234 | + | let muted: Option<bool> = sqlx::query_scalar("SELECT muted FROM watches WHERE account_id = $1 AND repo_id = $2") |
|
| 227 | 235 | .bind(account_id) |
|
| 228 | 236 | .bind(repo_id) |
|
| 229 | 237 | .fetch_optional(pool) |
|
| … | 6 unchanged lines |
||
| 236 | 244 | ||
| 237 | 245 | /// Watch `repo_id`, or stop: for an owner, stopping mutes it. |
|
| 238 | 246 | pub async fn set_watch(pool: &Pool, account_id: i64, repo_id: i64, on: bool) -> sqlx::Result<()> { |
|
| 239 | - | sqlx::query("INSERT INTO watches (account_id, repo_id, muted) VALUES (?, ?, ?) ON CONFLICT (account_id, repo_id) DO UPDATE SET muted = excluded.muted") |
|
| 247 | + | sqlx::query("INSERT INTO watches (account_id, repo_id, muted) VALUES ($1, $2, $3) ON CONFLICT (account_id, repo_id) DO UPDATE SET muted = excluded.muted") |
|
| 240 | 248 | .bind(account_id) |
|
| 241 | 249 | .bind(repo_id) |
|
| 242 | 250 | .bind(!on) |
|
| … | 6 unchanged lines |
||
| 249 | 257 | /// have not muted it. |
|
| 250 | 258 | pub async fn watchers(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<i64>> { |
|
| 251 | 259 | let rows: Vec<(i64, bool)> = |
|
| 252 | - | sqlx::query_as("SELECT account_id, muted FROM watches WHERE repo_id = ?").bind(repo_id).fetch_all(pool).await?; |
|
| 260 | + | sqlx::query_as("SELECT account_id, muted FROM watches WHERE repo_id = $1").bind(repo_id).fetch_all(pool).await?; |
|
| 253 | 261 | let mut out: Vec<i64> = rows.iter().filter(|(_, m)| !m).map(|(a, _)| *a).collect(); |
|
| 254 | 262 | for o in owners_of(pool, repo_id).await? { |
|
| 255 | 263 | if !rows.iter().any(|(a, _)| *a == o) && !out.contains(&o) { |
|
| … | 4 unchanged lines |
||
| 260 | 268 | } |
|
| 261 | 269 | ||
| 262 | 270 | pub async fn subscribe(pool: &Pool, account_id: i64, kind: &str, target_id: i64) -> sqlx::Result<()> { |
|
| 263 | - | sqlx::query("INSERT OR IGNORE INTO subscriptions (account_id, kind, target_id) VALUES (?, ?, ?)") |
|
| 271 | + | sqlx::query("INSERT OR IGNORE INTO subscriptions (account_id, kind, target_id) VALUES ($1, $2, $3)") |
|
| 264 | 272 | .bind(account_id) |
|
| 265 | 273 | .bind(kind) |
|
| 266 | 274 | .bind(target_id) |
|
| … | 3 unchanged lines |
||
| 270 | 278 | } |
|
| 271 | 279 | ||
| 272 | 280 | pub async fn unsubscribe(pool: &Pool, account_id: i64, kind: &str, target_id: i64) -> sqlx::Result<()> { |
|
| 273 | - | sqlx::query("DELETE FROM subscriptions WHERE account_id = ? AND kind = ? AND target_id = ?") |
|
| 281 | + | sqlx::query("DELETE FROM subscriptions WHERE account_id = $1 AND kind = $2 AND target_id = $3") |
|
| 274 | 282 | .bind(account_id) |
|
| 275 | 283 | .bind(kind) |
|
| 276 | 284 | .bind(target_id) |
|
| … | 3 unchanged lines |
||
| 280 | 288 | } |
|
| 281 | 289 | ||
| 282 | 290 | pub async fn subscribed(pool: &Pool, account_id: i64, kind: &str, target_id: i64) -> sqlx::Result<bool> { |
|
| 283 | - | let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM subscriptions WHERE account_id = ? AND kind = ? AND target_id = ?") |
|
| 291 | + | let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM subscriptions WHERE account_id = $1 AND kind = $2 AND target_id = $3") |
|
| 284 | 292 | .bind(account_id) |
|
| 285 | 293 | .bind(kind) |
|
| 286 | 294 | .bind(target_id) |
|
| … | 3 unchanged lines |
||
| 290 | 298 | } |
|
| 291 | 299 | ||
| 292 | 300 | pub async fn subscribers(pool: &Pool, kind: &str, target_id: i64) -> sqlx::Result<Vec<i64>> { |
|
| 293 | - | sqlx::query_scalar("SELECT account_id FROM subscriptions WHERE kind = ? AND target_id = ?") |
|
| 301 | + | sqlx::query_scalar("SELECT account_id FROM subscriptions WHERE kind = $1 AND target_id = $2") |
|
| 294 | 302 | .bind(kind) |
|
| 295 | 303 | .bind(target_id) |
|
| 296 | 304 | .fetch_all(pool) |
|
| … | 14 unchanged lines |
||
| 311 | 319 | ||
| 312 | 320 | /// Replace the indexed text of one thread (title plus body plus comments). |
|
| 313 | 321 | pub async fn index(pool: &Pool, kind: &str, repo_id: i64, target_id: i64, number: i64, title: &str, body: &str) -> sqlx::Result<()> { |
|
| 314 | - | sqlx::query("DELETE FROM search WHERE kind = ? AND target_id = ?").bind(kind).bind(target_id).execute(pool).await?; |
|
| 315 | - | sqlx::query("INSERT INTO search (kind, repo_id, target_id, number, title, body) VALUES (?, ?, ?, ?, ?, ?)") |
|
| 322 | + | sqlx::query("DELETE FROM search WHERE kind = $1 AND target_id = $2").bind(kind).bind(target_id).execute(pool).await?; |
|
| 323 | + | sqlx::query("INSERT INTO search (kind, repo_id, target_id, number, title, body) VALUES ($1, $2, $3, $4, $5, $6)") |
|
| 316 | 324 | .bind(kind) |
|
| 317 | 325 | .bind(repo_id) |
|
| 318 | 326 | .bind(target_id) |
|
| … | 13 unchanged lines |
||
| 332 | 340 | return Ok(vec![]); |
|
| 333 | 341 | } |
|
| 334 | 342 | let q = terms.join(" "); |
|
| 335 | - | let sql = "SELECT kind, repo_id, target_id, number, title, snippet(search, 5, '<mark>', '</mark>', '…', 12) AS snippet FROM search WHERE search MATCH ? AND (? IS NULL OR repo_id = ?) ORDER BY rank LIMIT ?"; |
|
| 343 | + | let sql = "SELECT kind, repo_id, target_id, number, title, snippet(search, 5, '<mark>', '</mark>', '…', 12) AS snippet FROM search WHERE search MATCH $1 AND ($2 IS NULL OR repo_id = $3) ORDER BY rank LIMIT $4"; |
|
| 336 | 344 | sqlx::query_as(sql).bind(q).bind(repo_id).bind(repo_id).bind(limit).fetch_all(pool).await |
|
| 337 | 345 | } |
modified crates/hub/src/db/projects.rs +35 −35
| … | 42 unchanged lines |
||
| 43 | 43 | pub async fn create(pool: &Pool, owner: &str, title: &str, description: &str, public: bool) -> sqlx::Result<Project> { |
|
| 44 | 44 | let mut tx = super::begin_write(pool).await?; |
|
| 45 | 45 | let number: i64 = |
|
| 46 | - | sqlx::query_scalar("SELECT COALESCE(MAX(number), 0) + 1 FROM projects WHERE owner = ?").bind(owner).fetch_one(&mut *tx).await?; |
|
| 46 | + | sqlx::query_scalar("SELECT COALESCE(MAX(number), 0) + 1 FROM projects WHERE owner = $1").bind(owner).fetch_one(&mut *tx).await?; |
|
| 47 | 47 | let now = now_ms(); |
|
| 48 | 48 | let p: Project = sqlx::query_as( |
|
| 49 | - | "INSERT INTO projects (owner, number, title, description, public, created_ms, updated_ms) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *", |
|
| 49 | + | "INSERT INTO projects (owner, number, title, description, public, created_ms, updated_ms) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", |
|
| 50 | 50 | ) |
|
| 51 | 51 | .bind(owner) |
|
| 52 | 52 | .bind(number) |
|
| … | 5 unchanged lines |
||
| 58 | 58 | .fetch_one(&mut *tx) |
|
| 59 | 59 | .await?; |
|
| 60 | 60 | // A board here now: an old address of a transferred one gives way (#125). |
|
| 61 | - | sqlx::query("DELETE FROM project_redirects WHERE owner = ? AND number = ?").bind(owner).bind(number).execute(&mut *tx).await?; |
|
| 61 | + | sqlx::query("DELETE FROM project_redirects WHERE owner = $1 AND number = $2").bind(owner).bind(number).execute(&mut *tx).await?; |
|
| 62 | 62 | for (i, name) in DEFAULT_COLUMNS.iter().enumerate() { |
|
| 63 | - | sqlx::query("INSERT INTO project_columns (project_id, name, position) VALUES (?, ?, ?)") |
|
| 63 | + | sqlx::query("INSERT INTO project_columns (project_id, name, position) VALUES ($1, $2, $3)") |
|
| 64 | 64 | .bind(p.id) |
|
| 65 | 65 | .bind(name) |
|
| 66 | 66 | .bind(i as i64) |
|
| … | 5 unchanged lines |
||
| 72 | 72 | } |
|
| 73 | 73 | ||
| 74 | 74 | pub async fn of_owner(pool: &Pool, owner: &str) -> sqlx::Result<Vec<Project>> { |
|
| 75 | - | sqlx::query_as("SELECT * FROM projects WHERE owner = ? ORDER BY closed, updated_ms DESC").bind(owner).fetch_all(pool).await |
|
| 75 | + | sqlx::query_as("SELECT * FROM projects WHERE owner = $1 ORDER BY closed, updated_ms DESC").bind(owner).fetch_all(pool).await |
|
| 76 | 76 | } |
|
| 77 | 77 | ||
| 78 | 78 | /// Move project `id` to owner `to` (#125): it takes the next number there, |
|
| 79 | 79 | /// with its columns, cards and privacy, and its old address redirects to it. |
|
| 80 | 80 | pub async fn transfer(pool: &Pool, id: i64, to: &str) -> sqlx::Result<Project> { |
|
| 81 | 81 | let mut tx = super::begin_write(pool).await?; |
|
| 82 | - | let old: Project = sqlx::query_as("SELECT * FROM projects WHERE id = ?").bind(id).fetch_one(&mut *tx).await?; |
|
| 82 | + | let old: Project = sqlx::query_as("SELECT * FROM projects WHERE id = $1").bind(id).fetch_one(&mut *tx).await?; |
|
| 83 | 83 | let number: i64 = |
|
| 84 | - | sqlx::query_scalar("SELECT COALESCE(MAX(number), 0) + 1 FROM projects WHERE owner = ?").bind(to).fetch_one(&mut *tx).await?; |
|
| 85 | - | sqlx::query("INSERT OR REPLACE INTO project_redirects (owner, number, project_id) VALUES (?, ?, ?)") |
|
| 84 | + | sqlx::query_scalar("SELECT COALESCE(MAX(number), 0) + 1 FROM projects WHERE owner = $1").bind(to).fetch_one(&mut *tx).await?; |
|
| 85 | + | sqlx::query("INSERT OR REPLACE INTO project_redirects (owner, number, project_id) VALUES ($1, $2, $3)") |
|
| 86 | 86 | .bind(&old.owner) |
|
| 87 | 87 | .bind(old.number) |
|
| 88 | 88 | .bind(id) |
|
| 89 | 89 | .execute(&mut *tx) |
|
| 90 | 90 | .await?; |
|
| 91 | - | sqlx::query("DELETE FROM project_redirects WHERE owner = ? AND number = ?").bind(to).bind(number).execute(&mut *tx).await?; |
|
| 92 | - | let moved: Project = sqlx::query_as("UPDATE projects SET owner = ?, number = ?, updated_ms = ? WHERE id = ? RETURNING *") |
|
| 91 | + | sqlx::query("DELETE FROM project_redirects WHERE owner = $1 AND number = $2").bind(to).bind(number).execute(&mut *tx).await?; |
|
| 92 | + | let moved: Project = sqlx::query_as("UPDATE projects SET owner = $1, number = $2, updated_ms = $3 WHERE id = $4 RETURNING *") |
|
| 93 | 93 | .bind(to) |
|
| 94 | 94 | .bind(number) |
|
| 95 | 95 | .bind(now_ms()) |
|
| … | 7 unchanged lines |
||
| 103 | 103 | /// The project an old address (`owner`, `number`) now redirects to, after |
|
| 104 | 104 | /// a transfer (#125). |
|
| 105 | 105 | pub async fn redirected(pool: &Pool, owner: &str, number: i64) -> sqlx::Result<Option<Project>> { |
|
| 106 | - | sqlx::query_as("SELECT p.* FROM project_redirects r JOIN projects p ON p.id = r.project_id WHERE r.owner = ? AND r.number = ?") |
|
| 106 | + | sqlx::query_as("SELECT p.* FROM project_redirects r JOIN projects p ON p.id = r.project_id WHERE r.owner = $1 AND r.number = $2") |
|
| 107 | 107 | .bind(owner) |
|
| 108 | 108 | .bind(number) |
|
| 109 | 109 | .fetch_optional(pool) |
|
| … | 1 unchanged lines |
||
| 111 | 111 | } |
|
| 112 | 112 | ||
| 113 | 113 | pub async fn get(pool: &Pool, owner: &str, number: i64) -> sqlx::Result<Option<Project>> { |
|
| 114 | - | sqlx::query_as("SELECT * FROM projects WHERE owner = ? AND number = ?").bind(owner).bind(number).fetch_optional(pool).await |
|
| 114 | + | sqlx::query_as("SELECT * FROM projects WHERE owner = $1 AND number = $2").bind(owner).bind(number).fetch_optional(pool).await |
|
| 115 | 115 | } |
|
| 116 | 116 | ||
| 117 | 117 | pub async fn set(pool: &Pool, id: i64, title: &str, description: &str, public: bool, closed: bool) -> sqlx::Result<()> { |
|
| 118 | - | sqlx::query("UPDATE projects SET title = ?, description = ?, public = ?, closed = ?, updated_ms = ? WHERE id = ?") |
|
| 118 | + | sqlx::query("UPDATE projects SET title = $1, description = $2, public = $3, closed = $4, updated_ms = $5 WHERE id = $6") |
|
| 119 | 119 | .bind(title) |
|
| 120 | 120 | .bind(description) |
|
| 121 | 121 | .bind(public) |
|
| … | 6 unchanged lines |
||
| 128 | 128 | } |
|
| 129 | 129 | ||
| 130 | 130 | pub async fn delete(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 131 | - | sqlx::query("DELETE FROM projects WHERE id = ?").bind(id).execute(pool).await?; |
|
| 131 | + | sqlx::query("DELETE FROM projects WHERE id = $1").bind(id).execute(pool).await?; |
|
| 132 | 132 | Ok(()) |
|
| 133 | 133 | } |
|
| 134 | 134 | ||
| 135 | 135 | async fn touch(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 136 | - | sqlx::query("UPDATE projects SET updated_ms = ? WHERE id = ?").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 136 | + | sqlx::query("UPDATE projects SET updated_ms = $1 WHERE id = $2").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 137 | 137 | Ok(()) |
|
| 138 | 138 | } |
|
| 139 | 139 | ||
| 140 | 140 | pub async fn columns(pool: &Pool, project_id: i64) -> sqlx::Result<Vec<Column>> { |
|
| 141 | - | sqlx::query_as("SELECT * FROM project_columns WHERE project_id = ? ORDER BY position, id").bind(project_id).fetch_all(pool).await |
|
| 141 | + | sqlx::query_as("SELECT * FROM project_columns WHERE project_id = $1 ORDER BY position, id").bind(project_id).fetch_all(pool).await |
|
| 142 | 142 | } |
|
| 143 | 143 | ||
| 144 | 144 | pub async fn cards(pool: &Pool, project_id: i64) -> sqlx::Result<Vec<Card>> { |
|
| 145 | - | sqlx::query_as("SELECT * FROM project_cards WHERE project_id = ? ORDER BY position, id").bind(project_id).fetch_all(pool).await |
|
| 145 | + | sqlx::query_as("SELECT * FROM project_cards WHERE project_id = $1 ORDER BY position, id").bind(project_id).fetch_all(pool).await |
|
| 146 | 146 | } |
|
| 147 | 147 | ||
| 148 | 148 | pub async fn add_column(pool: &Pool, project_id: i64, name: &str) -> sqlx::Result<()> { |
|
| 149 | - | let pos: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(position), -1) + 1 FROM project_columns WHERE project_id = ?") |
|
| 149 | + | let pos: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(position), -1) + 1 FROM project_columns WHERE project_id = $1") |
|
| 150 | 150 | .bind(project_id) |
|
| 151 | 151 | .fetch_one(pool) |
|
| 152 | 152 | .await?; |
|
| 153 | - | sqlx::query("INSERT INTO project_columns (project_id, name, position) VALUES (?, ?, ?)") |
|
| 153 | + | sqlx::query("INSERT INTO project_columns (project_id, name, position) VALUES ($1, $2, $3)") |
|
| 154 | 154 | .bind(project_id) |
|
| 155 | 155 | .bind(name) |
|
| 156 | 156 | .bind(pos) |
|
| … | 3 unchanged lines |
||
| 160 | 160 | } |
|
| 161 | 161 | ||
| 162 | 162 | pub async fn rename_column(pool: &Pool, project_id: i64, column_id: i64, name: &str) -> sqlx::Result<()> { |
|
| 163 | - | sqlx::query("UPDATE project_columns SET name = ? WHERE id = ? AND project_id = ?") |
|
| 163 | + | sqlx::query("UPDATE project_columns SET name = $1 WHERE id = $2 AND project_id = $3") |
|
| 164 | 164 | .bind(name) |
|
| 165 | 165 | .bind(column_id) |
|
| 166 | 166 | .bind(project_id) |
|
| … | 13 unchanged lines |
||
| 180 | 180 | let mut ids: Vec<i64> = cols.iter().map(|c| c.id).collect(); |
|
| 181 | 181 | ids.swap(i, j as usize); |
|
| 182 | 182 | for (pos, id) in ids.iter().enumerate() { |
|
| 183 | - | sqlx::query("UPDATE project_columns SET position = ? WHERE id = ?").bind(pos as i64).bind(id).execute(pool).await?; |
|
| 183 | + | sqlx::query("UPDATE project_columns SET position = $1 WHERE id = $2").bind(pos as i64).bind(id).execute(pool).await?; |
|
| 184 | 184 | } |
|
| 185 | 185 | touch(pool, project_id).await |
|
| 186 | 186 | } |
|
| … | 6 unchanged lines |
||
| 193 | 193 | return Ok(false); |
|
| 194 | 194 | } |
|
| 195 | 195 | let mut tx = super::begin_write(pool).await?; |
|
| 196 | - | let base: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = ?") |
|
| 196 | + | let base: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = $1") |
|
| 197 | 197 | .bind(to.id) |
|
| 198 | 198 | .fetch_one(&mut *tx) |
|
| 199 | 199 | .await?; |
|
| 200 | - | sqlx::query("UPDATE project_cards SET column_id = ?, position = position + ? WHERE column_id = ?") |
|
| 200 | + | sqlx::query("UPDATE project_cards SET column_id = $1, position = position + $2 WHERE column_id = $3") |
|
| 201 | 201 | .bind(to.id) |
|
| 202 | 202 | .bind(base) |
|
| 203 | 203 | .bind(column_id) |
|
| 204 | 204 | .execute(&mut *tx) |
|
| 205 | 205 | .await?; |
|
| 206 | - | sqlx::query("DELETE FROM project_columns WHERE id = ?").bind(column_id).execute(&mut *tx).await?; |
|
| 206 | + | sqlx::query("DELETE FROM project_columns WHERE id = $1").bind(column_id).execute(&mut *tx).await?; |
|
| 207 | 207 | tx.commit().await?; |
|
| 208 | 208 | touch(pool, project_id).await?; |
|
| 209 | 209 | Ok(true) |
|
| … | 11 unchanged lines |
||
| 221 | 221 | /// Add a card at the bottom of a column; `Ok(false)` when that issue or |
|
| 222 | 222 | /// proposal is already on the project. |
|
| 223 | 223 | pub async fn add_card(pool: &Pool, project_id: i64, c: NewCard<'_>) -> sqlx::Result<bool> { |
|
| 224 | - | let pos: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = ?") |
|
| 224 | + | let pos: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = $1") |
|
| 225 | 225 | .bind(c.column_id) |
|
| 226 | 226 | .fetch_one(pool) |
|
| 227 | 227 | .await?; |
|
| 228 | 228 | let r = sqlx::query( |
|
| 229 | - | "INSERT OR IGNORE INTO project_cards (project_id, column_id, position, kind, repo_id, number, note, added_by, created_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", |
|
| 229 | + | "INSERT OR IGNORE INTO project_cards (project_id, column_id, position, kind, repo_id, number, note, added_by, created_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", |
|
| 230 | 230 | ) |
|
| 231 | 231 | .bind(project_id) |
|
| 232 | 232 | .bind(c.column_id) |
|
| … | 12 unchanged lines |
||
| 245 | 245 | ||
| 246 | 246 | /// Move a card to the bottom of another column of the same project. |
|
| 247 | 247 | pub async fn move_card(pool: &Pool, project_id: i64, card_id: i64, column_id: i64) -> sqlx::Result<()> { |
|
| 248 | - | let ok: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM project_columns WHERE id = ? AND project_id = ?") |
|
| 248 | + | let ok: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM project_columns WHERE id = $1 AND project_id = $2") |
|
| 249 | 249 | .bind(column_id) |
|
| 250 | 250 | .bind(project_id) |
|
| 251 | 251 | .fetch_one(pool) |
|
| … | 1 unchanged lines |
||
| 253 | 253 | if ok == 0 { |
|
| 254 | 254 | return Ok(()); |
|
| 255 | 255 | } |
|
| 256 | - | let pos: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = ?") |
|
| 256 | + | let pos: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(position), -1) + 1 FROM project_cards WHERE column_id = $1") |
|
| 257 | 257 | .bind(column_id) |
|
| 258 | 258 | .fetch_one(pool) |
|
| 259 | 259 | .await?; |
|
| 260 | - | sqlx::query("UPDATE project_cards SET column_id = ?, position = ? WHERE id = ? AND project_id = ?") |
|
| 260 | + | sqlx::query("UPDATE project_cards SET column_id = $1, position = $2 WHERE id = $3 AND project_id = $4") |
|
| 261 | 261 | .bind(column_id) |
|
| 262 | 262 | .bind(pos) |
|
| 263 | 263 | .bind(card_id) |
|
| … | 5 unchanged lines |
||
| 269 | 269 | ||
| 270 | 270 | /// Swap a card with its neighbour in its column (`-1` up, `1` down). |
|
| 271 | 271 | pub async fn shift_card(pool: &Pool, project_id: i64, card_id: i64, by: i64) -> sqlx::Result<()> { |
|
| 272 | - | let Some(card): Option<Card> = sqlx::query_as("SELECT * FROM project_cards WHERE id = ? AND project_id = ?") |
|
| 272 | + | let Some(card): Option<Card> = sqlx::query_as("SELECT * FROM project_cards WHERE id = $1 AND project_id = $2") |
|
| 273 | 273 | .bind(card_id) |
|
| 274 | 274 | .bind(project_id) |
|
| 275 | 275 | .fetch_optional(pool) |
|
| … | 1 unchanged lines |
||
| 277 | 277 | else { |
|
| 278 | 278 | return Ok(()); |
|
| 279 | 279 | }; |
|
| 280 | - | let col: Vec<Card> = sqlx::query_as("SELECT * FROM project_cards WHERE column_id = ? ORDER BY position, id") |
|
| 280 | + | let col: Vec<Card> = sqlx::query_as("SELECT * FROM project_cards WHERE column_id = $1 ORDER BY position, id") |
|
| 281 | 281 | .bind(card.column_id) |
|
| 282 | 282 | .fetch_all(pool) |
|
| 283 | 283 | .await?; |
|
| … | 5 unchanged lines |
||
| 289 | 289 | let mut ids: Vec<i64> = col.iter().map(|c| c.id).collect(); |
|
| 290 | 290 | ids.swap(i, j as usize); |
|
| 291 | 291 | for (pos, id) in ids.iter().enumerate() { |
|
| 292 | - | sqlx::query("UPDATE project_cards SET position = ? WHERE id = ?").bind(pos as i64).bind(id).execute(pool).await?; |
|
| 292 | + | sqlx::query("UPDATE project_cards SET position = $1 WHERE id = $2").bind(pos as i64).bind(id).execute(pool).await?; |
|
| 293 | 293 | } |
|
| 294 | 294 | touch(pool, project_id).await |
|
| 295 | 295 | } |
|
| 296 | 296 | ||
| 297 | 297 | pub async fn remove_card(pool: &Pool, project_id: i64, card_id: i64) -> sqlx::Result<()> { |
|
| 298 | - | sqlx::query("DELETE FROM project_cards WHERE id = ? AND project_id = ?").bind(card_id).bind(project_id).execute(pool).await?; |
|
| 298 | + | sqlx::query("DELETE FROM project_cards WHERE id = $1 AND project_id = $2").bind(card_id).bind(project_id).execute(pool).await?; |
|
| 299 | 299 | touch(pool, project_id).await |
|
| 300 | 300 | } |
|
| 301 | 301 | ||
| 302 | 302 | /// The projects (and column names) an issue or proposal is on. |
|
| 303 | 303 | pub async fn placements(pool: &Pool, repo_id: i64, number: i64) -> sqlx::Result<Vec<(Project, String)>> { |
|
| 304 | 304 | let rows: Vec<(i64, String)> = sqlx::query_as( |
|
| 305 | - | "SELECT c.project_id, col.name FROM project_cards c JOIN project_columns col ON col.id = c.column_id WHERE c.repo_id = ? AND c.number = ? AND c.kind <> 'note'", |
|
| 305 | + | "SELECT c.project_id, col.name FROM project_cards c JOIN project_columns col ON col.id = c.column_id WHERE c.repo_id = $1 AND c.number = $2 AND c.kind <> 'note'", |
|
| 306 | 306 | ) |
|
| 307 | 307 | .bind(repo_id) |
|
| 308 | 308 | .bind(number) |
|
| … | 1 unchanged lines |
||
| 310 | 310 | .await?; |
|
| 311 | 311 | let mut out = Vec::new(); |
|
| 312 | 312 | for (pid, col) in rows { |
|
| 313 | - | if let Some(p) = sqlx::query_as::<_, Project>("SELECT * FROM projects WHERE id = ?").bind(pid).fetch_optional(pool).await? { |
|
| 313 | + | if let Some(p) = sqlx::query_as::<_, Project>("SELECT * FROM projects WHERE id = $1").bind(pid).fetch_optional(pool).await? { |
|
| 314 | 314 | out.push((p, col)); |
|
| 315 | 315 | } |
|
| 316 | 316 | } |
|
| … | 2 unchanged lines |
modified crates/hub/src/db/proposals.rs +25 −25
| … | 95 unchanged lines |
||
| 96 | 96 | /// Take the next number of a repository's shared number space. |
|
| 97 | 97 | pub async fn next_number(pool: &Pool, repo_id: i64) -> sqlx::Result<i64> { |
|
| 98 | 98 | let mut tx = super::begin_write(pool).await?; |
|
| 99 | - | sqlx::query("INSERT OR IGNORE INTO numbers (repo_id, next) VALUES (?, 1)").bind(repo_id).execute(&mut *tx).await?; |
|
| 100 | - | let n: i64 = sqlx::query_scalar("SELECT next FROM numbers WHERE repo_id = ?").bind(repo_id).fetch_one(&mut *tx).await?; |
|
| 101 | - | sqlx::query("UPDATE numbers SET next = next + 1 WHERE repo_id = ?").bind(repo_id).execute(&mut *tx).await?; |
|
| 99 | + | sqlx::query("INSERT OR IGNORE INTO numbers (repo_id, next) VALUES ($1, 1)").bind(repo_id).execute(&mut *tx).await?; |
|
| 100 | + | let n: i64 = sqlx::query_scalar("SELECT next FROM numbers WHERE repo_id = $1").bind(repo_id).fetch_one(&mut *tx).await?; |
|
| 101 | + | sqlx::query("UPDATE numbers SET next = next + 1 WHERE repo_id = $1").bind(repo_id).execute(&mut *tx).await?; |
|
| 102 | 102 | tx.commit().await?; |
|
| 103 | 103 | Ok(n) |
|
| 104 | 104 | } |
|
| … | 17 unchanged lines |
||
| 122 | 122 | let now = now_ms(); |
|
| 123 | 123 | sqlx::query_as( |
|
| 124 | 124 | "INSERT INTO proposals (repo_id, number, title, body, author_id, author, state, target, source_kind, source_repo_id, source_bookmark, source_changes, created_ms, updated_ms) \ |
|
| 125 | - | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *", |
|
| 125 | + | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *", |
|
| 126 | 126 | ) |
|
| 127 | 127 | .bind(p.repo_id) |
|
| 128 | 128 | .bind(number) |
|
| … | 14 unchanged lines |
||
| 143 | 143 | } |
|
| 144 | 144 | ||
| 145 | 145 | pub async fn get(pool: &Pool, repo_id: i64, number: i64) -> sqlx::Result<Option<ProposalRow>> { |
|
| 146 | - | sqlx::query_as("SELECT * FROM proposals WHERE repo_id = ? AND number = ?").bind(repo_id).bind(number).fetch_optional(pool).await |
|
| 146 | + | sqlx::query_as("SELECT * FROM proposals WHERE repo_id = $1 AND number = $2").bind(repo_id).bind(number).fetch_optional(pool).await |
|
| 147 | 147 | } |
|
| 148 | 148 | ||
| 149 | 149 | pub async fn list(pool: &Pool, repo_id: i64, states: &[&str]) -> sqlx::Result<Vec<ProposalRow>> { |
|
| 150 | 150 | let all: Vec<ProposalRow> = |
|
| 151 | - | sqlx::query_as("SELECT * FROM proposals WHERE repo_id = ? ORDER BY number DESC").bind(repo_id).fetch_all(pool).await?; |
|
| 151 | + | sqlx::query_as("SELECT * FROM proposals WHERE repo_id = $1 ORDER BY number DESC").bind(repo_id).fetch_all(pool).await?; |
|
| 152 | 152 | Ok(all.into_iter().filter(|p| states.is_empty() || states.contains(&p.state.as_str())).collect()) |
|
| 153 | 153 | } |
|
| 154 | 154 | ||
| 155 | 155 | /// Open proposals of every repository, for the front page. |
|
| 156 | 156 | pub async fn open_for_author(pool: &Pool, author_id: i64) -> sqlx::Result<Vec<ProposalRow>> { |
|
| 157 | - | sqlx::query_as("SELECT * FROM proposals WHERE author_id = ? AND state IN ('open', 'draft') ORDER BY updated_ms DESC") |
|
| 157 | + | sqlx::query_as("SELECT * FROM proposals WHERE author_id = $1 AND state IN ('open', 'draft') ORDER BY updated_ms DESC") |
|
| 158 | 158 | .bind(author_id) |
|
| 159 | 159 | .fetch_all(pool) |
|
| 160 | 160 | .await |
|
| … | 8 unchanged lines |
||
| 169 | 169 | changes: &str, |
|
| 170 | 170 | conflicted: bool, |
|
| 171 | 171 | ) -> sqlx::Result<()> { |
|
| 172 | - | sqlx::query("UPDATE proposals SET projected_state = ?, projected_heads = ?, projected_target = ?, projected_changes = ?, conflicted = ? WHERE id = ?") |
|
| 172 | + | sqlx::query("UPDATE proposals SET projected_state = $1, projected_heads = $2, projected_target = $3, projected_changes = $4, conflicted = $5 WHERE id = $6") |
|
| 173 | 173 | .bind(state) |
|
| 174 | 174 | .bind(heads) |
|
| 175 | 175 | .bind(target_state) |
|
| … | 6 unchanged lines |
||
| 182 | 182 | } |
|
| 183 | 183 | ||
| 184 | 184 | pub async fn set_state(pool: &Pool, id: i64, state: &str, landed_state: &str) -> sqlx::Result<()> { |
|
| 185 | - | sqlx::query("UPDATE proposals SET state = ?, landed_state = ?, updated_ms = ? WHERE id = ?") |
|
| 185 | + | sqlx::query("UPDATE proposals SET state = $1, landed_state = $2, updated_ms = $3 WHERE id = $4") |
|
| 186 | 186 | .bind(state) |
|
| 187 | 187 | .bind(landed_state) |
|
| 188 | 188 | .bind(now_ms()) |
|
| … | 7 unchanged lines |
||
| 196 | 196 | pub async fn set_landed(pool: &Pool, id: i64, landed_state: &str, by: &str, via: &str) -> sqlx::Result<()> { |
|
| 197 | 197 | let now = now_ms(); |
|
| 198 | 198 | sqlx::query( |
|
| 199 | - | "UPDATE proposals SET state = 'landed', landed_state = ?, landed_by = ?, landed_via = ?, landed_ms = ?, updated_ms = ? WHERE id = ?", |
|
| 199 | + | "UPDATE proposals SET state = 'landed', landed_state = $1, landed_by = $2, landed_via = $3, landed_ms = $4, updated_ms = $5 WHERE id = $6", |
|
| 200 | 200 | ) |
|
| 201 | 201 | .bind(landed_state) |
|
| 202 | 202 | .bind(by) |
|
| … | 7 unchanged lines |
||
| 210 | 210 | } |
|
| 211 | 211 | ||
| 212 | 212 | pub async fn edit(pool: &Pool, id: i64, title: &str, body: &str, target: &str) -> sqlx::Result<()> { |
|
| 213 | - | sqlx::query("UPDATE proposals SET title = ?, body = ?, target = ?, updated_ms = ? WHERE id = ?") |
|
| 213 | + | sqlx::query("UPDATE proposals SET title = $1, body = $2, target = $3, updated_ms = $4 WHERE id = $5") |
|
| 214 | 214 | .bind(title) |
|
| 215 | 215 | .bind(body) |
|
| 216 | 216 | .bind(target) |
|
| … | 5 unchanged lines |
||
| 222 | 222 | } |
|
| 223 | 223 | ||
| 224 | 224 | pub async fn touch(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 225 | - | sqlx::query("UPDATE proposals SET updated_ms = ? WHERE id = ?").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 225 | + | sqlx::query("UPDATE proposals SET updated_ms = $1 WHERE id = $2").bind(now_ms()).bind(id).execute(pool).await?; |
|
| 226 | 226 | Ok(()) |
|
| 227 | 227 | } |
|
| 228 | 228 | ||
| 229 | 229 | pub async fn comments(pool: &Pool, proposal_id: i64) -> sqlx::Result<Vec<CommentRow>> { |
|
| 230 | - | sqlx::query_as("SELECT * FROM proposal_comments WHERE proposal_id = ? ORDER BY created_ms, id").bind(proposal_id).fetch_all(pool).await |
|
| 230 | + | sqlx::query_as("SELECT * FROM proposal_comments WHERE proposal_id = $1 ORDER BY created_ms, id").bind(proposal_id).fetch_all(pool).await |
|
| 231 | 231 | } |
|
| 232 | 232 | ||
| 233 | 233 | pub async fn add_comment( |
|
| … | 6 unchanged lines |
||
| 240 | 240 | anchor: Option<(&str, i64)>, |
|
| 241 | 241 | ) -> sqlx::Result<CommentRow> { |
|
| 242 | 242 | let row = sqlx::query_as( |
|
| 243 | - | "INSERT INTO proposal_comments (proposal_id, author_id, author, body, path, anchor_change, anchor_index, created_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *", |
|
| 243 | + | "INSERT INTO proposal_comments (proposal_id, author_id, author, body, path, anchor_change, anchor_index, created_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *", |
|
| 244 | 244 | ) |
|
| 245 | 245 | .bind(proposal_id) |
|
| 246 | 246 | .bind(author_id) |
|
| … | 20 unchanged lines |
||
| 267 | 267 | via: &str, |
|
| 268 | 268 | ) -> sqlx::Result<CommentRow> { |
|
| 269 | 269 | let row = sqlx::query_as( |
|
| 270 | - | "INSERT INTO proposal_comments (proposal_id, author_id, author, body, path, anchor_change, anchor_index, created_ms, via) VALUES (?, ?, ?, ?, '', '', 0, ?, ?) RETURNING *", |
|
| 270 | + | "INSERT INTO proposal_comments (proposal_id, author_id, author, body, path, anchor_change, anchor_index, created_ms, via) VALUES ($1, $2, $3, $4, '', '', 0, $5, $6) RETURNING *", |
|
| 271 | 271 | ) |
|
| 272 | 272 | .bind(proposal_id) |
|
| 273 | 273 | .bind(author_id) |
|
| … | 8 unchanged lines |
||
| 282 | 282 | } |
|
| 283 | 283 | ||
| 284 | 284 | pub async fn set_comment_resolved(pool: &Pool, id: i64, resolved: bool) -> sqlx::Result<()> { |
|
| 285 | - | sqlx::query("UPDATE proposal_comments SET resolved = ? WHERE id = ?").bind(resolved).bind(id).execute(pool).await?; |
|
| 285 | + | sqlx::query("UPDATE proposal_comments SET resolved = $1 WHERE id = $2").bind(resolved).bind(id).execute(pool).await?; |
|
| 286 | 286 | Ok(()) |
|
| 287 | 287 | } |
|
| 288 | 288 | ||
| 289 | 289 | pub async fn reviews(pool: &Pool, proposal_id: i64) -> sqlx::Result<Vec<ReviewRow>> { |
|
| 290 | - | sqlx::query_as("SELECT * FROM proposal_reviews WHERE proposal_id = ? ORDER BY created_ms, id").bind(proposal_id).fetch_all(pool).await |
|
| 290 | + | sqlx::query_as("SELECT * FROM proposal_reviews WHERE proposal_id = $1 ORDER BY created_ms, id").bind(proposal_id).fetch_all(pool).await |
|
| 291 | 291 | } |
|
| 292 | 292 | ||
| 293 | 293 | pub async fn add_review(pool: &Pool, proposal_id: i64, author_id: i64, author: &str, verdict: &str, body: &str) -> sqlx::Result<ReviewRow> { |
|
| … | 11 unchanged lines |
||
| 305 | 305 | via: &str, |
|
| 306 | 306 | ) -> sqlx::Result<ReviewRow> { |
|
| 307 | 307 | let row = sqlx::query_as( |
|
| 308 | - | "INSERT INTO proposal_reviews (proposal_id, author_id, author, verdict, body, created_ms, via) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *", |
|
| 308 | + | "INSERT INTO proposal_reviews (proposal_id, author_id, author, verdict, body, created_ms, via) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", |
|
| 309 | 309 | ) |
|
| 310 | 310 | .bind(proposal_id) |
|
| 311 | 311 | .bind(author_id) |
|
| … | 9 unchanged lines |
||
| 321 | 321 | } |
|
| 322 | 322 | ||
| 323 | 323 | pub async fn events(pool: &Pool, proposal_id: i64) -> sqlx::Result<Vec<EventRow>> { |
|
| 324 | - | sqlx::query_as("SELECT id, at_ms, actor, event, detail FROM proposal_events WHERE proposal_id = ? ORDER BY at_ms, id") |
|
| 324 | + | sqlx::query_as("SELECT id, at_ms, actor, event, detail FROM proposal_events WHERE proposal_id = $1 ORDER BY at_ms, id") |
|
| 325 | 325 | .bind(proposal_id) |
|
| 326 | 326 | .fetch_all(pool) |
|
| 327 | 327 | .await |
|
| 328 | 328 | } |
|
| 329 | 329 | ||
| 330 | 330 | pub async fn add_event(pool: &Pool, proposal_id: i64, actor: &str, event: &str, detail: &str) -> sqlx::Result<()> { |
|
| 331 | - | sqlx::query("INSERT INTO proposal_events (proposal_id, at_ms, actor, event, detail) VALUES (?, ?, ?, ?, ?)") |
|
| 331 | + | sqlx::query("INSERT INTO proposal_events (proposal_id, at_ms, actor, event, detail) VALUES ($1, $2, $3, $4, $5)") |
|
| 332 | 332 | .bind(proposal_id) |
|
| 333 | 333 | .bind(now_ms()) |
|
| 334 | 334 | .bind(actor) |
|
| … | 13 unchanged lines |
||
| 348 | 348 | } |
|
| 349 | 349 | ||
| 350 | 350 | pub async fn requirements(pool: &Pool, repo_id: i64) -> sqlx::Result<Requirements> { |
|
| 351 | - | sqlx::query_as("SELECT required_approvals, require_owners, allow_conflicts FROM repositories WHERE id = ?") |
|
| 351 | + | sqlx::query_as("SELECT required_approvals, require_owners, allow_conflicts FROM repositories WHERE id = $1") |
|
| 352 | 352 | .bind(repo_id) |
|
| 353 | 353 | .fetch_one(pool) |
|
| 354 | 354 | .await |
|
| 355 | 355 | } |
|
| 356 | 356 | ||
| 357 | 357 | pub async fn set_requirements(pool: &Pool, repo_id: i64, r: Requirements) -> sqlx::Result<()> { |
|
| 358 | - | sqlx::query("UPDATE repositories SET required_approvals = ?, require_owners = ?, allow_conflicts = ? WHERE id = ?") |
|
| 358 | + | sqlx::query("UPDATE repositories SET required_approvals = $1, require_owners = $2, allow_conflicts = $3 WHERE id = $4") |
|
| 359 | 359 | .bind(r.required_approvals) |
|
| 360 | 360 | .bind(r.require_owners) |
|
| 361 | 361 | .bind(r.allow_conflicts) |
|
| … | 8 unchanged lines |
||
| 370 | 370 | /// Ask an account to review a proposal; false when it was already asked. |
|
| 371 | 371 | pub async fn request_review(pool: &Pool, proposal_id: i64, account_id: i64, by: &str) -> sqlx::Result<bool> { |
|
| 372 | 372 | let r = sqlx::query( |
|
| 373 | - | "INSERT OR IGNORE INTO proposal_review_requests (proposal_id, account_id, requested_by, requested_ms) VALUES (?, ?, ?, ?)", |
|
| 373 | + | "INSERT OR IGNORE INTO proposal_review_requests (proposal_id, account_id, requested_by, requested_ms) VALUES ($1, $2, $3, $4)", |
|
| 374 | 374 | ) |
|
| 375 | 375 | .bind(proposal_id) |
|
| 376 | 376 | .bind(account_id) |
|
| … | 6 unchanged lines |
||
| 383 | 383 | ||
| 384 | 384 | /// Withdraw a request, or mark it answered; false when there was none. |
|
| 385 | 385 | pub async fn unrequest_review(pool: &Pool, proposal_id: i64, account_id: i64) -> sqlx::Result<bool> { |
|
| 386 | - | let r = sqlx::query("DELETE FROM proposal_review_requests WHERE proposal_id = ? AND account_id = ?") |
|
| 386 | + | let r = sqlx::query("DELETE FROM proposal_review_requests WHERE proposal_id = $1 AND account_id = $2") |
|
| 387 | 387 | .bind(proposal_id) |
|
| 388 | 388 | .bind(account_id) |
|
| 389 | 389 | .execute(pool) |
|
| … | 6 unchanged lines |
||
| 396 | 396 | pub async fn review_requests(pool: &Pool, proposal_id: i64) -> sqlx::Result<Vec<String>> { |
|
| 397 | 397 | sqlx::query_scalar( |
|
| 398 | 398 | "SELECT a.username FROM proposal_review_requests r JOIN accounts a ON a.id = r.account_id \ |
|
| 399 | - | WHERE r.proposal_id = ? ORDER BY r.requested_ms, a.username", |
|
| 399 | + | WHERE r.proposal_id = $1 ORDER BY r.requested_ms, a.username", |
|
| 400 | 400 | ) |
|
| 401 | 401 | .bind(proposal_id) |
|
| 402 | 402 | .fetch_all(pool) |
|
| … | 2 unchanged lines |
modified crates/hub/src/db/releases.rs +16 −12
| … | 28 unchanged lines |
||
| 29 | 29 | } |
|
| 30 | 30 | ||
| 31 | 31 | pub async fn list(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<Release>> { |
|
| 32 | - | sqlx::query_as("SELECT * FROM releases WHERE repo_id = ? ORDER BY created_ms DESC, id DESC").bind(repo_id).fetch_all(pool).await |
|
| 32 | + | sqlx::query_as("SELECT * FROM releases WHERE repo_id = $1 ORDER BY created_ms DESC, id DESC").bind(repo_id).fetch_all(pool).await |
|
| 33 | 33 | } |
|
| 34 | 34 | ||
| 35 | 35 | pub async fn get(pool: &Pool, repo_id: i64, tag: &str) -> sqlx::Result<Option<Release>> { |
|
| 36 | - | sqlx::query_as("SELECT * FROM releases WHERE repo_id = ? AND tag = ?").bind(repo_id).bind(tag).fetch_optional(pool).await |
|
| 36 | + | sqlx::query_as("SELECT * FROM releases WHERE repo_id = $1 AND tag = $2").bind(repo_id).bind(tag).fetch_optional(pool).await |
|
| 37 | 37 | } |
|
| 38 | 38 | ||
| 39 | 39 | /// The newest release that is not a pre-release: the "latest" marker. |
|
| 40 | 40 | pub async fn latest(pool: &Pool, repo_id: i64) -> sqlx::Result<Option<Release>> { |
|
| 41 | - | sqlx::query_as("SELECT * FROM releases WHERE repo_id = ? AND prerelease = 0 ORDER BY created_ms DESC, id DESC LIMIT 1") |
|
| 41 | + | sqlx::query_as("SELECT * FROM releases WHERE repo_id = $1 AND prerelease = 0 ORDER BY created_ms DESC, id DESC LIMIT 1") |
|
| 42 | 42 | .bind(repo_id) |
|
| 43 | 43 | .fetch_optional(pool) |
|
| 44 | 44 | .await |
|
| … | 8 unchanged lines |
||
| 53 | 53 | prerelease: bool, |
|
| 54 | 54 | author: Option<(i64, &str)>, |
|
| 55 | 55 | ) -> sqlx::Result<Release> { |
|
| 56 | - | sqlx::query_as("INSERT INTO releases (repo_id, tag, title, notes, prerelease, author_id, author, created_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *") |
|
| 56 | + | sqlx::query_as("INSERT INTO releases (repo_id, tag, title, notes, prerelease, author_id, author, created_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *") |
|
| 57 | 57 | .bind(repo_id) |
|
| 58 | 58 | .bind(tag) |
|
| 59 | 59 | .bind(title) |
|
| … | 7 unchanged lines |
||
| 67 | 67 | } |
|
| 68 | 68 | ||
| 69 | 69 | pub async fn edit(pool: &Pool, id: i64, title: &str, notes: &str, prerelease: bool) -> sqlx::Result<()> { |
|
| 70 | - | sqlx::query("UPDATE releases SET title = ?, notes = ?, prerelease = ? WHERE id = ?") |
|
| 70 | + | sqlx::query("UPDATE releases SET title = $1, notes = $2, prerelease = $3 WHERE id = $4") |
|
| 71 | 71 | .bind(title) |
|
| 72 | 72 | .bind(notes) |
|
| 73 | 73 | .bind(prerelease) |
|
| … | 4 unchanged lines |
||
| 78 | 78 | } |
|
| 79 | 79 | ||
| 80 | 80 | pub async fn delete(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 81 | - | sqlx::query("DELETE FROM releases WHERE id = ?").bind(id).execute(pool).await?; |
|
| 81 | + | sqlx::query("DELETE FROM releases WHERE id = $1").bind(id).execute(pool).await?; |
|
| 82 | 82 | Ok(()) |
|
| 83 | 83 | } |
|
| 84 | 84 | ||
| 85 | 85 | pub async fn assets(pool: &Pool, release_id: i64) -> sqlx::Result<Vec<Asset>> { |
|
| 86 | - | sqlx::query_as("SELECT * FROM release_assets WHERE release_id = ? ORDER BY name").bind(release_id).fetch_all(pool).await |
|
| 86 | + | sqlx::query_as("SELECT * FROM release_assets WHERE release_id = $1 ORDER BY name").bind(release_id).fetch_all(pool).await |
|
| 87 | 87 | } |
|
| 88 | 88 | ||
| 89 | 89 | pub async fn asset(pool: &Pool, release_id: i64, name: &str) -> sqlx::Result<Option<Asset>> { |
|
| 90 | - | sqlx::query_as("SELECT * FROM release_assets WHERE release_id = ? AND name = ?").bind(release_id).bind(name).fetch_optional(pool).await |
|
| 90 | + | sqlx::query_as("SELECT * FROM release_assets WHERE release_id = $1 AND name = $2") |
|
| 91 | + | .bind(release_id) |
|
| 92 | + | .bind(name) |
|
| 93 | + | .fetch_optional(pool) |
|
| 94 | + | .await |
|
| 91 | 95 | } |
|
| 92 | 96 | ||
| 93 | 97 | pub async fn add_asset(pool: &Pool, release_id: i64, name: &str, size: i64, hash: &str, content_type: &str) -> sqlx::Result<Asset> { |
|
| 94 | 98 | sqlx::query_as( |
|
| 95 | - | "INSERT INTO release_assets (release_id, name, size, hash, content_type, created_ms) VALUES (?, ?, ?, ?, ?, ?) \ |
|
| 99 | + | "INSERT INTO release_assets (release_id, name, size, hash, content_type, created_ms) VALUES ($1, $2, $3, $4, $5, $6) \ |
|
| 96 | 100 | ON CONFLICT (release_id, name) DO UPDATE SET size = excluded.size, hash = excluded.hash, content_type = excluded.content_type, created_ms = excluded.created_ms RETURNING *", |
|
| 97 | 101 | ) |
|
| 98 | 102 | .bind(release_id) |
|
| … | 7 unchanged lines |
||
| 106 | 110 | } |
|
| 107 | 111 | ||
| 108 | 112 | pub async fn delete_asset(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 109 | - | sqlx::query("DELETE FROM release_assets WHERE id = ?").bind(id).execute(pool).await?; |
|
| 113 | + | sqlx::query("DELETE FROM release_assets WHERE id = $1").bind(id).execute(pool).await?; |
|
| 110 | 114 | Ok(()) |
|
| 111 | 115 | } |
|
| 112 | 116 | ||
| 113 | 117 | pub async fn count_download(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 114 | - | sqlx::query("UPDATE release_assets SET downloads = downloads + 1 WHERE id = ?").bind(id).execute(pool).await?; |
|
| 118 | + | sqlx::query("UPDATE release_assets SET downloads = downloads + 1 WHERE id = $1").bind(id).execute(pool).await?; |
|
| 115 | 119 | Ok(()) |
|
| 116 | 120 | } |
|
| 117 | 121 | ||
| 118 | 122 | /// Whether any asset of any release still names this content. |
|
| 119 | 123 | pub async fn hash_in_use(pool: &Pool, hash: &str) -> sqlx::Result<bool> { |
|
| 120 | - | let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM release_assets WHERE hash = ?").bind(hash).fetch_one(pool).await?; |
|
| 124 | + | let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM release_assets WHERE hash = $1").bind(hash).fetch_one(pool).await?; |
|
| 121 | 125 | Ok(n > 0) |
|
| 122 | 126 | } |
modified crates/hub/src/db/repos.rs +29 −29
| … | 95 unchanged lines |
||
| 96 | 96 | } |
|
| 97 | 97 | ||
| 98 | 98 | pub async fn get_by_id(pool: &Pool, id: i64) -> sqlx::Result<Option<RepoRow>> { |
|
| 99 | - | sqlx::query_as("SELECT * FROM repositories WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 99 | + | sqlx::query_as("SELECT * FROM repositories WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 100 | 100 | } |
|
| 101 | 101 | ||
| 102 | 102 | pub async fn list_by_owner(pool: &Pool, owner: &str) -> sqlx::Result<Vec<RepoRow>> { |
|
| 103 | - | sqlx::query_as("SELECT * FROM repositories WHERE owner = ? ORDER BY updated_ms DESC, name").bind(owner).fetch_all(pool).await |
|
| 103 | + | sqlx::query_as("SELECT * FROM repositories WHERE owner = $1 ORDER BY updated_ms DESC, name").bind(owner).fetch_all(pool).await |
|
| 104 | 104 | } |
|
| 105 | 105 | ||
| 106 | 106 | /// One repository, with its open issue and proposal counts for the tabs. |
|
| … | 2 unchanged lines |
||
| 109 | 109 | "SELECT r.*, \ |
|
| 110 | 110 | (SELECT COUNT(*) FROM issues i WHERE i.repo_id = r.id AND i.state = 'open') AS open_issues, \ |
|
| 111 | 111 | (SELECT COUNT(*) FROM proposals p WHERE p.repo_id = r.id AND p.state IN ('open', 'draft')) AS open_proposals \ |
|
| 112 | - | FROM repositories r WHERE r.owner = ? AND r.name = ?", |
|
| 112 | + | FROM repositories r WHERE r.owner = $1 AND r.name = $2", |
|
| 113 | 113 | ) |
|
| 114 | 114 | .bind(owner) |
|
| 115 | 115 | .bind(name) |
|
| … | 8 unchanged lines |
||
| 124 | 124 | let now = now_ms(); |
|
| 125 | 125 | sqlx::query_as( |
|
| 126 | 126 | "INSERT INTO repositories (owner, name, description, visibility, created_ms, updated_ms, required_approvals) \ |
|
| 127 | - | VALUES (?, ?, ?, ?, ?, ?, (SELECT COUNT(*) >= 2 FROM org_members m JOIN organisations o ON o.id = m.org_id WHERE o.name = ?)) RETURNING *", |
|
| 127 | + | VALUES ($1, $2, $3, $4, $5, $6, (SELECT COUNT(*) >= 2 FROM org_members m JOIN organisations o ON o.id = m.org_id WHERE o.name = $7)) RETURNING *", |
|
| 128 | 128 | ) |
|
| 129 | 129 | .bind(owner) |
|
| 130 | 130 | .bind(name) |
|
| … | 8 unchanged lines |
||
| 139 | 139 | ||
| 140 | 140 | /// The repository an old name now points at, after a transfer. |
|
| 141 | 141 | pub async fn moved_to(pool: &Pool, owner: &str, name: &str) -> sqlx::Result<Option<RepoRow>> { |
|
| 142 | - | let id: Option<i64> = sqlx::query_scalar("SELECT repo_id FROM repo_redirects WHERE old_owner = ? AND old_name = ?") |
|
| 142 | + | let id: Option<i64> = sqlx::query_scalar("SELECT repo_id FROM repo_redirects WHERE old_owner = $1 AND old_name = $2") |
|
| 143 | 143 | .bind(owner) |
|
| 144 | 144 | .bind(name) |
|
| 145 | 145 | .fetch_optional(pool) |
|
| … | 8 unchanged lines |
||
| 154 | 154 | /// redirect from the old name. A redirect that pointed at the new name |
|
| 155 | 155 | /// goes, so names never loop. |
|
| 156 | 156 | pub async fn transfer(pool: &Pool, id: i64, old_owner: &str, name: &str, new_owner: &str) -> sqlx::Result<()> { |
|
| 157 | - | sqlx::query("UPDATE repositories SET owner = ?, updated_ms = ? WHERE id = ?") |
|
| 157 | + | sqlx::query("UPDATE repositories SET owner = $1, updated_ms = $2 WHERE id = $3") |
|
| 158 | 158 | .bind(new_owner) |
|
| 159 | 159 | .bind(now_ms()) |
|
| 160 | 160 | .bind(id) |
|
| 161 | 161 | .execute(pool) |
|
| 162 | 162 | .await?; |
|
| 163 | - | sqlx::query("DELETE FROM repo_redirects WHERE old_owner = ? AND old_name = ?").bind(new_owner).bind(name).execute(pool).await?; |
|
| 164 | - | sqlx::query("INSERT OR REPLACE INTO repo_redirects (old_owner, old_name, repo_id, moved_ms) VALUES (?, ?, ?, ?)") |
|
| 163 | + | sqlx::query("DELETE FROM repo_redirects WHERE old_owner = $1 AND old_name = $2").bind(new_owner).bind(name).execute(pool).await?; |
|
| 164 | + | sqlx::query("INSERT OR REPLACE INTO repo_redirects (old_owner, old_name, repo_id, moved_ms) VALUES ($1, $2, $3, $4)") |
|
| 165 | 165 | .bind(old_owner) |
|
| 166 | 166 | .bind(name) |
|
| 167 | 167 | .bind(id) |
|
| … | 5 unchanged lines |
||
| 173 | 173 | ||
| 174 | 174 | /// Archive (read-only) or bring back a repository. |
|
| 175 | 175 | pub async fn set_archived(pool: &Pool, id: i64, archived: bool, reason: &str) -> sqlx::Result<()> { |
|
| 176 | - | sqlx::query("UPDATE repositories SET archived = ?, archived_reason = ?, updated_ms = ? WHERE id = ?") |
|
| 176 | + | sqlx::query("UPDATE repositories SET archived = $1, archived_reason = $2, updated_ms = $3 WHERE id = $4") |
|
| 177 | 177 | .bind(archived) |
|
| 178 | 178 | .bind(if archived { reason } else { "" }) |
|
| 179 | 179 | .bind(now_ms()) |
|
| … | 4 unchanged lines |
||
| 184 | 184 | } |
|
| 185 | 185 | ||
| 186 | 186 | pub async fn delete(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 187 | - | sqlx::query("DELETE FROM repositories WHERE id = ?").bind(id).execute(pool).await?; |
|
| 187 | + | sqlx::query("DELETE FROM repositories WHERE id = $1").bind(id).execute(pool).await?; |
|
| 188 | 188 | Ok(()) |
|
| 189 | 189 | } |
|
| 190 | 190 | ||
| 191 | 191 | pub async fn set_description(pool: &Pool, id: i64, description: &str) -> sqlx::Result<()> { |
|
| 192 | - | sqlx::query("UPDATE repositories SET description = ? WHERE id = ?").bind(description).bind(id).execute(pool).await?; |
|
| 192 | + | sqlx::query("UPDATE repositories SET description = $1 WHERE id = $2").bind(description).bind(id).execute(pool).await?; |
|
| 193 | 193 | Ok(()) |
|
| 194 | 194 | } |
|
| 195 | 195 | ||
| 196 | 196 | pub async fn set_default_bookmark(pool: &Pool, id: i64, bookmark: &str) -> sqlx::Result<()> { |
|
| 197 | - | sqlx::query("UPDATE repositories SET default_bookmark = ? WHERE id = ?").bind(bookmark).bind(id).execute(pool).await?; |
|
| 197 | + | sqlx::query("UPDATE repositories SET default_bookmark = $1 WHERE id = $2").bind(bookmark).bind(id).execute(pool).await?; |
|
| 198 | 198 | Ok(()) |
|
| 199 | 199 | } |
|
| 200 | 200 | ||
| 201 | 201 | pub async fn set_visibility(pool: &Pool, id: i64, visibility: Visibility) -> sqlx::Result<()> { |
|
| 202 | - | sqlx::query("UPDATE repositories SET visibility = ? WHERE id = ?").bind(visibility.as_str()).bind(id).execute(pool).await?; |
|
| 202 | + | sqlx::query("UPDATE repositories SET visibility = $1 WHERE id = $2").bind(visibility.as_str()).bind(id).execute(pool).await?; |
|
| 203 | 203 | Ok(()) |
|
| 204 | 204 | } |
|
| 205 | 205 | ||
| … | 7 unchanged lines |
||
| 213 | 213 | let now = now_ms(); |
|
| 214 | 214 | let mut tx = super::begin_write(pool).await?; |
|
| 215 | 215 | for r in results { |
|
| 216 | - | sqlx::query("INSERT INTO pushes (repo_id, at_ms, reference, ok, reason, old_state, new_state, heads, changes, account_id, pusher, via) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") |
|
| 216 | + | sqlx::query("INSERT INTO pushes (repo_id, at_ms, reference, ok, reason, old_state, new_state, heads, changes, account_id, pusher, via) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)") |
|
| 217 | 217 | .bind(id) |
|
| 218 | 218 | .bind(now) |
|
| 219 | 219 | .bind(&r.reference) |
|
| … | 10 unchanged lines |
||
| 230 | 230 | .await?; |
|
| 231 | 231 | } |
|
| 232 | 232 | if results.iter().any(|r| r.ok) { |
|
| 233 | - | sqlx::query("UPDATE repositories SET updated_ms = ? WHERE id = ?").bind(now).bind(id).execute(&mut *tx).await?; |
|
| 233 | + | sqlx::query("UPDATE repositories SET updated_ms = $1 WHERE id = $2").bind(now).bind(id).execute(&mut *tx).await?; |
|
| 234 | 234 | } |
|
| 235 | 235 | tx.commit().await |
|
| 236 | 236 | } |
|
| 237 | 237 | ||
| 238 | 238 | pub async fn pushes(pool: &Pool, id: i64, limit: i64) -> sqlx::Result<Vec<PushRow>> { |
|
| 239 | - | sqlx::query_as("SELECT id, at_ms, reference, ok, reason, old_state, new_state, heads, changes, pusher FROM pushes WHERE repo_id = ? ORDER BY at_ms DESC, id DESC LIMIT ?") |
|
| 239 | + | sqlx::query_as("SELECT id, at_ms, reference, ok, reason, old_state, new_state, heads, changes, pusher FROM pushes WHERE repo_id = $1 ORDER BY at_ms DESC, id DESC LIMIT $2") |
|
| 240 | 240 | .bind(id) |
|
| 241 | 241 | .bind(limit) |
|
| 242 | 242 | .fetch_all(pool) |
|
| … | 20 unchanged lines |
||
| 263 | 263 | } |
|
| 264 | 264 | ||
| 265 | 265 | pub async fn set_topics(pool: &Pool, id: i64, topics: &str) -> sqlx::Result<()> { |
|
| 266 | - | sqlx::query("UPDATE repositories SET topics = ? WHERE id = ?").bind(topics).bind(id).execute(pool).await?; |
|
| 266 | + | sqlx::query("UPDATE repositories SET topics = $1 WHERE id = $2").bind(topics).bind(id).execute(pool).await?; |
|
| 267 | 267 | Ok(()) |
|
| 268 | 268 | } |
|
| 269 | 269 | ||
| 270 | 270 | /// Star or unstar; returns whether it is now starred. |
|
| 271 | 271 | pub async fn toggle_star(pool: &Pool, account_id: i64, repo_id: i64) -> sqlx::Result<bool> { |
|
| 272 | - | let gone = sqlx::query("DELETE FROM stars WHERE account_id = ? AND repo_id = ?").bind(account_id).bind(repo_id).execute(pool).await?; |
|
| 272 | + | let gone = sqlx::query("DELETE FROM stars WHERE account_id = $1 AND repo_id = $2").bind(account_id).bind(repo_id).execute(pool).await?; |
|
| 273 | 273 | if gone.rows_affected() > 0 { |
|
| 274 | 274 | return Ok(false); |
|
| 275 | 275 | } |
|
| 276 | - | sqlx::query("INSERT INTO stars (account_id, repo_id, at_ms) VALUES (?, ?, ?)") |
|
| 276 | + | sqlx::query("INSERT INTO stars (account_id, repo_id, at_ms) VALUES ($1, $2, $3)") |
|
| 277 | 277 | .bind(account_id) |
|
| 278 | 278 | .bind(repo_id) |
|
| 279 | 279 | .bind(now_ms()) |
|
| … | 3 unchanged lines |
||
| 283 | 283 | } |
|
| 284 | 284 | ||
| 285 | 285 | pub async fn starred(pool: &Pool, account_id: i64, repo_id: i64) -> sqlx::Result<bool> { |
|
| 286 | - | let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM stars WHERE account_id = ? AND repo_id = ?") |
|
| 286 | + | let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM stars WHERE account_id = $1 AND repo_id = $2") |
|
| 287 | 287 | .bind(account_id) |
|
| 288 | 288 | .bind(repo_id) |
|
| 289 | 289 | .fetch_one(pool) |
|
| … | 3 unchanged lines |
||
| 293 | 293 | ||
| 294 | 294 | /// Stars and watchers of one repository. |
|
| 295 | 295 | pub async fn counts(pool: &Pool, repo_id: i64) -> sqlx::Result<(i64, i64)> { |
|
| 296 | - | let stars: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM stars WHERE repo_id = ?").bind(repo_id).fetch_one(pool).await?; |
|
| 296 | + | let stars: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM stars WHERE repo_id = $1").bind(repo_id).fetch_one(pool).await?; |
|
| 297 | 297 | let watchers = super::notify::watchers(pool, repo_id).await?.len() as i64; |
|
| 298 | 298 | Ok((stars, watchers)) |
|
| 299 | 299 | } |
|
| … | 6 unchanged lines |
||
| 306 | 306 | ||
| 307 | 307 | /// Record that `id` is a fork of `from`. |
|
| 308 | 308 | pub async fn set_forked_from(pool: &Pool, id: i64, from: i64) -> sqlx::Result<()> { |
|
| 309 | - | sqlx::query("UPDATE repositories SET forked_from = ? WHERE id = ?").bind(from).bind(id).execute(pool).await?; |
|
| 309 | + | sqlx::query("UPDATE repositories SET forked_from = $1 WHERE id = $2").bind(from).bind(id).execute(pool).await?; |
|
| 310 | 310 | Ok(()) |
|
| 311 | 311 | } |
|
| 312 | 312 | ||
| 313 | 313 | /// The forks of `id`, newest activity first. |
|
| 314 | 314 | pub async fn forks_of(pool: &Pool, id: i64) -> sqlx::Result<Vec<RepoRow>> { |
|
| 315 | - | sqlx::query_as("SELECT * FROM repositories WHERE forked_from = ? ORDER BY updated_ms DESC").bind(id).fetch_all(pool).await |
|
| 315 | + | sqlx::query_as("SELECT * FROM repositories WHERE forked_from = $1 ORDER BY updated_ms DESC").bind(id).fetch_all(pool).await |
|
| 316 | 316 | } |
|
| 317 | 317 | ||
| 318 | 318 | /// `on`, `off`, or `` (follow hub.toml's `[flow] repos`). |
|
| 319 | 319 | pub async fn set_flow(pool: &Pool, id: i64, setting: &str) -> sqlx::Result<()> { |
|
| 320 | - | sqlx::query("UPDATE repositories SET flow = ? WHERE id = ?").bind(setting).bind(id).execute(pool).await?; |
|
| 320 | + | sqlx::query("UPDATE repositories SET flow = $1 WHERE id = $2").bind(setting).bind(id).execute(pool).await?; |
|
| 321 | 321 | Ok(()) |
|
| 322 | 322 | } |
|
| 323 | 323 | ||
| 324 | 324 | pub async fn set_backup(pool: &Pool, id: i64, setting: &str) -> sqlx::Result<()> { |
|
| 325 | - | sqlx::query("UPDATE repositories SET backup = ? WHERE id = ?").bind(setting).bind(id).execute(pool).await?; |
|
| 325 | + | sqlx::query("UPDATE repositories SET backup = $1 WHERE id = $2").bind(setting).bind(id).execute(pool).await?; |
|
| 326 | 326 | Ok(()) |
|
| 327 | 327 | } |
|
| 328 | 328 | ||
| 329 | 329 | pub async fn set_diffusion_land(pool: &Pool, id: i64, setting: &str) -> sqlx::Result<()> { |
|
| 330 | - | sqlx::query("UPDATE repositories SET diffusion_land = ? WHERE id = ?").bind(setting).bind(id).execute(pool).await?; |
|
| 330 | + | sqlx::query("UPDATE repositories SET diffusion_land = $1 WHERE id = $2").bind(setting).bind(id).execute(pool).await?; |
|
| 331 | 331 | Ok(()) |
|
| 332 | 332 | } |
|
| 333 | 333 | ||
| 334 | 334 | pub async fn set_diffusion(pool: &Pool, id: i64, setting: &str) -> sqlx::Result<()> { |
|
| 335 | - | sqlx::query("UPDATE repositories SET diffusion = ? WHERE id = ?").bind(setting).bind(id).execute(pool).await?; |
|
| 335 | + | sqlx::query("UPDATE repositories SET diffusion = $1 WHERE id = $2").bind(setting).bind(id).execute(pool).await?; |
|
| 336 | 336 | Ok(()) |
|
| 337 | 337 | } |
|
| 338 | 338 | ||
| 339 | 339 | pub async fn set_git_mirror(pool: &Pool, id: i64, on: bool) -> sqlx::Result<()> { |
|
| 340 | - | sqlx::query("UPDATE repositories SET git_mirror = ? WHERE id = ?").bind(on).bind(id).execute(pool).await?; |
|
| 340 | + | sqlx::query("UPDATE repositories SET git_mirror = $1 WHERE id = $2").bind(on).bind(id).execute(pool).await?; |
|
| 341 | 341 | Ok(()) |
|
| 342 | 342 | } |
|
| 343 | 343 | ||
| 344 | 344 | pub async fn set_git_mirror_state(pool: &Pool, id: i64, state: &str) -> sqlx::Result<()> { |
|
| 345 | - | sqlx::query("UPDATE repositories SET git_mirror_state = ? WHERE id = ?").bind(state).bind(id).execute(pool).await?; |
|
| 345 | + | sqlx::query("UPDATE repositories SET git_mirror_state = $1 WHERE id = $2").bind(state).bind(id).execute(pool).await?; |
|
| 346 | 346 | Ok(()) |
|
| 347 | 347 | } |
|
| 348 | 348 | ||
| … | 14 unchanged lines |
modified crates/hub/src/db/runners.rs +26 −26
| … | 107 unchanged lines |
||
| 108 | 108 | } |
|
| 109 | 109 | ||
| 110 | 110 | pub async fn by_token(pool: &Pool, token_hash: &str) -> sqlx::Result<Option<Runner>> { |
|
| 111 | - | sqlx::query_as("SELECT * FROM flow_runners WHERE token_hash = ?").bind(token_hash).fetch_optional(pool).await |
|
| 111 | + | sqlx::query_as("SELECT * FROM flow_runners WHERE token_hash = $1").bind(token_hash).fetch_optional(pool).await |
|
| 112 | 112 | } |
|
| 113 | 113 | ||
| 114 | 114 | pub async fn get(pool: &Pool, id: i64) -> sqlx::Result<Option<Runner>> { |
|
| 115 | - | sqlx::query_as("SELECT * FROM flow_runners WHERE id = ?").bind(id).fetch_optional(pool).await |
|
| 115 | + | sqlx::query_as("SELECT * FROM flow_runners WHERE id = $1").bind(id).fetch_optional(pool).await |
|
| 116 | 116 | } |
|
| 117 | 117 | ||
| 118 | 118 | pub async fn list(pool: &Pool) -> sqlx::Result<Vec<Runner>> { |
|
| … | 2 unchanged lines |
||
| 121 | 121 | ||
| 122 | 122 | /// The row of a runner configured with a hub.toml token, made on first sight. |
|
| 123 | 123 | pub async fn configured(pool: &Pool, token_hash: &str, name: &str) -> sqlx::Result<Runner> { |
|
| 124 | - | sqlx::query("INSERT OR IGNORE INTO flow_runners (name, token_hash, source, created_ms) VALUES (?, ?, 'config', ?)") |
|
| 124 | + | sqlx::query("INSERT OR IGNORE INTO flow_runners (name, token_hash, source, created_ms) VALUES ($1, $2, 'config', $3)") |
|
| 125 | 125 | .bind(name) |
|
| 126 | 126 | .bind(token_hash) |
|
| 127 | 127 | .bind(now_ms()) |
|
| … | 6 unchanged lines |
||
| 134 | 134 | /// configured runner takes the name it claims with. |
|
| 135 | 135 | /// The administrator's settings for a runner (#98). |
|
| 136 | 136 | pub async fn set_limits(pool: &Pool, id: i64, admin_labels: &str, timeout_minutes: Option<i64>) -> sqlx::Result<()> { |
|
| 137 | - | sqlx::query("UPDATE flow_runners SET admin_labels = ?, timeout_minutes = ? WHERE id = ?") |
|
| 137 | + | sqlx::query("UPDATE flow_runners SET admin_labels = $1, timeout_minutes = $2 WHERE id = $3") |
|
| 138 | 138 | .bind(admin_labels) |
|
| 139 | 139 | .bind(timeout_minutes) |
|
| 140 | 140 | .bind(id) |
|
| … | 7 unchanged lines |
||
| 148 | 148 | match info { |
|
| 149 | 149 | Some(i) => { |
|
| 150 | 150 | sqlx::query( |
|
| 151 | - | "UPDATE flow_runners SET arch = ?, os = ?, version = ?, labels = ?, max_jobs = ?, memory_mib = ?, cpus = ?, last_seen_ms = ? WHERE id = ?", |
|
| 151 | + | "UPDATE flow_runners SET arch = $1, os = $2, version = $3, labels = $4, max_jobs = $5, memory_mib = $6, cpus = $7, last_seen_ms = $8 WHERE id = $9", |
|
| 152 | 152 | ) |
|
| 153 | 153 | .bind(&i.arch) |
|
| 154 | 154 | .bind(&i.os) |
|
| … | 8 unchanged lines |
||
| 163 | 163 | .await?; |
|
| 164 | 164 | } |
|
| 165 | 165 | None => { |
|
| 166 | - | sqlx::query("UPDATE flow_runners SET last_seen_ms = ? WHERE id = ?").bind(now).bind(id).execute(pool).await?; |
|
| 166 | + | sqlx::query("UPDATE flow_runners SET last_seen_ms = $1 WHERE id = $2").bind(now).bind(id).execute(pool).await?; |
|
| 167 | 167 | } |
|
| 168 | 168 | } |
|
| 169 | 169 | if let Some(n) = name { |
|
| 170 | - | sqlx::query("UPDATE flow_runners SET name = ? WHERE id = ? AND source = 'config'").bind(n).bind(id).execute(pool).await?; |
|
| 170 | + | sqlx::query("UPDATE flow_runners SET name = $1 WHERE id = $2 AND source = 'config'").bind(n).bind(id).execute(pool).await?; |
|
| 171 | 171 | } |
|
| 172 | 172 | Ok(()) |
|
| 173 | 173 | } |
|
| 174 | 174 | ||
| 175 | 175 | /// Keep what a runner measured about itself at a claim (oxygenflow#40). |
|
| 176 | 176 | pub async fn set_facts(pool: &Pool, id: i64, f: &flow_core::api::Facts) -> sqlx::Result<()> { |
|
| 177 | - | sqlx::query("UPDATE flow_runners SET free_disk_mib = ?, load_x100 = ?, speed = ?, speed_class = ?, kernel = ? WHERE id = ?") |
|
| 177 | + | sqlx::query("UPDATE flow_runners SET free_disk_mib = $1, load_x100 = $2, speed = $3, speed_class = $4, kernel = $5 WHERE id = $6") |
|
| 178 | 178 | .bind(f.free_disk_mib.map(|v| v.min(i64::MAX as u64) as i64)) |
|
| 179 | 179 | .bind(f.load_x100.map(i64::from)) |
|
| 180 | 180 | .bind(f.speed.map(i64::from)) |
|
| … | 35 unchanged lines |
||
| 216 | 216 | profile: &str, |
|
| 217 | 217 | ) -> sqlx::Result<()> { |
|
| 218 | 218 | let images = images.map(|i| serde_json::to_string(i).unwrap_or_default()).unwrap_or_default(); |
|
| 219 | - | sqlx::query("UPDATE flow_runners SET tiers = ?, images = ?, fetches_images = ?, profile = ? WHERE id = ?") |
|
| 219 | + | sqlx::query("UPDATE flow_runners SET tiers = $1, images = $2, fetches_images = $3, profile = $4 WHERE id = $5") |
|
| 220 | 220 | .bind(tiers.join(",")) |
|
| 221 | 221 | .bind(images) |
|
| 222 | 222 | .bind(fetches_images) |
|
| … | 20 unchanged lines |
||
| 243 | 243 | } |
|
| 244 | 244 | ||
| 245 | 245 | pub async fn set_state(pool: &Pool, id: i64, state: &str) -> sqlx::Result<()> { |
|
| 246 | - | sqlx::query("UPDATE flow_runners SET state = ?, state_ms = ? WHERE id = ?") |
|
| 246 | + | sqlx::query("UPDATE flow_runners SET state = $1, state_ms = $2 WHERE id = $3") |
|
| 247 | 247 | .bind(state) |
|
| 248 | 248 | .bind(super::now_ms()) |
|
| 249 | 249 | .bind(id) |
|
| … | 10 unchanged lines |
||
| 260 | 260 | } |
|
| 261 | 261 | let last: Vec<(String, String)> = sqlx::query_as( |
|
| 262 | 262 | "SELECT j.status, j.error FROM flow_jobs j JOIN flow_runners r ON r.id = j.runner_id |
|
| 263 | - | WHERE j.runner_id = ? AND j.finished_ms IS NOT NULL AND j.finished_ms >= r.state_ms |
|
| 264 | - | ORDER BY j.finished_ms DESC, j.id DESC LIMIT ?", |
|
| 263 | + | WHERE j.runner_id = $1 AND j.finished_ms IS NOT NULL AND j.finished_ms >= r.state_ms |
|
| 264 | + | ORDER BY j.finished_ms DESC, j.id DESC LIMIT $2", |
|
| 265 | 265 | ) |
|
| 266 | 266 | .bind(id) |
|
| 267 | 267 | .bind(n as i64) |
|
| … | 4 unchanged lines |
||
| 272 | 272 | ||
| 273 | 273 | /// Forget a runner: its credential stops working. Its jobs keep its name. |
|
| 274 | 274 | pub async fn remove(pool: &Pool, id: i64) -> sqlx::Result<()> { |
|
| 275 | - | sqlx::query("DELETE FROM flow_runners WHERE id = ?").bind(id).execute(pool).await?; |
|
| 275 | + | sqlx::query("DELETE FROM flow_runners WHERE id = $1").bind(id).execute(pool).await?; |
|
| 276 | 276 | Ok(()) |
|
| 277 | 277 | } |
|
| 278 | 278 | ||
| 279 | 279 | /// The key a runner signs receipts with: the one bound, or `key` (named |
|
| 280 | 280 | /// `name`) when none is yet. |
|
| 281 | 281 | pub async fn bind_key(pool: &Pool, id: i64, key: &str, name: &str) -> sqlx::Result<String> { |
|
| 282 | - | sqlx::query("UPDATE flow_runners SET key = ?, key_name = ? WHERE id = ? AND key = ''") |
|
| 282 | + | sqlx::query("UPDATE flow_runners SET key = $1, key_name = $2 WHERE id = $3 AND key = ''") |
|
| 283 | 283 | .bind(key) |
|
| 284 | 284 | .bind(name) |
|
| 285 | 285 | .bind(id) |
|
| 286 | 286 | .execute(pool) |
|
| 287 | 287 | .await?; |
|
| 288 | - | sqlx::query_scalar("SELECT key FROM flow_runners WHERE id = ?").bind(id).fetch_one(pool).await |
|
| 288 | + | sqlx::query_scalar("SELECT key FROM flow_runners WHERE id = $1").bind(id).fetch_one(pool).await |
|
| 289 | 289 | } |
|
| 290 | 290 | ||
| 291 | 291 | /// Keys that runners of this hub sign with. |
|
| … | 5 unchanged lines |
||
| 297 | 297 | pub async fn add_registration(pool: &Pool, token_hash: &str, by: &str, scope: &Scope) -> sqlx::Result<()> { |
|
| 298 | 298 | let now = now_ms(); |
|
| 299 | 299 | sqlx::query( |
|
| 300 | - | "INSERT INTO flow_runner_registrations (token_hash, created_by, created_ms, expires_ms, scope_owner, scope_repo_id) VALUES (?, ?, ?, ?, ?, ?)", |
|
| 300 | + | "INSERT INTO flow_runner_registrations (token_hash, created_by, created_ms, expires_ms, scope_owner, scope_repo_id) VALUES ($1, $2, $3, $4, $5, $6)", |
|
| 301 | 301 | ) |
|
| 302 | 302 | .bind(token_hash) |
|
| 303 | 303 | .bind(by) |
|
| … | 11 unchanged lines |
||
| 315 | 315 | pub async fn serving(pool: &Pool, owner: &str, repo_id: i64) -> sqlx::Result<Vec<Runner>> { |
|
| 316 | 316 | sqlx::query_as( |
|
| 317 | 317 | "SELECT * FROM flow_runners WHERE (scope_owner = '' AND scope_repo_id IS NULL) \ |
|
| 318 | - | OR (scope_owner = ? AND scope_repo_id IS NULL) OR scope_repo_id = ? ORDER BY name", |
|
| 318 | + | OR (scope_owner = $1 AND scope_repo_id IS NULL) OR scope_repo_id = $2 ORDER BY name", |
|
| 319 | 319 | ) |
|
| 320 | 320 | .bind(owner) |
|
| 321 | 321 | .bind(repo_id) |
|
| … | 4 unchanged lines |
||
| 326 | 326 | /// The runners that serve an owner: the site's, and those scoped to the |
|
| 327 | 327 | /// owner or one of its repositories. |
|
| 328 | 328 | pub async fn serving_owner(pool: &Pool, owner: &str) -> sqlx::Result<Vec<Runner>> { |
|
| 329 | - | sqlx::query_as("SELECT * FROM flow_runners WHERE (scope_owner = '' AND scope_repo_id IS NULL) OR scope_owner = ? ORDER BY name") |
|
| 329 | + | sqlx::query_as("SELECT * FROM flow_runners WHERE (scope_owner = '' AND scope_repo_id IS NULL) OR scope_owner = $1 ORDER BY name") |
|
| 330 | 330 | .bind(owner) |
|
| 331 | 331 | .fetch_all(pool) |
|
| 332 | 332 | .await |
|
| … | 1 unchanged lines |
||
| 334 | 334 | ||
| 335 | 335 | /// Change the repositories a runner serves. |
|
| 336 | 336 | pub async fn set_scope(pool: &Pool, id: i64, scope: &Scope) -> sqlx::Result<()> { |
|
| 337 | - | sqlx::query("UPDATE flow_runners SET scope_owner = ?, scope_repo_id = ? WHERE id = ?") |
|
| 337 | + | sqlx::query("UPDATE flow_runners SET scope_owner = $1, scope_repo_id = $2 WHERE id = $3") |
|
| 338 | 338 | .bind(&scope.owner) |
|
| 339 | 339 | .bind(scope.repo_id) |
|
| 340 | 340 | .bind(id) |
|
| … | 4 unchanged lines |
||
| 345 | 345 | ||
| 346 | 346 | /// Registration tokens not used yet and not expired: (created by, created, expires). |
|
| 347 | 347 | pub async fn pending_registrations(pool: &Pool) -> sqlx::Result<Vec<(String, i64, i64)>> { |
|
| 348 | - | sqlx::query_as("SELECT created_by, created_ms, expires_ms FROM flow_runner_registrations WHERE used_ms IS NULL AND expires_ms > ? ORDER BY created_ms DESC") |
|
| 348 | + | sqlx::query_as("SELECT created_by, created_ms, expires_ms FROM flow_runner_registrations WHERE used_ms IS NULL AND expires_ms > $1 ORDER BY created_ms DESC") |
|
| 349 | 349 | .bind(now_ms()) |
|
| 350 | 350 | .fetch_all(pool) |
|
| 351 | 351 | .await |
|
| … | 14 unchanged lines |
||
| 366 | 366 | let mut tx = super::begin_write(pool).await?; |
|
| 367 | 367 | let now = now_ms(); |
|
| 368 | 368 | let found: Option<(String, String, Option<i64>)> = sqlx::query_as( |
|
| 369 | - | "SELECT created_by, scope_owner, scope_repo_id FROM flow_runner_registrations WHERE token_hash = ? AND used_ms IS NULL AND expires_ms > ?", |
|
| 369 | + | "SELECT created_by, scope_owner, scope_repo_id FROM flow_runner_registrations WHERE token_hash = $1 AND used_ms IS NULL AND expires_ms > $2", |
|
| 370 | 370 | ) |
|
| 371 | 371 | .bind(registration) |
|
| 372 | 372 | .bind(now) |
|
| … | 1 unchanged lines |
||
| 374 | 374 | .await?; |
|
| 375 | 375 | let Some((who, scope_owner, scope_repo_id)) = found else { return Ok(None) }; |
|
| 376 | 376 | let id: i64 = sqlx::query_scalar( |
|
| 377 | - | "INSERT INTO flow_runners (name, token_hash, source, key, key_name, created_ms, created_by, scope_owner, scope_repo_id) VALUES (?, ?, 'registered', ?, ?, ?, ?, ?, ?) RETURNING id", |
|
| 377 | + | "INSERT INTO flow_runners (name, token_hash, source, key, key_name, created_ms, created_by, scope_owner, scope_repo_id) VALUES ($1, $2, 'registered', $3, $4, $5, $6, $7, $8) RETURNING id", |
|
| 378 | 378 | ) |
|
| 379 | 379 | .bind(name) |
|
| 380 | 380 | .bind(token_hash) |
|
| … | 5 unchanged lines |
||
| 386 | 386 | .bind(scope_repo_id) |
|
| 387 | 387 | .fetch_one(&mut *tx) |
|
| 388 | 388 | .await?; |
|
| 389 | - | sqlx::query("UPDATE flow_runner_registrations SET used_ms = ?, runner_id = ? WHERE token_hash = ?") |
|
| 389 | + | sqlx::query("UPDATE flow_runner_registrations SET used_ms = $1, runner_id = $2 WHERE token_hash = $3") |
|
| 390 | 390 | .bind(now) |
|
| 391 | 391 | .bind(id) |
|
| 392 | 392 | .bind(registration) |
|
| … | 22 unchanged lines |
||
| 415 | 415 | "SELECT p.owner AS owner, p.name AS repo, r.number AS run_number, j.name AS job, j.status AS status, j.started_ms AS started_ms, \ |
|
| 416 | 416 | j.finished_ms AS finished_ms, j.receipt_attest AS receipt_attest \ |
|
| 417 | 417 | FROM flow_jobs j JOIN flow_runs r ON r.id = j.run_id JOIN repositories p ON p.id = r.repo_id \ |
|
| 418 | - | WHERE j.runner_id = ? AND (? IS NULL OR j.started_ms < ?) ORDER BY j.started_ms DESC LIMIT ?", |
|
| 418 | + | WHERE j.runner_id = $1 AND ($2 IS NULL OR j.started_ms < $3) ORDER BY j.started_ms DESC LIMIT $4", |
|
| 419 | 419 | ) |
|
| 420 | 420 | .bind(id) |
|
| 421 | 421 | .bind(before) |
|
| … | 7 unchanged lines |
||
| 429 | 429 | pub async fn totals(pool: &Pool, id: i64) -> sqlx::Result<(i64, i64, i64)> { |
|
| 430 | 430 | sqlx::query_as( |
|
| 431 | 431 | "SELECT COUNT(*), COALESCE(SUM(status = 'passed'), 0), COALESCE(SUM(finished_ms - started_ms), 0) \ |
|
| 432 | - | FROM flow_jobs WHERE runner_id = ? AND finished_ms IS NOT NULL", |
|
| 432 | + | FROM flow_jobs WHERE runner_id = $1 AND finished_ms IS NOT NULL", |
|
| 433 | 433 | ) |
|
| 434 | 434 | .bind(id) |
|
| 435 | 435 | .fetch_one(pool) |
|
| … | 8 unchanged lines |
||
| 444 | 444 | } |
|
| 445 | 445 | ||
| 446 | 446 | pub async fn set_job_runner(pool: &Pool, job_id: i64, runner_id: i64) -> sqlx::Result<()> { |
|
| 447 | - | sqlx::query("UPDATE flow_jobs SET runner_id = ? WHERE id = ?").bind(runner_id).bind(job_id).execute(pool).await?; |
|
| 447 | + | sqlx::query("UPDATE flow_jobs SET runner_id = $1 WHERE id = $2").bind(runner_id).bind(job_id).execute(pool).await?; |
|
| 448 | 448 | Ok(()) |
|
| 449 | 449 | } |
modified crates/hub/src/db/sessions.rs +9 −9
| … | 31 unchanged lines |
||
| 32 | 32 | ||
| 33 | 33 | pub async fn create(pool: &Pool, account_id: i64, hash: &str, salt: &str) -> sqlx::Result<i64> { |
|
| 34 | 34 | let now = now_ms(); |
|
| 35 | - | let r = sqlx::query("INSERT INTO sessions (account_id, hash, salt, created_ms, seen_ms) VALUES (?, ?, ?, ?, ?)") |
|
| 35 | + | let r = sqlx::query("INSERT INTO sessions (account_id, hash, salt, created_ms, seen_ms) VALUES ($1, $2, $3, $4, $5)") |
|
| 36 | 36 | .bind(account_id) |
|
| 37 | 37 | .bind(hash) |
|
| 38 | 38 | .bind(salt) |
|
| … | 5 unchanged lines |
||
| 44 | 44 | } |
|
| 45 | 45 | ||
| 46 | 46 | pub async fn by_hash(pool: &Pool, hash: &str) -> sqlx::Result<Option<Session>> { |
|
| 47 | - | sqlx::query_as(&format!("SELECT {COLUMNS} FROM sessions WHERE hash = ?")).bind(hash).fetch_optional(pool).await |
|
| 47 | + | sqlx::query_as(&format!("SELECT {COLUMNS} FROM sessions WHERE hash = $1")).bind(hash).fetch_optional(pool).await |
|
| 48 | 48 | } |
|
| 49 | 49 | ||
| 50 | 50 | /// Note a use: the time (at most once a minute) and, the first time, the |
|
| 51 | 51 | /// address and browser it came from. |
|
| 52 | 52 | pub async fn touch(pool: &Pool, s: &Session, address: &str, agent: &str, now: i64) -> sqlx::Result<()> { |
|
| 53 | 53 | if s.agent.is_empty() && s.address.is_empty() && (!agent.is_empty() || !address.is_empty()) { |
|
| 54 | - | sqlx::query("UPDATE sessions SET seen_ms = ?, address = ?, agent = ? WHERE id = ?") |
|
| 54 | + | sqlx::query("UPDATE sessions SET seen_ms = $1, address = $2, agent = $3 WHERE id = $4") |
|
| 55 | 55 | .bind(now) |
|
| 56 | 56 | .bind(address) |
|
| 57 | 57 | .bind(agent) |
|
| … | 1 unchanged lines |
||
| 59 | 59 | .execute(pool) |
|
| 60 | 60 | .await?; |
|
| 61 | 61 | } else if now - s.seen_ms >= TOUCH_EVERY_MS { |
|
| 62 | - | sqlx::query("UPDATE sessions SET seen_ms = ? WHERE id = ?").bind(now).bind(s.id).execute(pool).await?; |
|
| 62 | + | sqlx::query("UPDATE sessions SET seen_ms = $1 WHERE id = $2").bind(now).bind(s.id).execute(pool).await?; |
|
| 63 | 63 | } |
|
| 64 | 64 | Ok(()) |
|
| 65 | 65 | } |
|
| … | 1 unchanged lines |
||
| 67 | 67 | /// An account's live sessions, most recently used first. |
|
| 68 | 68 | pub async fn of_account(pool: &Pool, account_id: i64, salt: &str) -> sqlx::Result<Vec<Session>> { |
|
| 69 | 69 | let now = now_ms(); |
|
| 70 | - | let all: Vec<Session> = sqlx::query_as(&format!("SELECT {COLUMNS} FROM sessions WHERE account_id = ? ORDER BY seen_ms DESC")) |
|
| 70 | + | let all: Vec<Session> = sqlx::query_as(&format!("SELECT {COLUMNS} FROM sessions WHERE account_id = $1 ORDER BY seen_ms DESC")) |
|
| 71 | 71 | .bind(account_id) |
|
| 72 | 72 | .fetch_all(pool) |
|
| 73 | 73 | .await?; |
|
| … | 2 unchanged lines |
||
| 76 | 76 | ||
| 77 | 77 | /// End one session of an account's; whether there was one. |
|
| 78 | 78 | pub async fn end(pool: &Pool, account_id: i64, id: i64) -> sqlx::Result<bool> { |
|
| 79 | - | let r = sqlx::query("DELETE FROM sessions WHERE id = ? AND account_id = ?").bind(id).bind(account_id).execute(pool).await?; |
|
| 79 | + | let r = sqlx::query("DELETE FROM sessions WHERE id = $1 AND account_id = $2").bind(id).bind(account_id).execute(pool).await?; |
|
| 80 | 80 | Ok(r.rows_affected() > 0) |
|
| 81 | 81 | } |
|
| 82 | 82 | ||
| 83 | 83 | pub async fn end_hash(pool: &Pool, hash: &str) -> sqlx::Result<()> { |
|
| 84 | - | sqlx::query("DELETE FROM sessions WHERE hash = ?").bind(hash).execute(pool).await?; |
|
| 84 | + | sqlx::query("DELETE FROM sessions WHERE hash = $1").bind(hash).execute(pool).await?; |
|
| 85 | 85 | Ok(()) |
|
| 86 | 86 | } |
|
| 87 | 87 | ||
| 88 | 88 | /// Every session of an account's but `keep`. |
|
| 89 | 89 | pub async fn end_others(pool: &Pool, account_id: i64, keep: i64) -> sqlx::Result<()> { |
|
| 90 | - | sqlx::query("DELETE FROM sessions WHERE account_id = ? AND id != ?").bind(account_id).bind(keep).execute(pool).await?; |
|
| 90 | + | sqlx::query("DELETE FROM sessions WHERE account_id = $1 AND id != $2").bind(account_id).bind(keep).execute(pool).await?; |
|
| 91 | 91 | Ok(()) |
|
| 92 | 92 | } |
|
| 93 | 93 | ||
| … | 1 unchanged lines |
||
| 95 | 95 | pub async fn sweep(pool: &Pool) -> sqlx::Result<u64> { |
|
| 96 | 96 | let now = now_ms(); |
|
| 97 | 97 | let r = sqlx::query( |
|
| 98 | - | "DELETE FROM sessions WHERE seen_ms < ? OR created_ms < ? \ |
|
| 98 | + | "DELETE FROM sessions WHERE seen_ms < $1 OR created_ms < $2 \ |
|
| 99 | 99 | OR salt != (SELECT session_salt FROM accounts WHERE accounts.id = sessions.account_id)", |
|
| 100 | 100 | ) |
|
| 101 | 101 | .bind(now - IDLE_MS) |
|
| … | 23 unchanged lines |
modified crates/hub/src/diffusion.rs +32 −30
| … | 22 unchanged lines |
||
| 23 | 23 | pub async fn target(pool: &Pool, row: &RepoRow) -> String { |
|
| 24 | 24 | let since = db::now_ms() - LEARN_DAYS * 24 * 3600 * 1000; |
|
| 25 | 25 | let learned: Option<String> = sqlx::query_scalar( |
|
| 26 | - | "SELECT target FROM proposals WHERE repo_id = ? AND state = 'landed' AND landed_ms >= ? |
|
| 26 | + | "SELECT target FROM proposals WHERE repo_id = $1 AND state = 'landed' AND landed_ms >= $2 |
|
| 27 | 27 | GROUP BY target ORDER BY COUNT(*) DESC, MAX(landed_ms) DESC LIMIT 1", |
|
| 28 | 28 | ) |
|
| 29 | 29 | .bind(row.id) |
|
| … | 94 unchanged lines |
||
| 124 | 124 | return Err("begin: forced for a test".into()); |
|
| 125 | 125 | } |
|
| 126 | 126 | let mut tx = app.db.begin().await.map_err(|e| format!("begin: {e}"))?; |
|
| 127 | - | sqlx::query("DELETE FROM diffusion_pins WHERE consumer_id = ? AND bookmark = ?") |
|
| 127 | + | sqlx::query("DELETE FROM diffusion_pins WHERE consumer_id = $1 AND bookmark = $2") |
|
| 128 | 128 | .bind(row.id) |
|
| 129 | 129 | .bind(bookmark) |
|
| 130 | 130 | .execute(&mut *tx) |
|
| … | 2 unchanged lines |
||
| 133 | 133 | for ((path, id, pinned), upstream_row) in pins.iter().zip(&upstreams) { |
|
| 134 | 134 | let heads: Vec<String> = pinned.iter().map(|h| h.to_hex()).collect(); |
|
| 135 | 135 | sqlx::query( |
|
| 136 | - | "INSERT INTO diffusion_pins (consumer_id, bookmark, path, upstream, heads, updated_ms, upstream_repo_id) VALUES (?, ?, ?, ?, ?, ?, ?)", |
|
| 136 | + | "INSERT INTO diffusion_pins (consumer_id, bookmark, path, upstream, heads, updated_ms, upstream_repo_id) VALUES ($1, $2, $3, $4, $5, $6, $7)", |
|
| 137 | 137 | ) |
|
| 138 | 138 | .bind(row.id) |
|
| 139 | 139 | .bind(bookmark) |
|
| … | 6 unchanged lines |
||
| 146 | 146 | .await |
|
| 147 | 147 | .map_err(|e| format!("insert {path}: {e}"))?; |
|
| 148 | 148 | } |
|
| 149 | - | sqlx::query("INSERT OR REPLACE INTO diffusion_indexed (consumer_id, bookmark, heads, updated_ms) VALUES (?, ?, ?, ?)") |
|
| 149 | + | sqlx::query("INSERT OR REPLACE INTO diffusion_indexed (consumer_id, bookmark, heads, updated_ms) VALUES ($1, $2, $3, $4)") |
|
| 150 | 150 | .bind(row.id) |
|
| 151 | 151 | .bind(bookmark) |
|
| 152 | 152 | .bind(heads_key(heads)) |
|
| … | 94 unchanged lines |
||
| 247 | 247 | for bookmark in followed_bookmarks(&app.db, row).await { |
|
| 248 | 248 | let b = bookmark.clone(); |
|
| 249 | 249 | let Ok(heads) = app.repos.read(&name, move |r, _| heads_of(r, &b)).await else { continue }; |
|
| 250 | - | let indexed: Option<String> = sqlx::query_scalar("SELECT heads FROM diffusion_indexed WHERE consumer_id = ? AND bookmark = ?") |
|
| 251 | - | .bind(row.id) |
|
| 252 | - | .bind(&bookmark) |
|
| 253 | - | .fetch_optional(&app.db) |
|
| 254 | - | .await |
|
| 255 | - | .ok() |
|
| 256 | - | .flatten(); |
|
| 250 | + | let indexed: Option<String> = |
|
| 251 | + | sqlx::query_scalar("SELECT heads FROM diffusion_indexed WHERE consumer_id = $1 AND bookmark = $2") |
|
| 252 | + | .bind(row.id) |
|
| 253 | + | .bind(&bookmark) |
|
| 254 | + | .fetch_optional(&app.db) |
|
| 255 | + | .await |
|
| 256 | + | .ok() |
|
| 257 | + | .flatten(); |
|
| 257 | 258 | if indexed.as_deref() != Some(heads_key(&heads).as_str()) { |
|
| 258 | 259 | tracing::info!("diffusion: {}/{} {bookmark}: the index was not at the bookmark; re-indexing", row.owner, row.name); |
|
| 259 | 260 | record(app, row, &bookmark, &heads).await; |
|
| … | 213 unchanged lines |
||
| 473 | 474 | let Some(name) = RepoName::new(&row.owner, &row.name) else { return Ok(Vec::new()) }; |
|
| 474 | 475 | let id = app.repos.read(&name, |r, _| Ok(r.id()?)).await?; |
|
| 475 | 476 | let found: Vec<(i64, String, String, String)> = sqlx::query_as( |
|
| 476 | - | "SELECT consumer_id, bookmark, path, heads FROM diffusion_pins WHERE upstream = ? ORDER BY consumer_id, bookmark, path", |
|
| 477 | + | "SELECT consumer_id, bookmark, path, heads FROM diffusion_pins WHERE upstream = $1 ORDER BY consumer_id, bookmark, path", |
|
| 477 | 478 | ) |
|
| 478 | 479 | .bind(hex::encode(id)) |
|
| 479 | 480 | .fetch_all(&app.db) |
|
| … | 23 unchanged lines |
||
| 503 | 504 | let bump = mine.iter().find(|p| p.state == "open" || p.state == "draft").map(|p| p.number); |
|
| 504 | 505 | let landed = mine.iter().filter(|p| p.state == "landed").max_by_key(|p| p.landed_ms).map(|p| (p.number, p.landed_ms)); |
|
| 505 | 506 | let run: Option<(i64, String)> = |
|
| 506 | - | sqlx::query_as("SELECT number, status FROM flow_runs WHERE repo_id = ? AND bookmark = ? ORDER BY id DESC LIMIT 1") |
|
| 507 | + | sqlx::query_as("SELECT number, status FROM flow_runs WHERE repo_id = $1 AND bookmark = $2 ORDER BY id DESC LIMIT 1") |
|
| 507 | 508 | .bind(c.id) |
|
| 508 | 509 | .bind(&bump_bm) |
|
| 509 | 510 | .fetch_optional(&app.db) |
|
| … | 33 unchanged lines |
||
| 543 | 544 | }) |
|
| 544 | 545 | .await?; |
|
| 545 | 546 | let up_run: Option<String> = |
|
| 546 | - | sqlx::query_scalar("SELECT status FROM flow_runs WHERE repo_id = ? AND bookmark = ? ORDER BY id DESC LIMIT 1") |
|
| 547 | + | sqlx::query_scalar("SELECT status FROM flow_runs WHERE repo_id = $1 AND bookmark = $2 ORDER BY id DESC LIMIT 1") |
|
| 547 | 548 | .bind(row.id) |
|
| 548 | 549 | .bind(&row.default_bookmark) |
|
| 549 | 550 | .fetch_optional(&app.db) |
|
| … | 15 unchanged lines |
||
| 565 | 566 | /// this hub it pins, with its status there. |
|
| 566 | 567 | pub async fn follows(app: &Shared, consumer: &RepoRow, account: Option<&Account>) -> Vec<(RepoRow, Consumer)> { |
|
| 567 | 568 | let ups: Vec<i64> = |
|
| 568 | - | sqlx::query_scalar("SELECT DISTINCT upstream_repo_id FROM diffusion_pins WHERE consumer_id = ? AND upstream_repo_id IS NOT NULL") |
|
| 569 | + | sqlx::query_scalar("SELECT DISTINCT upstream_repo_id FROM diffusion_pins WHERE consumer_id = $1 AND upstream_repo_id IS NOT NULL") |
|
| 569 | 570 | .bind(consumer.id) |
|
| 570 | 571 | .fetch_all(&app.db) |
|
| 571 | 572 | .await |
|
| … | 471 unchanged lines |
||
| 1043 | 1044 | async fn propagate_to(app: &Shared, upstream: &RepoRow, heads: Vec<ChangeHash>, green: bool, only: Option<i64>) { |
|
| 1044 | 1045 | let Some(uname) = RepoName::new(&upstream.owner, &upstream.name) else { return }; |
|
| 1045 | 1046 | let Ok(id) = app.repos.read(&uname, |r, _| Ok(r.id()?)).await else { return }; |
|
| 1046 | - | let rows: Vec<(i64, String, String)> = sqlx::query_as("SELECT consumer_id, bookmark, path FROM diffusion_pins WHERE upstream = ?") |
|
| 1047 | + | let rows: Vec<(i64, String, String)> = sqlx::query_as("SELECT consumer_id, bookmark, path FROM diffusion_pins WHERE upstream = $1") |
|
| 1047 | 1048 | .bind(hex::encode(id)) |
|
| 1048 | 1049 | .fetch_all(&app.db) |
|
| 1049 | 1050 | .await |
|
| … | 101 unchanged lines |
||
| 1151 | 1152 | return Err(Held::Manual); |
|
| 1152 | 1153 | } |
|
| 1153 | 1154 | let upstream_id: Option<i64> = sqlx::query_scalar( |
|
| 1154 | - | "SELECT upstream_repo_id FROM diffusion_pins WHERE consumer_id = ? AND path = ? AND upstream_repo_id IS NOT NULL LIMIT 1", |
|
| 1155 | + | "SELECT upstream_repo_id FROM diffusion_pins WHERE consumer_id = $1 AND path = $2 AND upstream_repo_id IS NOT NULL LIMIT 1", |
|
| 1155 | 1156 | ) |
|
| 1156 | 1157 | .bind(consumer.id) |
|
| 1157 | 1158 | .bind(path) |
|
| … | 40 unchanged lines |
||
| 1198 | 1199 | a == b |
|
| 1199 | 1200 | }; |
|
| 1200 | 1201 | let upstream_runs: Vec<String> = sqlx::query_scalar( |
|
| 1201 | - | "SELECT heads FROM flow_runs WHERE repo_id = ? AND bookmark = ? AND status = 'passed' ORDER BY id DESC LIMIT 100", |
|
| 1202 | + | "SELECT heads FROM flow_runs WHERE repo_id = $1 AND bookmark = $2 AND status = 'passed' ORDER BY id DESC LIMIT 100", |
|
| 1202 | 1203 | ) |
|
| 1203 | 1204 | .bind(upstream.id) |
|
| 1204 | 1205 | .bind(&upstream.default_bookmark) |
|
| … | 4 unchanged lines |
||
| 1209 | 1210 | return Err(Held::UpstreamUnchecked); |
|
| 1210 | 1211 | } |
|
| 1211 | 1212 | let run: Option<(String, String)> = |
|
| 1212 | - | sqlx::query_as("SELECT status, heads FROM flow_runs WHERE repo_id = ? AND bookmark = ? ORDER BY id DESC LIMIT 1") |
|
| 1213 | + | sqlx::query_as("SELECT status, heads FROM flow_runs WHERE repo_id = $1 AND bookmark = $2 ORDER BY id DESC LIMIT 1") |
|
| 1213 | 1214 | .bind(consumer.id) |
|
| 1214 | 1215 | .bind(&bm) |
|
| 1215 | 1216 | .fetch_optional(&app.db) |
|
| … | 39 unchanged lines |
||
| 1255 | 1256 | ||
| 1256 | 1257 | /// Try every open bump of `consumer` (its setting turned to `auto`). |
|
| 1257 | 1258 | pub async fn land_ready(app: &Shared, consumer: &RepoRow) { |
|
| 1258 | - | let paths: Vec<String> = sqlx::query_scalar("SELECT DISTINCT path FROM diffusion_pins WHERE consumer_id = ?") |
|
| 1259 | + | let paths: Vec<String> = sqlx::query_scalar("SELECT DISTINCT path FROM diffusion_pins WHERE consumer_id = $1") |
|
| 1259 | 1260 | .bind(consumer.id) |
|
| 1260 | 1261 | .fetch_all(&app.db) |
|
| 1261 | 1262 | .await |
|
| … | 10 unchanged lines |
||
| 1272 | 1273 | if !crate::flow::enabled(app, upstream) { |
|
| 1273 | 1274 | return true; |
|
| 1274 | 1275 | } |
|
| 1275 | - | let runs: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM flow_runs WHERE repo_id = ? AND bookmark = ?") |
|
| 1276 | + | let runs: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM flow_runs WHERE repo_id = $1 AND bookmark = $2") |
|
| 1276 | 1277 | .bind(upstream.id) |
|
| 1277 | 1278 | .bind(&upstream.default_bookmark) |
|
| 1278 | 1279 | .fetch_one(&app.db) |
|
| … | 15 unchanged lines |
||
| 1294 | 1295 | ||
| 1295 | 1296 | /// The newest state of `upstream`'s default bookmark whose run passed. |
|
| 1296 | 1297 | async fn last_green(app: &Shared, upstream: &RepoRow) -> Option<Vec<ChangeHash>> { |
|
| 1297 | - | let heads: String = |
|
| 1298 | - | sqlx::query_scalar("SELECT heads FROM flow_runs WHERE repo_id = ? AND bookmark = ? AND status = 'passed' ORDER BY id DESC LIMIT 1") |
|
| 1299 | - | .bind(upstream.id) |
|
| 1300 | - | .bind(&upstream.default_bookmark) |
|
| 1301 | - | .fetch_optional(&app.db) |
|
| 1302 | - | .await |
|
| 1303 | - | .ok() |
|
| 1304 | - | .flatten()?; |
|
| 1298 | + | let heads: String = sqlx::query_scalar( |
|
| 1299 | + | "SELECT heads FROM flow_runs WHERE repo_id = $1 AND bookmark = $2 AND status = 'passed' ORDER BY id DESC LIMIT 1", |
|
| 1300 | + | ) |
|
| 1301 | + | .bind(upstream.id) |
|
| 1302 | + | .bind(&upstream.default_bookmark) |
|
| 1303 | + | .fetch_optional(&app.db) |
|
| 1304 | + | .await |
|
| 1305 | + | .ok() |
|
| 1306 | + | .flatten()?; |
|
| 1305 | 1307 | let h: Vec<ChangeHash> = heads.split('+').filter_map(ChangeHash::from_hex).collect(); |
|
| 1306 | 1308 | (!h.is_empty()).then_some(h) |
|
| 1307 | 1309 | } |
|
| … | 2 unchanged lines |
||
| 1310 | 1312 | /// it turns Diffusion on, it is bumped to each upstream's newest state |
|
| 1311 | 1313 | /// that passed (or simply its newest, when it follows every move). |
|
| 1312 | 1314 | pub async fn catch_up(app: &Shared, consumer: &RepoRow) { |
|
| 1313 | - | let upstreams: Vec<String> = sqlx::query_scalar("SELECT DISTINCT upstream FROM diffusion_pins WHERE consumer_id = ?") |
|
| 1315 | + | let upstreams: Vec<String> = sqlx::query_scalar("SELECT DISTINCT upstream FROM diffusion_pins WHERE consumer_id = $1") |
|
| 1314 | 1316 | .bind(consumer.id) |
|
| 1315 | 1317 | .fetch_all(&app.db) |
|
| 1316 | 1318 | .await |
|
| … | 217 unchanged lines |
modified crates/hub/src/flow.rs +4 −4
| … | 1452 unchanged lines |
||
| 1453 | 1453 | // pin), and no other (#150). |
|
| 1454 | 1454 | let diffused = run.actor == crate::diffusion::ACCOUNT |
|
| 1455 | 1455 | && run.bookmark.starts_with("diffusion/") |
|
| 1456 | - | && sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM diffusion_pins WHERE consumer_id = ? AND upstream_repo_id = ?") |
|
| 1456 | + | && sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM diffusion_pins WHERE consumer_id = $1 AND upstream_repo_id = $2") |
|
| 1457 | 1457 | .bind(run.repo_id) |
|
| 1458 | 1458 | .bind(other.id) |
|
| 1459 | 1459 | .fetch_one(&app.db) |
|
| … | 523 unchanged lines |
||
| 1983 | 1983 | // A pass after a failure on the same bookmark: fixed (#183). |
|
| 1984 | 1984 | if done.status == "passed" { |
|
| 1985 | 1985 | let before: Option<String> = sqlx::query_scalar( |
|
| 1986 | - | "SELECT status FROM flow_runs WHERE repo_id = ? AND bookmark = ? AND id < ? AND status IN ('passed', 'failed') |
|
| 1986 | + | "SELECT status FROM flow_runs WHERE repo_id = $1 AND bookmark = $2 AND id < $3 AND status IN ('passed', 'failed') |
|
| 1987 | 1987 | ORDER BY id DESC LIMIT 1", |
|
| 1988 | 1988 | ) |
|
| 1989 | 1989 | .bind(done.repo_id) |
|
| … | 3 unchanged lines |
||
| 1993 | 1993 | .await?; |
|
| 1994 | 1994 | // Or this very run failed and a job of it was run again. |
|
| 1995 | 1995 | let url = format!("/{}/{}/flow/{}", row.owner, row.name, done.number); |
|
| 1996 | - | let failed_here: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM notifications WHERE kind = 'flow.failed' AND url = ?") |
|
| 1996 | + | let failed_here: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM notifications WHERE kind = 'flow.failed' AND url = $1") |
|
| 1997 | 1997 | .bind(&url) |
|
| 1998 | 1998 | .fetch_one(&app.db) |
|
| 1999 | 1999 | .await?; |
|
| … | 31 unchanged lines |
||
| 2031 | 2031 | } |
|
| 2032 | 2032 | ||
| 2033 | 2033 | async fn runner_of(app: &Shared, job_id: i64) -> Option<i64> { |
|
| 2034 | - | sqlx::query_scalar("SELECT runner_id FROM flow_jobs WHERE id = ?").bind(job_id).fetch_optional(&app.db).await.ok().flatten().flatten() |
|
| 2034 | + | sqlx::query_scalar("SELECT runner_id FROM flow_jobs WHERE id = $1").bind(job_id).fetch_optional(&app.db).await.ok().flatten().flatten() |
|
| 2035 | 2035 | } |
|
| 2036 | 2036 | ||
| 2037 | 2037 | /// A failed run (oxygenflow#30): its pusher and the repository's watchers |
|
| … | 451 unchanged lines |
modified crates/hub/src/images.rs +3 −3
| … | 165 unchanged lines |
||
| 166 | 166 | /// Record an image this hub now keeps. |
|
| 167 | 167 | pub async fn record(pool: &Pool, m: &Manifest, uploader: &str) -> sqlx::Result<()> { |
|
| 168 | 168 | sqlx::query( |
|
| 169 | - | "INSERT INTO jet_images (hash, size, arch, chunks, uploader, created_ms) VALUES (?, ?, ?, ?, ?, ?) \ |
|
| 169 | + | "INSERT INTO jet_images (hash, size, arch, chunks, uploader, created_ms) VALUES ($1, $2, $3, $4, $5, $6) \ |
|
| 170 | 170 | ON CONFLICT (hash) DO UPDATE SET arch = excluded.arch WHERE excluded.arch != ''", |
|
| 171 | 171 | ) |
|
| 172 | 172 | .bind(&m.hash) |
|
| … | 29 unchanged lines |
||
| 202 | 202 | .ok() |
|
| 203 | 203 | .flatten(); |
|
| 204 | 204 | if let Some(a) = arch { |
|
| 205 | - | sqlx::query("UPDATE jet_images SET arch = ? WHERE hash = ?").bind(a).bind(&k.hash).execute(&app.db).await?; |
|
| 205 | + | sqlx::query("UPDATE jet_images SET arch = $1 WHERE hash = $2").bind(a).bind(&k.hash).execute(&app.db).await?; |
|
| 206 | 206 | crate::db::flow::record_image_archs(&app.db, &[(k.hash.clone(), a.to_string())].into_iter().collect()).await?; |
|
| 207 | 207 | read += 1; |
|
| 208 | 208 | } |
|
| … | 39 unchanged lines |
||
| 248 | 248 | "SELECT lower(json_extract(j.sandbox, '$.rootfs')) AS image, p.owner, p.name, COUNT(*) AS jobs, |
|
| 249 | 249 | MAX(COALESCE(j.started_ms, r.created_ms)) AS last_ms |
|
| 250 | 250 | FROM flow_jobs j JOIN flow_runs r ON r.id = j.run_id JOIN repositories p ON p.id = r.repo_id |
|
| 251 | - | WHERE r.created_ms >= ? AND COALESCE(json_extract(j.sandbox, '$.rootfs'), '') != '' |
|
| 251 | + | WHERE r.created_ms >= $1 AND COALESCE(json_extract(j.sandbox, '$.rootfs'), '') != '' |
|
| 252 | 252 | GROUP BY image, p.id ORDER BY last_ms DESC", |
|
| 253 | 253 | ) |
|
| 254 | 254 | .bind(since_ms) |
|
| … | 8 unchanged lines |
modified crates/hub/src/junit.rs +22 −20
| … | 275 unchanged lines |
||
| 276 | 276 | pub async fn keep(app: &Shared, o: &Origin<'_>, cases: &[Case]) -> sqlx::Result<i64> { |
|
| 277 | 277 | let count = |s: &str| cases.iter().filter(|c| c.status == s).count() as i64; |
|
| 278 | 278 | let mut tx = crate::db::begin_write(&app.db).await?; |
|
| 279 | - | sqlx::query("DELETE FROM test_reports WHERE job_id = ?").bind(o.job_id).execute(&mut *tx).await?; |
|
| 279 | + | sqlx::query("DELETE FROM test_reports WHERE job_id = $1").bind(o.job_id).execute(&mut *tx).await?; |
|
| 280 | 280 | let id: i64 = sqlx::query_scalar( |
|
| 281 | 281 | "INSERT INTO test_reports (repo_id, run_id, job_id, job, run_number, bookmark, state, blob, \ |
|
| 282 | 282 | tests, passed, failed, errors, skipped, time_ms, created_ms) \ |
|
| 283 | - | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id", |
|
| 283 | + | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id", |
|
| 284 | 284 | ) |
|
| 285 | 285 | .bind(o.repo_id) |
|
| 286 | 286 | .bind(o.run_id) |
|
| … | 15 unchanged lines |
||
| 302 | 302 | for (i, c) in cases.iter().enumerate() { |
|
| 303 | 303 | sqlx::query( |
|
| 304 | 304 | "INSERT INTO test_cases (report_id, idx, suite, classname, name, status, time_ms, message, output) \ |
|
| 305 | - | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", |
|
| 305 | + | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", |
|
| 306 | 306 | ) |
|
| 307 | 307 | .bind(id) |
|
| 308 | 308 | .bind(i as i64) |
|
| … | 14 unchanged lines |
||
| 323 | 323 | ||
| 324 | 324 | /// A run's reports, one per job that kept one. |
|
| 325 | 325 | pub async fn for_run(pool: &Pool, run_id: i64) -> sqlx::Result<Vec<Report>> { |
|
| 326 | - | sqlx::query_as("SELECT * FROM test_reports WHERE run_id = ? ORDER BY job").bind(run_id).fetch_all(pool).await |
|
| 326 | + | sqlx::query_as("SELECT * FROM test_reports WHERE run_id = $1 ORDER BY job").bind(run_id).fetch_all(pool).await |
|
| 327 | 327 | } |
|
| 328 | 328 | ||
| 329 | 329 | /// A report's cases in report order, all or only those with a status |
|
| … | 2 unchanged lines |
||
| 332 | 332 | let filter = if statuses.is_empty() { |
|
| 333 | 333 | String::new() |
|
| 334 | 334 | } else { |
|
| 335 | - | format!(" AND status IN ({})", statuses.iter().map(|_| "?").collect::<Vec<_>>().join(", ")) |
|
| 335 | + | // $1 is the report; the statuses follow, then the limit. |
|
| 336 | + | format!(" AND status IN ({})", (2..2 + statuses.len()).map(|i| format!("${i}")).collect::<Vec<_>>().join(", ")) |
|
| 336 | 337 | }; |
|
| 338 | + | let limit_at = 2 + statuses.len(); |
|
| 337 | 339 | let sql = format!( |
|
| 338 | - | "SELECT suite, classname, name, status, time_ms, message, output FROM test_cases WHERE report_id = ?{filter} ORDER BY idx LIMIT ?" |
|
| 340 | + | "SELECT suite, classname, name, status, time_ms, message, output FROM test_cases WHERE report_id = $1{filter} ORDER BY idx LIMIT ${limit_at}" |
|
| 339 | 341 | ); |
|
| 340 | 342 | let mut q = sqlx::query_as(&sql).bind(report_id); |
|
| 341 | 343 | for s in statuses { |
|
| … | 6 unchanged lines |
||
| 348 | 350 | pub async fn failures(pool: &Pool, report_id: i64, limit: i64) -> sqlx::Result<Vec<Case>> { |
|
| 349 | 351 | sqlx::query_as( |
|
| 350 | 352 | "SELECT suite, classname, name, status, time_ms, message, output FROM test_cases \ |
|
| 351 | - | WHERE report_id = ? AND status IN ('failed', 'error') ORDER BY idx LIMIT ?", |
|
| 353 | + | WHERE report_id = $1 AND status IN ('failed', 'error') ORDER BY idx LIMIT $2", |
|
| 352 | 354 | ) |
|
| 353 | 355 | .bind(report_id) |
|
| 354 | 356 | .bind(limit) |
|
| … | 27 unchanged lines |
||
| 382 | 384 | } |
|
| 383 | 385 | ||
| 384 | 386 | pub async fn report(pool: &Pool, repo_id: i64, id: i64) -> sqlx::Result<Option<Report>> { |
|
| 385 | - | sqlx::query_as("SELECT * FROM test_reports WHERE id = ? AND repo_id = ?").bind(id).bind(repo_id).fetch_optional(pool).await |
|
| 387 | + | sqlx::query_as("SELECT * FROM test_reports WHERE id = $1 AND repo_id = $2").bind(id).bind(repo_id).fetch_optional(pool).await |
|
| 386 | 388 | } |
|
| 387 | 389 | ||
| 388 | 390 | /// The newest reports on a bookmark, newest first. |
|
| 389 | 391 | pub async fn on_bookmark(pool: &Pool, repo_id: i64, bookmark: &str, limit: i64) -> sqlx::Result<Vec<Report>> { |
|
| 390 | - | sqlx::query_as("SELECT * FROM test_reports WHERE repo_id = ? AND bookmark = ? ORDER BY created_ms DESC, id DESC LIMIT ?") |
|
| 392 | + | sqlx::query_as("SELECT * FROM test_reports WHERE repo_id = $1 AND bookmark = $2 ORDER BY created_ms DESC, id DESC LIMIT $3") |
|
| 391 | 393 | .bind(repo_id) |
|
| 392 | 394 | .bind(bookmark) |
|
| 393 | 395 | .bind(limit) |
|
| … | 4 unchanged lines |
||
| 398 | 400 | /// Bookmarks with reports, the most recently reported first. |
|
| 399 | 401 | pub async fn bookmarks(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<String>> { |
|
| 400 | 402 | let rows: Vec<(String,)> = |
|
| 401 | - | sqlx::query_as("SELECT bookmark FROM test_reports WHERE repo_id = ? GROUP BY bookmark ORDER BY MAX(created_ms) DESC") |
|
| 403 | + | sqlx::query_as("SELECT bookmark FROM test_reports WHERE repo_id = $1 GROUP BY bookmark ORDER BY MAX(created_ms) DESC") |
|
| 402 | 404 | .bind(repo_id) |
|
| 403 | 405 | .fetch_all(pool) |
|
| 404 | 406 | .await?; |
|
| … | 1 unchanged lines |
||
| 406 | 408 | } |
|
| 407 | 409 | ||
| 408 | 410 | pub async fn first_ms(pool: &Pool, repo_id: i64) -> sqlx::Result<Option<i64>> { |
|
| 409 | - | sqlx::query_scalar("SELECT MIN(created_ms) FROM test_reports WHERE repo_id = ?").bind(repo_id).fetch_one(pool).await |
|
| 411 | + | sqlx::query_scalar("SELECT MIN(created_ms) FROM test_reports WHERE repo_id = $1").bind(repo_id).fetch_one(pool).await |
|
| 410 | 412 | } |
|
| 411 | 413 | ||
| 412 | 414 | /// A report's slowest cases. |
|
| 413 | 415 | pub async fn slowest(pool: &Pool, report_id: i64, limit: i64) -> sqlx::Result<Vec<Case>> { |
|
| 414 | 416 | sqlx::query_as( |
|
| 415 | 417 | "SELECT suite, classname, name, status, time_ms, message, '' AS output FROM test_cases \ |
|
| 416 | - | WHERE report_id = ? AND status != 'skipped' ORDER BY time_ms DESC, idx LIMIT ?", |
|
| 418 | + | WHERE report_id = $1 AND status != 'skipped' ORDER BY time_ms DESC, idx LIMIT $2", |
|
| 417 | 419 | ) |
|
| 418 | 420 | .bind(report_id) |
|
| 419 | 421 | .bind(limit) |
|
| … | 4 unchanged lines |
||
| 424 | 426 | /// Every case of a report as `(classname, name) -> status`. |
|
| 425 | 427 | pub async fn statuses(pool: &Pool, report_id: i64) -> sqlx::Result<std::collections::HashMap<(String, String), String>> { |
|
| 426 | 428 | let rows: Vec<(String, String, String)> = |
|
| 427 | - | sqlx::query_as("SELECT classname, name, status FROM test_cases WHERE report_id = ?").bind(report_id).fetch_all(pool).await?; |
|
| 429 | + | sqlx::query_as("SELECT classname, name, status FROM test_cases WHERE report_id = $1").bind(report_id).fetch_all(pool).await?; |
|
| 428 | 430 | Ok(rows.into_iter().map(|(c, n, s)| ((c, n), s)).collect()) |
|
| 429 | 431 | } |
|
| 430 | 432 | ||
| … | 13 unchanged lines |
||
| 444 | 446 | sqlx::query_as( |
|
| 445 | 447 | "SELECT r.id AS report_id, r.run_number, r.job, r.created_ms, c.status, c.time_ms, c.message \ |
|
| 446 | 448 | FROM test_cases c JOIN test_reports r ON r.id = c.report_id \ |
|
| 447 | - | WHERE r.repo_id = ? AND r.bookmark = ? AND c.classname = ? AND c.name = ? \ |
|
| 448 | - | ORDER BY r.created_ms DESC, r.id DESC LIMIT ?", |
|
| 449 | + | WHERE r.repo_id = $1 AND r.bookmark = $2 AND c.classname = $3 AND c.name = $4 \ |
|
| 450 | + | ORDER BY r.created_ms DESC, r.id DESC LIMIT $5", |
|
| 449 | 451 | ) |
|
| 450 | 452 | .bind(repo_id) |
|
| 451 | 453 | .bind(bookmark) |
|
| … | 8 unchanged lines |
||
| 460 | 462 | /// the default bookmark for a run on it, its latest for any other. |
|
| 461 | 463 | pub async fn baseline(pool: &Pool, r: &Report, default_bookmark: &str) -> sqlx::Result<Option<Report>> { |
|
| 462 | 464 | sqlx::query_as( |
|
| 463 | - | "SELECT * FROM test_reports WHERE repo_id = ? AND bookmark = ? AND job = ? AND id != ? \ |
|
| 464 | - | AND (created_ms < ? OR (created_ms = ? AND id < ?)) ORDER BY created_ms DESC, id DESC LIMIT 1", |
|
| 465 | + | "SELECT * FROM test_reports WHERE repo_id = $1 AND bookmark = $2 AND job = $3 AND id != $4 \ |
|
| 466 | + | AND (created_ms < $5 OR (created_ms = $6 AND id < $7)) ORDER BY created_ms DESC, id DESC LIMIT 1", |
|
| 465 | 467 | ) |
|
| 466 | 468 | .bind(r.repo_id) |
|
| 467 | 469 | .bind(default_bookmark) |
|
| … | 10 unchanged lines |
||
| 478 | 480 | pub async fn newly_failing(pool: &Pool, report: i64, base: i64) -> sqlx::Result<Vec<String>> { |
|
| 479 | 481 | let rows: Vec<(String, String)> = sqlx::query_as( |
|
| 480 | 482 | "SELECT c.classname, c.name FROM test_cases c JOIN test_cases b \ |
|
| 481 | - | ON b.report_id = ? AND b.classname = c.classname AND b.name = c.name AND b.status = 'passed' \ |
|
| 482 | - | WHERE c.report_id = ? AND c.status IN ('failed', 'error') ORDER BY c.idx", |
|
| 483 | + | ON b.report_id = $1 AND b.classname = c.classname AND b.name = c.name AND b.status = 'passed' \ |
|
| 484 | + | WHERE c.report_id = $2 AND c.status IN ('failed', 'error') ORDER BY c.idx", |
|
| 483 | 485 | ) |
|
| 484 | 486 | .bind(base) |
|
| 485 | 487 | .bind(report) |
|
| … | 31 unchanged lines |
||
| 517 | 519 | sqlx::query_as(&format!( |
|
| 518 | 520 | "SELECT classname, name, SUM(status IN ('failed', 'error')) AS failed, SUM(status = 'passed') AS passed \ |
|
| 519 | 521 | FROM test_cases WHERE report_id IN ({ids}) GROUP BY classname, name \ |
|
| 520 | - | HAVING failed > 0 AND passed > 0 ORDER BY failed DESC, classname, name LIMIT ?" |
|
| 522 | + | HAVING failed > 0 AND passed > 0 ORDER BY failed DESC, classname, name LIMIT $1" |
|
| 521 | 523 | )) |
|
| 522 | 524 | .bind(limit) |
|
| 523 | 525 | .fetch_all(pool) |
|
| … | 56 unchanged lines |
modified crates/hub/src/reports.rs +15 −15
| … | 252 unchanged lines |
||
| 253 | 253 | /// Index report `name` of a job, replacing any the job kept before. |
|
| 254 | 254 | pub async fn keep(app: &Shared, o: &Origin<'_>, name: &str, d: &Declaration, m: &Manifest, manifest: &str) -> sqlx::Result<i64> { |
|
| 255 | 255 | let mut tx = crate::db::begin_write(&app.db).await?; |
|
| 256 | - | sqlx::query("DELETE FROM reports WHERE job_id = ? AND name = ?").bind(o.job_id).bind(name).execute(&mut *tx).await?; |
|
| 256 | + | sqlx::query("DELETE FROM reports WHERE job_id = $1 AND name = $2").bind(o.job_id).bind(name).execute(&mut *tx).await?; |
|
| 257 | 257 | let id: i64 = sqlx::query_scalar( |
|
| 258 | 258 | "INSERT INTO reports (repo_id, name, kind, title, summary, context, run_id, job_id, run_number, job, bookmark, state, \ |
|
| 259 | - | manifest, files, bytes, created_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id", |
|
| 259 | + | manifest, files, bytes, created_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING id", |
|
| 260 | 260 | ) |
|
| 261 | 261 | .bind(o.repo_id) |
|
| 262 | 262 | .bind(name) |
|
| … | 15 unchanged lines |
||
| 278 | 278 | .await?; |
|
| 279 | 279 | for x in &d.metrics { |
|
| 280 | 280 | sqlx::query( |
|
| 281 | - | "INSERT INTO report_metrics (report_id, key, value, unit, better, fail_above, fail_below) VALUES (?, ?, ?, ?, ?, ?, ?)", |
|
| 281 | + | "INSERT INTO report_metrics (report_id, key, value, unit, better, fail_above, fail_below) VALUES ($1, $2, $3, $4, $5, $6, $7)", |
|
| 282 | 282 | ) |
|
| 283 | 283 | .bind(id) |
|
| 284 | 284 | .bind(&x.key) |
|
| … | 6 unchanged lines |
||
| 291 | 291 | .await?; |
|
| 292 | 292 | } |
|
| 293 | 293 | for f in &m.files { |
|
| 294 | - | sqlx::query("INSERT INTO report_files (report_id, path, blob, size) VALUES (?, ?, ?, ?)") |
|
| 294 | + | sqlx::query("INSERT INTO report_files (report_id, path, blob, size) VALUES ($1, $2, $3, $4)") |
|
| 295 | 295 | .bind(id) |
|
| 296 | 296 | .bind(&f.path) |
|
| 297 | 297 | .bind(&f.blob) |
|
| … | 70 unchanged lines |
||
| 368 | 368 | /// given (else on any), newest first. |
|
| 369 | 369 | pub async fn latest(pool: &Pool, repo_id: i64, bookmark: Option<&str>) -> sqlx::Result<Vec<Report>> { |
|
| 370 | 370 | sqlx::query_as( |
|
| 371 | - | "SELECT * FROM reports r WHERE repo_id = ? AND (? IS NULL OR bookmark = ?) \ |
|
| 372 | - | AND id = (SELECT id FROM reports s WHERE s.repo_id = r.repo_id AND s.name = r.name AND (? IS NULL OR s.bookmark = ?) \ |
|
| 371 | + | "SELECT * FROM reports r WHERE repo_id = $1 AND ($2 IS NULL OR bookmark = $3) \ |
|
| 372 | + | AND id = (SELECT id FROM reports s WHERE s.repo_id = r.repo_id AND s.name = r.name AND ($4 IS NULL OR s.bookmark = $5) \ |
|
| 373 | 373 | ORDER BY created_ms DESC, id DESC LIMIT 1) \ |
|
| 374 | 374 | ORDER BY created_ms DESC", |
|
| 375 | 375 | ) |
|
| … | 8 unchanged lines |
||
| 384 | 384 | ||
| 385 | 385 | /// Report `name`'s history on `bookmark`, newest first. |
|
| 386 | 386 | pub async fn history(pool: &Pool, repo_id: i64, name: &str, bookmark: &str, limit: i64) -> sqlx::Result<Vec<Report>> { |
|
| 387 | - | sqlx::query_as("SELECT * FROM reports WHERE repo_id = ? AND name = ? AND bookmark = ? ORDER BY created_ms DESC, id DESC LIMIT ?") |
|
| 387 | + | sqlx::query_as("SELECT * FROM reports WHERE repo_id = $1 AND name = $2 AND bookmark = $3 ORDER BY created_ms DESC, id DESC LIMIT $4") |
|
| 388 | 388 | .bind(repo_id) |
|
| 389 | 389 | .bind(name) |
|
| 390 | 390 | .bind(bookmark) |
|
| … | 4 unchanged lines |
||
| 395 | 395 | ||
| 396 | 396 | /// The bookmarks report `name` has runs on, most recent first. |
|
| 397 | 397 | pub async fn bookmarks_of(pool: &Pool, repo_id: i64, name: &str) -> sqlx::Result<Vec<String>> { |
|
| 398 | - | sqlx::query_scalar("SELECT bookmark FROM reports WHERE repo_id = ? AND name = ? GROUP BY bookmark ORDER BY MAX(created_ms) DESC") |
|
| 398 | + | sqlx::query_scalar("SELECT bookmark FROM reports WHERE repo_id = $1 AND name = $2 GROUP BY bookmark ORDER BY MAX(created_ms) DESC") |
|
| 399 | 399 | .bind(repo_id) |
|
| 400 | 400 | .bind(name) |
|
| 401 | 401 | .fetch_all(pool) |
|
| … | 2 unchanged lines |
||
| 404 | 404 | ||
| 405 | 405 | /// One report of a repository. |
|
| 406 | 406 | pub async fn get(pool: &Pool, repo_id: i64, id: i64) -> sqlx::Result<Option<Report>> { |
|
| 407 | - | sqlx::query_as("SELECT * FROM reports WHERE repo_id = ? AND id = ?").bind(repo_id).bind(id).fetch_optional(pool).await |
|
| 407 | + | sqlx::query_as("SELECT * FROM reports WHERE repo_id = $1 AND id = $2").bind(repo_id).bind(id).fetch_optional(pool).await |
|
| 408 | 408 | } |
|
| 409 | 409 | ||
| 410 | 410 | /// A file of report `id`: its blob and size. |
|
| 411 | 411 | pub async fn file(pool: &Pool, id: i64, path: &str) -> sqlx::Result<Option<(String, i64)>> { |
|
| 412 | - | sqlx::query_as("SELECT blob, size FROM report_files WHERE report_id = ? AND path = ?").bind(id).bind(path).fetch_optional(pool).await |
|
| 412 | + | sqlx::query_as("SELECT blob, size FROM report_files WHERE report_id = $1 AND path = $2").bind(id).bind(path).fetch_optional(pool).await |
|
| 413 | 413 | } |
|
| 414 | 414 | ||
| 415 | 415 | /// The capability a report's files are served under: they are read from a |
|
| … | 19 unchanged lines |
||
| 435 | 435 | "SELECT r.id FROM reports r JOIN repositories p ON p.id = r.repo_id \ |
|
| 436 | 436 | WHERE r.files_expired_ms IS NULL AND r.bookmark != p.default_bookmark \ |
|
| 437 | 437 | AND (SELECT COUNT(*) FROM reports s WHERE s.repo_id = r.repo_id AND s.name = r.name AND s.bookmark = r.bookmark \ |
|
| 438 | - | AND (s.created_ms > r.created_ms OR (s.created_ms = r.created_ms AND s.id > r.id))) >= ?", |
|
| 438 | + | AND (s.created_ms > r.created_ms OR (s.created_ms = r.created_ms AND s.id > r.id))) >= $1", |
|
| 439 | 439 | ) |
|
| 440 | 440 | .bind(KEEP_OTHER_RUNS) |
|
| 441 | 441 | .fetch_all(&mut *tx) |
|
| 442 | 442 | .await?; |
|
| 443 | 443 | let now = now_ms(); |
|
| 444 | 444 | for id in &ids { |
|
| 445 | - | sqlx::query("DELETE FROM report_files WHERE report_id = ?").bind(id).execute(&mut *tx).await?; |
|
| 446 | - | sqlx::query("UPDATE reports SET files_expired_ms = ? WHERE id = ?").bind(now).bind(id).execute(&mut *tx).await?; |
|
| 445 | + | sqlx::query("DELETE FROM report_files WHERE report_id = $1").bind(id).execute(&mut *tx).await?; |
|
| 446 | + | sqlx::query("UPDATE reports SET files_expired_ms = $1 WHERE id = $2").bind(now).bind(id).execute(&mut *tx).await?; |
|
| 447 | 447 | } |
|
| 448 | 448 | tx.commit().await?; |
|
| 449 | 449 | Ok(ids.len() as u64) |
|
| … | 1 unchanged lines |
||
| 451 | 451 | ||
| 452 | 452 | /// Report `id`'s metrics, by key. |
|
| 453 | 453 | pub async fn metrics(pool: &Pool, id: i64) -> sqlx::Result<Vec<Metric>> { |
|
| 454 | - | sqlx::query_as("SELECT key, value, unit, better, fail_above, fail_below FROM report_metrics WHERE report_id = ? ORDER BY key") |
|
| 454 | + | sqlx::query_as("SELECT key, value, unit, better, fail_above, fail_below FROM report_metrics WHERE report_id = $1 ORDER BY key") |
|
| 455 | 455 | .bind(id) |
|
| 456 | 456 | .fetch_all(pool) |
|
| 457 | 457 | .await |
|
| … | 2 unchanged lines |
||
| 460 | 460 | /// Every file blob a repository's kept reports name, for garbage collection |
|
| 461 | 461 | /// to keep too: the database is the only thing that names them. |
|
| 462 | 462 | pub async fn file_blobs(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<String>> { |
|
| 463 | - | sqlx::query_scalar("SELECT DISTINCT f.blob FROM report_files f JOIN reports r ON r.id = f.report_id WHERE r.repo_id = ?") |
|
| 463 | + | sqlx::query_scalar("SELECT DISTINCT f.blob FROM report_files f JOIN reports r ON r.id = f.report_id WHERE r.repo_id = $1") |
|
| 464 | 464 | .bind(repo_id) |
|
| 465 | 465 | .fetch_all(pool) |
|
| 466 | 466 | .await |
|
| … | 62 unchanged lines |
modified crates/hub/src/secrets.rs +4 −4
| … | 33 unchanged lines |
||
| 34 | 34 | } |
|
| 35 | 35 | ||
| 36 | 36 | pub async fn list(pool: &Pool, scope: Scope) -> sqlx::Result<Vec<Secret>> { |
|
| 37 | - | sqlx::query_as("SELECT * FROM flow_secrets WHERE scope = ? AND scope_id = ? ORDER BY name") |
|
| 37 | + | sqlx::query_as("SELECT * FROM flow_secrets WHERE scope = $1 AND scope_id = $2 ORDER BY name") |
|
| 38 | 38 | .bind(scope.kind()) |
|
| 39 | 39 | .bind(scope.id()) |
|
| 40 | 40 | .fetch_all(pool) |
|
| … | 3 unchanged lines |
||
| 44 | 44 | /// Set a secret at a level, new or changed; `sealed` is the value sealed. |
|
| 45 | 45 | pub async fn set(pool: &Pool, scope: Scope, name: &str, sealed: &[u8]) -> sqlx::Result<()> { |
|
| 46 | 46 | sqlx::query( |
|
| 47 | - | "INSERT INTO flow_secrets (scope, scope_id, name, value, updated_ms) VALUES (?, ?, ?, ?, ?) \ |
|
| 47 | + | "INSERT INTO flow_secrets (scope, scope_id, name, value, updated_ms) VALUES ($1, $2, $3, $4, $5) \ |
|
| 48 | 48 | ON CONFLICT (scope, scope_id, name) DO UPDATE SET value = excluded.value, updated_ms = excluded.updated_ms", |
|
| 49 | 49 | ) |
|
| 50 | 50 | .bind(scope.kind()) |
|
| … | 8 unchanged lines |
||
| 59 | 59 | ||
| 60 | 60 | /// Remove a secret from a level; false when it was not there. |
|
| 61 | 61 | pub async fn delete(pool: &Pool, scope: Scope, name: &str) -> sqlx::Result<bool> { |
|
| 62 | - | let r = sqlx::query("DELETE FROM flow_secrets WHERE scope = ? AND scope_id = ? AND name = ?") |
|
| 62 | + | let r = sqlx::query("DELETE FROM flow_secrets WHERE scope = $1 AND scope_id = $2 AND name = $3") |
|
| 63 | 63 | .bind(scope.kind()) |
|
| 64 | 64 | .bind(scope.id()) |
|
| 65 | 65 | .bind(name) |
|
| … | 4 unchanged lines |
||
| 70 | 70 | ||
| 71 | 71 | /// Forget a level's secrets, when the organisation or repository goes. |
|
| 72 | 72 | pub async fn delete_scope(pool: &Pool, scope: Scope) -> sqlx::Result<()> { |
|
| 73 | - | sqlx::query("DELETE FROM flow_secrets WHERE scope = ? AND scope_id = ?").bind(scope.kind()).bind(scope.id()).execute(pool).await?; |
|
| 73 | + | sqlx::query("DELETE FROM flow_secrets WHERE scope = $1 AND scope_id = $2").bind(scope.kind()).bind(scope.id()).execute(pool).await?; |
|
| 74 | 74 | Ok(()) |
|
| 75 | 75 | } |
|
| 76 | 76 | ||
| … | 87 unchanged lines |
modified crates/hub/src/signin.rs +4 −4
| … | 300 unchanged lines |
||
| 301 | 301 | } |
|
| 302 | 302 | ||
| 303 | 303 | pub async fn linked_account(pool: &Pool, provider: &str, subject: &str) -> sqlx::Result<Option<accounts::Account>> { |
|
| 304 | - | let id: Option<i64> = sqlx::query_scalar("SELECT account_id FROM account_logins WHERE provider = ? AND subject = ?") |
|
| 304 | + | let id: Option<i64> = sqlx::query_scalar("SELECT account_id FROM account_logins WHERE provider = $1 AND subject = $2") |
|
| 305 | 305 | .bind(provider) |
|
| 306 | 306 | .bind(subject) |
|
| 307 | 307 | .fetch_optional(pool) |
|
| … | 5 unchanged lines |
||
| 313 | 313 | } |
|
| 314 | 314 | ||
| 315 | 315 | pub async fn link(pool: &Pool, provider: &str, subject: &str, account_id: i64, login: &str) -> sqlx::Result<()> { |
|
| 316 | - | sqlx::query("INSERT OR REPLACE INTO account_logins (provider, subject, account_id, login, linked_ms) VALUES (?, ?, ?, ?, ?)") |
|
| 316 | + | sqlx::query("INSERT OR REPLACE INTO account_logins (provider, subject, account_id, login, linked_ms) VALUES ($1, $2, $3, $4, $5)") |
|
| 317 | 317 | .bind(provider) |
|
| 318 | 318 | .bind(subject) |
|
| 319 | 319 | .bind(account_id) |
|
| … | 5 unchanged lines |
||
| 325 | 325 | } |
|
| 326 | 326 | ||
| 327 | 327 | pub async fn unlink(pool: &Pool, provider: &str, account_id: i64) -> sqlx::Result<bool> { |
|
| 328 | - | let r = sqlx::query("DELETE FROM account_logins WHERE provider = ? AND account_id = ?") |
|
| 328 | + | let r = sqlx::query("DELETE FROM account_logins WHERE provider = $1 AND account_id = $2") |
|
| 329 | 329 | .bind(provider) |
|
| 330 | 330 | .bind(account_id) |
|
| 331 | 331 | .execute(pool) |
|
| … | 2 unchanged lines |
||
| 334 | 334 | } |
|
| 335 | 335 | ||
| 336 | 336 | pub async fn links_of(pool: &Pool, account_id: i64) -> sqlx::Result<Vec<Login>> { |
|
| 337 | - | sqlx::query_as("SELECT * FROM account_logins WHERE account_id = ? ORDER BY provider").bind(account_id).fetch_all(pool).await |
|
| 337 | + | sqlx::query_as("SELECT * FROM account_logins WHERE account_id = $1 ORDER BY provider").bind(account_id).fetch_all(pool).await |
|
| 338 | 338 | } |
|
| 339 | 339 | ||
| 340 | 340 | /// A username for a new account from what the provider calls them: |
|
| … | 44 unchanged lines |
modified crates/hub/src/storage.rs +20 −16
| … | 121 unchanged lines |
||
| 122 | 122 | } |
|
| 123 | 123 | ||
| 124 | 124 | pub async fn of(pool: &Pool, repo_id: i64) -> sqlx::Result<Option<Storage>> { |
|
| 125 | - | sqlx::query_as("SELECT * FROM repo_storage WHERE repo_id = ?").bind(repo_id).fetch_optional(pool).await |
|
| 125 | + | sqlx::query_as("SELECT * FROM repo_storage WHERE repo_id = $1").bind(repo_id).fetch_optional(pool).await |
|
| 126 | 126 | } |
|
| 127 | 127 | ||
| 128 | 128 | /// Check a store URL a user gave: a bucket on S3, Google Cloud Storage or |
|
| … | 87 unchanged lines |
||
| 216 | 216 | return Err(format!("the repository could not be made in {url}: {e}")); |
|
| 217 | 217 | } |
|
| 218 | 218 | let now = now_ms(); |
|
| 219 | - | sqlx::query("INSERT INTO repo_storage (repo_id, url, credentials, created_ms, checked_ms, check_result) VALUES (?, ?, ?, ?, ?, ?)") |
|
| 220 | - | .bind(repo_id) |
|
| 221 | - | .bind(url) |
|
| 222 | - | .bind(sealed) |
|
| 223 | - | .bind(now) |
|
| 224 | - | .bind(now) |
|
| 225 | - | .bind(&result) |
|
| 226 | - | .execute(&app.db) |
|
| 227 | - | .await |
|
| 228 | - | .map_err(|e| e.to_string())?; |
|
| 219 | + | sqlx::query( |
|
| 220 | + | "INSERT INTO repo_storage (repo_id, url, credentials, created_ms, checked_ms, check_result) VALUES ($1, $2, $3, $4, $5, $6)", |
|
| 221 | + | ) |
|
| 222 | + | .bind(repo_id) |
|
| 223 | + | .bind(url) |
|
| 224 | + | .bind(sealed) |
|
| 225 | + | .bind(now) |
|
| 226 | + | .bind(now) |
|
| 227 | + | .bind(&result) |
|
| 228 | + | .execute(&app.db) |
|
| 229 | + | .await |
|
| 230 | + | .map_err(|e| e.to_string())?; |
|
| 229 | 231 | Ok(()) |
|
| 230 | 232 | } |
|
| 231 | 233 | ||
| … | 6 unchanged lines |
||
| 238 | 240 | Ok(r) => r.clone(), |
|
| 239 | 241 | Err(e) => format!("failed: {e}"), |
|
| 240 | 242 | }; |
|
| 241 | - | sqlx::query("UPDATE repo_storage SET checked_ms = ?, check_result = ? WHERE repo_id = ?") |
|
| 243 | + | sqlx::query("UPDATE repo_storage SET checked_ms = $1, check_result = $2 WHERE repo_id = $3") |
|
| 242 | 244 | .bind(now_ms()) |
|
| 243 | 245 | .bind(&text) |
|
| 244 | 246 | .bind(s.repo_id) |
|
| … | 7 unchanged lines |
||
| 252 | 254 | /// check against its store. |
|
| 253 | 255 | pub async fn set_credentials(app: &Shared, s: &Storage, credentials: Credentials) -> Result<String, String> { |
|
| 254 | 256 | let result = probe(&s.url, &credentials, false).await?; |
|
| 255 | - | sqlx::query("UPDATE repo_storage SET credentials = ?, checked_ms = ?, check_result = ? WHERE repo_id = ?") |
|
| 257 | + | sqlx::query("UPDATE repo_storage SET credentials = $1, checked_ms = $2, check_result = $3 WHERE repo_id = $4") |
|
| 256 | 258 | .bind(app.secret_key.seal(&credentials.to_json())) |
|
| 257 | 259 | .bind(now_ms()) |
|
| 258 | 260 | .bind(&result) |
|
| … | 50 unchanged lines |
||
| 309 | 311 | } |
|
| 310 | 312 | ||
| 311 | 313 | pub async fn policy(pool: &Pool, org_id: i64) -> sqlx::Result<Policy> { |
|
| 312 | - | let row: Option<(i64, String)> = |
|
| 313 | - | sqlx::query_as("SELECT require_bucket, allowed FROM org_storage_policy WHERE org_id = ?").bind(org_id).fetch_optional(pool).await?; |
|
| 314 | + | let row: Option<(i64, String)> = sqlx::query_as("SELECT require_bucket, allowed FROM org_storage_policy WHERE org_id = $1") |
|
| 315 | + | .bind(org_id) |
|
| 316 | + | .fetch_optional(pool) |
|
| 317 | + | .await?; |
|
| 314 | 318 | Ok(row |
|
| 315 | 319 | .map(|(r, a)| Policy { |
|
| 316 | 320 | require_bucket: r != 0, |
|
| … | 14 unchanged lines |
||
| 331 | 335 | prefixes.push(p.to_string()); |
|
| 332 | 336 | } |
|
| 333 | 337 | sqlx::query( |
|
| 334 | - | "INSERT INTO org_storage_policy (org_id, require_bucket, allowed) VALUES (?, ?, ?) \ |
|
| 338 | + | "INSERT INTO org_storage_policy (org_id, require_bucket, allowed) VALUES ($1, $2, $3) \ |
|
| 335 | 339 | ON CONFLICT (org_id) DO UPDATE SET require_bucket = excluded.require_bucket, allowed = excluded.allowed", |
|
| 336 | 340 | ) |
|
| 337 | 341 | .bind(org_id) |
|
| … | 66 unchanged lines |
modified crates/hub/src/variables.rs +4 −4
| … | 37 unchanged lines |
||
| 38 | 38 | } |
|
| 39 | 39 | ||
| 40 | 40 | pub async fn list(pool: &Pool, scope: Scope) -> sqlx::Result<Vec<Variable>> { |
|
| 41 | - | sqlx::query_as("SELECT * FROM flow_variables WHERE scope = ? AND scope_id = ? ORDER BY name") |
|
| 41 | + | sqlx::query_as("SELECT * FROM flow_variables WHERE scope = $1 AND scope_id = $2 ORDER BY name") |
|
| 42 | 42 | .bind(scope.kind()) |
|
| 43 | 43 | .bind(scope.id()) |
|
| 44 | 44 | .fetch_all(pool) |
|
| … | 3 unchanged lines |
||
| 48 | 48 | /// Set a variable at a level, new or changed. |
|
| 49 | 49 | pub async fn set(pool: &Pool, scope: Scope, name: &str, value: &str) -> sqlx::Result<()> { |
|
| 50 | 50 | sqlx::query( |
|
| 51 | - | "INSERT INTO flow_variables (scope, scope_id, name, value, updated_ms) VALUES (?, ?, ?, ?, ?) \ |
|
| 51 | + | "INSERT INTO flow_variables (scope, scope_id, name, value, updated_ms) VALUES ($1, $2, $3, $4, $5) \ |
|
| 52 | 52 | ON CONFLICT (scope, scope_id, name) DO UPDATE SET value = excluded.value, updated_ms = excluded.updated_ms", |
|
| 53 | 53 | ) |
|
| 54 | 54 | .bind(scope.kind()) |
|
| … | 8 unchanged lines |
||
| 63 | 63 | ||
| 64 | 64 | /// Remove a variable from a level; false when it was not there. |
|
| 65 | 65 | pub async fn delete(pool: &Pool, scope: Scope, name: &str) -> sqlx::Result<bool> { |
|
| 66 | - | let r = sqlx::query("DELETE FROM flow_variables WHERE scope = ? AND scope_id = ? AND name = ?") |
|
| 66 | + | let r = sqlx::query("DELETE FROM flow_variables WHERE scope = $1 AND scope_id = $2 AND name = $3") |
|
| 67 | 67 | .bind(scope.kind()) |
|
| 68 | 68 | .bind(scope.id()) |
|
| 69 | 69 | .bind(name) |
|
| … | 4 unchanged lines |
||
| 74 | 74 | ||
| 75 | 75 | /// Forget a level's variables, when the organisation or repository goes. |
|
| 76 | 76 | pub async fn delete_scope(pool: &Pool, scope: Scope) -> sqlx::Result<()> { |
|
| 77 | - | sqlx::query("DELETE FROM flow_variables WHERE scope = ? AND scope_id = ?").bind(scope.kind()).bind(scope.id()).execute(pool).await?; |
|
| 77 | + | sqlx::query("DELETE FROM flow_variables WHERE scope = $1 AND scope_id = $2").bind(scope.kind()).bind(scope.id()).execute(pool).await?; |
|
| 78 | 78 | Ok(()) |
|
| 79 | 79 | } |
|
| 80 | 80 | ||
| … | 50 unchanged lines |
modified crates/hub/src/web/inbox.rs +2 −2
| … | 193 unchanged lines |
||
| 194 | 194 | let me = require(&viewer, &shell)?; |
|
| 195 | 195 | csrf(&viewer, &f.csrf_token, &shell)?; |
|
| 196 | 196 | let key = if me.account.unsubscribe_key.is_empty() { crate::auth::random_hex(16) } else { me.account.unsubscribe_key.clone() }; |
|
| 197 | - | sqlx::query("UPDATE accounts SET email_notifications = ?, unsubscribe_key = ? WHERE id = ?") |
|
| 197 | + | sqlx::query("UPDATE accounts SET email_notifications = $1, unsubscribe_key = $2 WHERE id = $3") |
|
| 198 | 198 | .bind(f.email.is_some()) |
|
| 199 | 199 | .bind(&key) |
|
| 200 | 200 | .bind(me.account.id) |
|
| … | 18 unchanged lines |
||
| 219 | 219 | /// The one-click link in every email: no session needed, the key is the proof. |
|
| 220 | 220 | pub async fn unsubscribe(State(app): State<Shared>, Query(q): Query<UnsubQuery>) -> Response { |
|
| 221 | 221 | if let Some(key) = q.unsubscribe.filter(|k| k.len() >= 16) { |
|
| 222 | - | sqlx::query("UPDATE accounts SET email_notifications = 0 WHERE unsubscribe_key = ?").bind(key).execute(&app.db).await.ok(); |
|
| 222 | + | sqlx::query("UPDATE accounts SET email_notifications = 0 WHERE unsubscribe_key = $1").bind(key).execute(&app.db).await.ok(); |
|
| 223 | 223 | return (axum::http::StatusCode::OK, "Email notifications are off for that account.").into_response(); |
|
| 224 | 224 | } |
|
| 225 | 225 | Redirect::to("/notifications").into_response() |
|
| … | 77 unchanged lines |
modified crates/hub/src/web/issues.rs +1 −1
| … | 133 unchanged lines |
||
| 134 | 134 | && f.milestone.as_ref().is_none_or(|m| milestone.as_ref().is_some_and(|x| x.to_lowercase() == *m)) |
|
| 135 | 135 | && f.words.iter().all(|w| i.title.to_lowercase().contains(w) || i.body.to_lowercase().contains(w)); |
|
| 136 | 136 | if keep { |
|
| 137 | - | let comments: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM issue_comments WHERE issue_id = ?") |
|
| 137 | + | let comments: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM issue_comments WHERE issue_id = $1") |
|
| 138 | 138 | .bind(i.id) |
|
| 139 | 139 | .fetch_one(&app.db) |
|
| 140 | 140 | .await |
|
| … | 941 unchanged lines |
modified crates/hub/src/web/projects.rs +1 −1
| … | 403 unchanged lines |
||
| 404 | 404 | } |
|
| 405 | 405 | ||
| 406 | 406 | async fn newest_card(app: &Shared, project_id: i64) -> sqlx::Result<pdb::Card> { |
|
| 407 | - | sqlx::query_as("SELECT * FROM project_cards WHERE project_id = ? ORDER BY id DESC LIMIT 1").bind(project_id).fetch_one(&app.db).await |
|
| 407 | + | sqlx::query_as("SELECT * FROM project_cards WHERE project_id = $1 ORDER BY id DESC LIMIT 1").bind(project_id).fetch_one(&app.db).await |
|
| 408 | 408 | } |
|
| 409 | 409 | ||
| 410 | 410 | pub async fn act( |
|
| … | 251 unchanged lines |
modified crates/hub/src/web/proposals.rs +2 −2
| … | 546 unchanged lines |
||
| 547 | 547 | // The target moved, so the proposal's projected state is |
|
| 548 | 548 | // new and its checks would be for an older one (#134): run |
|
| 549 | 549 | // the pipeline on it, once. |
|
| 550 | - | let ran: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM flow_runs WHERE repo_id = ? AND state = ?") |
|
| 550 | + | let ran: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM flow_runs WHERE repo_id = $1 AND state = $2") |
|
| 551 | 551 | .bind(row.id) |
|
| 552 | 552 | .bind(&l.state) |
|
| 553 | 553 | .fetch_one(&app.db) |
|
| … | 562 unchanged lines |
||
| 1116 | 1116 | .await |
|
| 1117 | 1117 | .map_err(|e| Error::from_db(shell.clone(), e))? |
|
| 1118 | 1118 | .ok_or_else(|| Error::not_found(shell.clone()))?; |
|
| 1119 | - | let current: Option<bool> = sqlx::query_scalar("SELECT resolved FROM proposal_comments WHERE id = ? AND proposal_id = ?") |
|
| 1119 | + | let current: Option<bool> = sqlx::query_scalar("SELECT resolved FROM proposal_comments WHERE id = $1 AND proposal_id = $2") |
|
| 1120 | 1120 | .bind(id) |
|
| 1121 | 1121 | .bind(p.id) |
|
| 1122 | 1122 | .fetch_optional(&app.db) |
|
| … | 640 unchanged lines |
modified crates/hub/src/web/releases.rs +1 −1
| … | 399 unchanged lines |
||
| 400 | 400 | let (_, row, _) = lookup(&app, &shell, &viewer, &owner, &name).await?; |
|
| 401 | 401 | let e = |e| Error::from_db(shell.clone(), e); |
|
| 402 | 402 | let asset: Option<rdb::Asset> = |
|
| 403 | - | sqlx::query_as("SELECT a.* FROM release_assets a JOIN releases r ON r.id = a.release_id WHERE a.id = ? AND r.repo_id = ?") |
|
| 403 | + | sqlx::query_as("SELECT a.* FROM release_assets a JOIN releases r ON r.id = a.release_id WHERE a.id = $1 AND r.repo_id = $2") |
|
| 404 | 404 | .bind(id) |
|
| 405 | 405 | .bind(row.id) |
|
| 406 | 406 | .fetch_optional(&app.db) |
|
| … | 111 unchanged lines |
modified crates/hub/src/web/settings.rs +2 −2
| … | 587 unchanged lines |
||
| 588 | 588 | } |
|
| 589 | 589 | let bytes = auth::random_hex(32); |
|
| 590 | 590 | let id = accounts::create_challenge(&app.db, me.account.id, &bytes).await.map_err(|e| Error::from_db(shell.clone(), e))?; |
|
| 591 | - | sqlx::query("UPDATE challenges SET object = ? WHERE id = ?") |
|
| 591 | + | sqlx::query("UPDATE challenges SET object = $1 WHERE id = $2") |
|
| 592 | 592 | .bind(&object) |
|
| 593 | 593 | .bind(id) |
|
| 594 | 594 | .execute(&app.db) |
|
| … | 14 unchanged lines |
||
| 609 | 609 | let shell = Shell::new(&app, "/settings/keys", &viewer); |
|
| 610 | 610 | let me = require(&viewer, &shell)?; |
|
| 611 | 611 | csrf(&viewer, &f.csrf_token, &shell)?; |
|
| 612 | - | let object: Option<Vec<u8>> = sqlx::query_scalar("SELECT object FROM challenges WHERE id = ? AND account_id = ?") |
|
| 612 | + | let object: Option<Vec<u8>> = sqlx::query_scalar("SELECT object FROM challenges WHERE id = $1 AND account_id = $2") |
|
| 613 | 613 | .bind(f.challenge) |
|
| 614 | 614 | .bind(me.account.id) |
|
| 615 | 615 | .fetch_optional(&app.db) |
|
| … | 198 unchanged lines |