Skip to content

fix: harden login account duplicate-row reads - #6

Merged
wisdommen merged 1 commit into
masterfrom
fix/phase36-domain-key-duplicate-rows
Jul 7, 2026
Merged

fix: harden login account duplicate-row reads#6
wisdommen merged 1 commit into
masterfrom
fix/phase36-domain-key-duplicate-rows

Conversation

@wisdommen

@wisdommen wisdommen commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 36 domain-key duplicate-row hardening for UltiLogin.

LoginService.getAccount(UUID) now selects the canonical lowest-id AccountData row when duplicate player_uuid rows exist, with a regression test covering the duplicate-row ordering case.

Changes

  • src/main/java/com/ultikits/plugins/login/service/LoginService.java
  • src/test/java/com/ultikits/plugins/login/service/LoginServiceTest.java

Verification

  • mvn test -Dtest=LoginServiceTest — 122 tests, 0 failures, 0 errors\n- [x] mvn test — 459 tests, 0 failures, 0 errors
  • git diff --check

Scope / Non-goals

  • No schema, migration, POM, workflow, Framework, runtime datastore, cleanup, release, or deploy changes.
  • Runtime duplicate prevalence remains unknown; this PR only makes service-layer read behavior deterministic.
  • Part of UltiKits Phase 36: domain-key duplicate-row hardening follow-up cohort.

Summary by CodeRabbit

  • Bug Fixes

    • Improved account lookup so duplicate entries for the same player now resolve consistently to the account with the lowest ID.
    • This prevents unpredictable results when multiple matching records exist.
  • Tests

    • Added coverage for duplicate account records to verify the correct account is returned every time.

Phase 36 domain-key duplicate-row hardening.

Verification:

- [x] `mvn test -Dtest=LoginServiceTest` — 122 tests, 0 failures, 0 errors\n- [x] `mvn test` — 459 tests, 0 failures, 0 errors

gate_status: skill
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

LoginService's getAccount(UUID) method now resolves duplicate player_uuid rows by selecting the account with the minimum non-null ID via a new selectCanonicalAccount helper, instead of returning the first list element. A corresponding test verifies this canonical selection behavior.

Changes

Canonical account selection

Layer / File(s) Summary
Canonical account selection logic
src/main/java/com/ultikits/plugins/login/service/LoginService.java
Added Comparator import; getAccount(UUID) now calls a new selectCanonicalAccount helper that null-safely selects the account with the lowest id instead of returning accounts.get(0).
Duplicate UUID test coverage
src/test/java/com/ultikits/plugins/login/service/LoginServiceTest.java
Added a test creating two AccountData entries with the same playerUuid but different IDs, asserting the returned account has the lowest ID.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: hardening login account reads against duplicate rows.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/phase36-domain-key-duplicate-rows

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/com/ultikits/plugins/login/service/LoginService.java (1)

572-577: 🗄️ Data Integrity & Integration | 🔵 Trivial

Inconsistent duplicate-row handling across similar lookup methods.

getAccountByName (unchanged) still returns accounts.get(0) without canonical selection, while getAccount(UUID) now deterministically picks the lowest-id row. If player_name can also have duplicate rows, this method has the same non-determinism the PR is fixing for player_uuid.
[medium_effort_and_high_reward]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/ultikits/plugins/login/service/LoginService.java` around
lines 572 - 577, The duplicate-row handling in getAccountByName is still
non-deterministic because it returns the first list entry without a canonical
choice. Update this method to follow the same deterministic pattern as
getAccount(UUID) by selecting the lowest-id AccountData when multiple rows match
player_name. Keep the lookup in LoginService consistent across both methods so
duplicate records resolve the same way everywhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/ultikits/plugins/login/service/LoginService.java`:
- Around line 612-616: The canonical account selection in selectCanonicalAccount
is comparing AccountData.getId() as a string, which can choose the wrong account
for numeric-like ids. Update the ordering to use a numeric or persisted
timestamp/sequence field instead of String::compareTo, and keep the fallback in
selectCanonicalAccount consistent with the intended “oldest row” behavior. Use
the selectCanonicalAccount method and AccountData accessors to locate the
comparison logic.

---

Nitpick comments:
In `@src/main/java/com/ultikits/plugins/login/service/LoginService.java`:
- Around line 572-577: The duplicate-row handling in getAccountByName is still
non-deterministic because it returns the first list entry without a canonical
choice. Update this method to follow the same deterministic pattern as
getAccount(UUID) by selecting the lowest-id AccountData when multiple rows match
player_name. Keep the lookup in LoginService consistent across both methods so
duplicate records resolve the same way everywhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00e4136c-2554-459a-9a5f-bc2f2fb4a4ea

📥 Commits

Reviewing files that changed from the base of the PR and between 104d206 and 77a4cd4.

📒 Files selected for processing (2)
  • src/main/java/com/ultikits/plugins/login/service/LoginService.java
  • src/test/java/com/ultikits/plugins/login/service/LoginServiceTest.java

Comment on lines +612 to 616
private AccountData selectCanonicalAccount(List<AccountData> accounts) {
return accounts.stream()
.min(Comparator.comparing(AccountData::getId, Comparator.nullsLast(String::compareTo)))
.orElse(accounts.get(0));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find AccountData's id field/type and how IDs are generated (auto-increment numeric, UUID, etc.)
ast-grep outline src --items all --type class --match 'AccountData'
rg -n -A5 -B5 'class AccountData' src/main/java
rg -n 'setId\(' src/main/java --type=java -g '!*Test*'

Repository: UltiKits/UltiLogin

Length of output: 1324


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## AccountData.java\n'
sed -n '1,220p' src/main/java/com/ultikits/plugins/login/entity/AccountData.java

printf '\n## BaseDataEntity references\n'
rg -n -A4 -B4 'class BaseDataEntity|extends BaseDataEntity|setId\(|getId\(' src/main/java src/test/java --type=java

printf '\n## BaseDataEntity.java if present\n'
fd -a 'BaseDataEntity.java' src/main/java

Repository: UltiKits/UltiLogin

Length of output: 13023


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## LoginService selectCanonicalAccount and nearby logic\n'
sed -n '580,660p' src/main/java/com/ultikits/plugins/login/service/LoginService.java

printf '\n## LoginServiceTest duplicate-account case\n'
sed -n '540,590p' src/test/java/com/ultikits/plugins/login/service/LoginServiceTest.java

printf '\n## Search for any numeric-id or insertion-order assumptions\n'
rg -n 'lowest-id|canonical|insert|earliest|hashCode\(\)|playerUuid.hashCode|order by|Comparator.comparing\(AccountData::getId' src/main/java src/test/java --type=java

Repository: UltiKits/UltiLogin

Length of output: 7341


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## LoginService account creation / insert path\n'
sed -n '280,340p' src/main/java/com/ultikits/plugins/login/service/LoginService.java

printf '\n## Any other id assignment in main sources\n'
rg -n 'setId\(' src/main/java --type=java

printf '\n## AccountDataTest id-related assertions\n'
sed -n '170,210p' src/test/java/com/ultikits/plugins/login/entity/AccountDataTest.java

printf '\n## UltiLoginTestHelper sample account id assignment\n'
sed -n '130,170p' src/test/java/com/ultikits/plugins/login/UltiLoginTestHelper.java

Repository: UltiKits/UltiLogin

Length of output: 2208


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Files mentioning login_accounts or id column\n'
rg -n 'login_accounts|player_uuid|register_time|id\b' src main . --hidden --glob '!**/target/**' --glob '!**/.git/**'

printf '\n## Candidate schema / migration / docs files\n'
fd -a -e sql -e md -e yml -e yaml -e json -e conf -e properties . .

printf '\n## Search for BaseDataEntity in repository and nearby docs\n'
rg -n 'BaseDataEntity|Table\("login_accounts"\)|`@Table`\("login_accounts"\)' . --hidden --glob '!**/target/**' --glob '!**/.git/**'

Repository: UltiKits/UltiLogin

Length of output: 50375


Compare the canonical key numerically, not as a string. selectCanonicalAccount picks the lexicographically smallest id; with ids like String.valueOf(playerUuid.hashCode()), "10" sorts before "9". The duplicate-account test only covers equal-length ids, so it misses this case. If the intent is “oldest row,” sort by a persisted numeric/timestamp field instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/ultikits/plugins/login/service/LoginService.java` around
lines 612 - 616, The canonical account selection in selectCanonicalAccount is
comparing AccountData.getId() as a string, which can choose the wrong account
for numeric-like ids. Update the ordering to use a numeric or persisted
timestamp/sequence field instead of String::compareTo, and keep the fallback in
selectCanonicalAccount consistent with the intended “oldest row” behavior. Use
the selectCanonicalAccount method and AccountData accessors to locate the
comparison logic.

@wisdommen
wisdommen merged commit b09fbd3 into master Jul 7, 2026
7 checks passed
@wisdommen
wisdommen deleted the fix/phase36-domain-key-duplicate-rows branch July 7, 2026 01:46
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