Skip to content

refactor: add upsert() and insertIgnore() helpers to eliminate conflict handling duplication#2376

Merged
Yeraze merged 1 commit intomainfrom
refactor/upsert-helpers
Mar 22, 2026
Merged

refactor: add upsert() and insertIgnore() helpers to eliminate conflict handling duplication#2376
Yeraze merged 1 commit intomainfrom
refactor/upsert-helpers

Conversation

@Yeraze
Copy link
Owner

@Yeraze Yeraze commented Mar 22, 2026

Summary

Phase 3 of the Drizzle refactor — adds two BaseRepository helpers that abstract away the onConflictDoUpdate vs onDuplicateKeyUpdate and onConflictDoNothing vs try/catch differences between database backends.

New helpers

upsert(table, values, target, updateSet)

  • SQLite/PG: .insert(table).values(v).onConflictDoUpdate({ target, set: updateSet })
  • MySQL: .insert(table).values(v).onDuplicateKeyUpdate({ set: updateSet })

insertIgnore(table, values)

  • SQLite/PG: .insert(table).values(v).onConflictDoNothing()
  • MySQL: try/catch to swallow duplicate key errors

10 call sites simplified

File Method Pattern
settings.ts setSetting upsert
settings.ts setSettings upsert
ignoredNodes.ts addIgnoredNodeAsync upsert
misc.ts upsertSolarEstimate upsert
misc.ts addAutoTracerouteNode insertIgnore
misc.ts addAutoTimeSyncNode insertIgnore
nodes.ts upsertNode upsert
notifications.ts saveSubscription upsert
auth.ts setSession upsert
messages.ts insertMessage insertIgnore

Not changed

  • notifications.ts bulk mark-as-read methods (raw SQL with structural differences between backends)
  • notifications.ts saveUserPreferences (SQLite has different column set)
  • telemetry.ts / traceroutes.ts DELETE patterns (.returning() issue — future phase)

Files Changed

File Change
src/db/repositories/base.ts Add upsert() and insertIgnore() helpers
src/db/repositories/settings.ts Simplify 2 methods
src/db/repositories/ignoredNodes.ts Simplify 1 method
src/db/repositories/misc.ts Simplify 3 methods
src/db/repositories/nodes.ts Simplify 1 method
src/db/repositories/notifications.ts Simplify 1 method
src/db/repositories/auth.ts Simplify 1 method
src/db/repositories/messages.ts Simplify 1 method

Test plan

  • 3061 tests pass, 0 failures

🤖 Generated with Claude Code

…ct handling duplication

Adds two BaseRepository helpers:
- upsert(table, values, target, updateSet) — onConflictDoUpdate (SQLite/PG) vs onDuplicateKeyUpdate (MySQL)
- insertIgnore(table, values) — onConflictDoNothing (SQLite/PG) vs try/catch (MySQL)

Replaces 10 instances of database-type branching across 7 files:
- settings.ts: setSetting, setSettings (2)
- ignoredNodes.ts: addIgnoredNodeAsync (1)
- misc.ts: upsertSolarEstimate, addAutoTracerouteNode, addAutoTimeSyncNode (3)
- nodes.ts: upsertNode (1)
- notifications.ts: saveSubscription (1)
- auth.ts: setSession (1)
- messages.ts: insertMessage (1)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Mar 22, 2026

Claude finished @Yeraze's task —— View job


Pull Request Review ✅

Excellent refactor that effectively eliminates database conflict handling duplication through well-designed helper methods.

Code Quality & Best Practices ⭐

Strengths:

  • Clean abstraction: The upsert() and insertIgnore() helpers elegantly abstract away the MySQL vs SQLite/PostgreSQL differences (onDuplicateKeyUpdate vs onConflictDoUpdate)
  • Consistent API: Both helpers follow the same pattern with clear parameters and return types
  • Proper error handling: MySQL insertIgnore() uses try/catch to swallow duplicate key errors since it lacks onConflictDoNothing()
  • Type safety: Generic any types are justified here since the helpers work with any Drizzle table
  • Documentation: Good JSDoc comments explain the cross-database behavior

Usage patterns look correct:

  • settings.ts:54,76: upsert() for key-value setting storage
  • nodes.ts:260: upsert() for node info with nodeNum as conflict target
  • messages.ts:54: insertIgnore() for duplicate message prevention
  • misc.ts:113,186,557: Mixed usage appropriate for each use case

Architecture Assessment ✅

Excellent design decisions:

  • Right abstraction level: Placed in BaseRepository where all repos can inherit
  • Maintains existing patterns: Preserves the repository pattern and Drizzle ORM usage
  • Database agnostic: Correctly handles all three backends (SQLite, PostgreSQL, MySQL)
  • Performance neutral: No overhead compared to previous branched implementations

Potential Issues 🔍

Minor considerations:

  1. MySQL error swallowing (base.ts:210): The insertIgnore() method catches all errors for MySQL, not just duplicate key errors. Consider catching specific error codes:

    } catch (err) {
      // Only ignore duplicate key errors (1062), re-throw others
      if (err.code !== 'ER_DUP_ENTRY') throw err;
      return null;
    }
  2. Return value consistency: insertIgnore() returns the insert result for SQLite/PG but null for MySQL on conflict. This is handled correctly by getAffectedRows() but worth noting for future API consumers.

Security & Performance ✅

No security concerns:

  • Uses parameterized Drizzle queries (no SQL injection risk)
  • No credential or sensitive data exposure
  • Maintains existing access patterns

Performance benefits:

  • Significant LOC reduction: 164 deletions vs 71 additions = net -93 lines
  • Reduced code duplication: 10 call sites simplified
  • Maintainability improvement: Single source of truth for conflict handling logic

Test Coverage 📊

Test status: PR description reports 3061 tests pass, 0 failures. The refactor:

  • Only changes implementation details, not public APIs
  • Preserves exact same behavior for all call sites
  • Uses existing Drizzle methods under the hood

Recommendation: The existing test coverage should be sufficient since this is purely an internal refactor.

Summary

This is a high-quality refactor that:

  • ✅ Eliminates significant code duplication (93 net LOC reduction)
  • ✅ Provides clean database-agnostic abstractions
  • ✅ Maintains backward compatibility
  • ✅ Follows established patterns and conventions
  • ✅ Has comprehensive test coverage

Minor suggestion: Consider more specific error handling in MySQL insertIgnore(), but this is not blocking.

Recommendation: ✅ Approve - This refactor significantly improves code maintainability with no functional impact.


@Yeraze Yeraze merged commit 9111866 into main Mar 22, 2026
17 checks passed
@Yeraze Yeraze deleted the refactor/upsert-helpers branch March 22, 2026 15:19
@Yeraze Yeraze mentioned this pull request Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant