fix(ingestion): label upsert reliability, init idempotency, and sync health
Label upsert (issues + merge_requests): Replace INSERT ... ON CONFLICT DO UPDATE RETURNING with INSERT OR IGNORE + SELECT. The prior RETURNING-based approach relied on last_insert_rowid() matching the returned id, which is not guaranteed when ON CONFLICT triggers an update (SQLite may return 0). The new two-step approach is unambiguous and correctly tracks created_count. Init: Add ON CONFLICT(gitlab_project_id) DO UPDATE to the project insert so re-running `lore init` updates path/branch/url instead of failing with a unique constraint violation. MR discussions sync: Reset discussions_sync_attempts to 0 when clearing a sync health error, so previously-failed MRs get a fresh retry budget after successful sync. Count: format_number now handles negative numbers correctly by extracting the sign before inserting thousand-separators. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -179,9 +179,15 @@ fn count_notes(conn: &Connection, type_filter: Option<&str>) -> Result<CountResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn format_number(n: i64) -> String {
|
fn format_number(n: i64) -> String {
|
||||||
let s = n.to_string();
|
let (prefix, abs) = if n < 0 {
|
||||||
|
("-", n.unsigned_abs())
|
||||||
|
} else {
|
||||||
|
("", n.unsigned_abs())
|
||||||
|
};
|
||||||
|
|
||||||
|
let s = abs.to_string();
|
||||||
let chars: Vec<char> = s.chars().collect();
|
let chars: Vec<char> = s.chars().collect();
|
||||||
let mut result = String::new();
|
let mut result = String::from(prefix);
|
||||||
|
|
||||||
for (i, c) in chars.iter().enumerate() {
|
for (i, c) in chars.iter().enumerate() {
|
||||||
if i > 0 && (chars.len() - i).is_multiple_of(3) {
|
if i > 0 && (chars.len() - i).is_multiple_of(3) {
|
||||||
|
|||||||
@@ -133,7 +133,11 @@ pub async fn run_init(inputs: InitInputs, options: InitOptions) -> Result<InitRe
|
|||||||
for (_, gitlab_project) in &validated_projects {
|
for (_, gitlab_project) in &validated_projects {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO projects (gitlab_project_id, path_with_namespace, default_branch, web_url)
|
"INSERT INTO projects (gitlab_project_id, path_with_namespace, default_branch, web_url)
|
||||||
VALUES (?, ?, ?, ?)",
|
VALUES (?, ?, ?, ?)
|
||||||
|
ON CONFLICT(gitlab_project_id) DO UPDATE SET
|
||||||
|
path_with_namespace = excluded.path_with_namespace,
|
||||||
|
default_branch = excluded.default_branch,
|
||||||
|
web_url = excluded.web_url",
|
||||||
(
|
(
|
||||||
gitlab_project.id,
|
gitlab_project.id,
|
||||||
&gitlab_project.path_with_namespace,
|
&gitlab_project.path_with_namespace,
|
||||||
|
|||||||
@@ -299,18 +299,21 @@ fn upsert_label_tx(
|
|||||||
name: &str,
|
name: &str,
|
||||||
created_count: &mut usize,
|
created_count: &mut usize,
|
||||||
) -> Result<i64> {
|
) -> Result<i64> {
|
||||||
|
tx.execute(
|
||||||
|
"INSERT OR IGNORE INTO labels (project_id, name) VALUES (?1, ?2)",
|
||||||
|
(project_id, name),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if tx.changes() > 0 {
|
||||||
|
*created_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let id: i64 = tx.query_row(
|
let id: i64 = tx.query_row(
|
||||||
"INSERT INTO labels (project_id, name) VALUES (?1, ?2)
|
"SELECT id FROM labels WHERE project_id = ?1 AND name = ?2",
|
||||||
ON CONFLICT(project_id, name) DO UPDATE SET name = excluded.name
|
|
||||||
RETURNING id",
|
|
||||||
(project_id, name),
|
(project_id, name),
|
||||||
|row| row.get(0),
|
|row| row.get(0),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if tx.last_insert_rowid() == id {
|
|
||||||
*created_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -295,18 +295,21 @@ fn upsert_label_tx(
|
|||||||
name: &str,
|
name: &str,
|
||||||
created_count: &mut usize,
|
created_count: &mut usize,
|
||||||
) -> Result<i64> {
|
) -> Result<i64> {
|
||||||
|
tx.execute(
|
||||||
|
"INSERT OR IGNORE INTO labels (project_id, name) VALUES (?1, ?2)",
|
||||||
|
(project_id, name),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if tx.changes() > 0 {
|
||||||
|
*created_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let id: i64 = tx.query_row(
|
let id: i64 = tx.query_row(
|
||||||
"INSERT INTO labels (project_id, name) VALUES (?1, ?2)
|
"SELECT id FROM labels WHERE project_id = ?1 AND name = ?2",
|
||||||
ON CONFLICT(project_id, name) DO UPDATE SET name = excluded.name
|
|
||||||
RETURNING id",
|
|
||||||
(project_id, name),
|
(project_id, name),
|
||||||
|row| row.get(0),
|
|row| row.get(0),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if tx.last_insert_rowid() == id {
|
|
||||||
*created_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -593,6 +593,7 @@ fn clear_sync_health_error(conn: &Connection, local_mr_id: i64) -> Result<()> {
|
|||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE merge_requests SET
|
"UPDATE merge_requests SET
|
||||||
discussions_sync_last_attempt_at = ?,
|
discussions_sync_last_attempt_at = ?,
|
||||||
|
discussions_sync_attempts = 0,
|
||||||
discussions_sync_last_error = NULL
|
discussions_sync_last_error = NULL
|
||||||
WHERE id = ?",
|
WHERE id = ?",
|
||||||
params![now_ms(), local_mr_id],
|
params![now_ms(), local_mr_id],
|
||||||
|
|||||||
Reference in New Issue
Block a user