NationalSecurityAgency/ghidra · critical · SQLException

Unexpected updated row count: {}

Error message

Unexpected updated row count: {}

What it means

Thrown as an SQLException by H2VectorTable.updateVector() when an UPDATE on vec_hash affects more than one row. The h2_vectable has a unique index on vec_hash, so an UPDATE matching by vec_hash should affect at most one row. A row count greater than 1 indicates the unique constraint on vec_hash was violated (duplicate hash values exist), which is a data integrity violation.

Source

Thrown at Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/H2VectorTable.java:220

	public long updateVector(LSHVector vec, int countDiff) throws SQLException {

		if (countDiff <= 0) {
			throw new IllegalArgumentException("Invalid countDiff: " + countDiff);
		}

		// TODO: it may be possible to optimize the technique employed here

		PreparedStatement s = update_by_hash_stmt.prepareIfNeeded(() -> db.prepareStatement(
			"UPDATE " + TABLE_NAME + " SET count = count + ? WHERE vec_hash = ?"));
		long vecHash = vec.calcUniqueHash();
		s.setInt(1, countDiff);
		s.setLong(2, vecHash);
		int rc = s.executeUpdate();
		if (rc == 0) {
			return insert(countDiff, vec);
		}
		if (rc > 1) {
			throw new SQLException("Unexpected updated row count: " + rc);
		}

		s = select_id_by_hash_stmt.prepareIfNeeded(() -> db
				.prepareStatement("SELECT id, count FROM " + TABLE_NAME + " WHERE vec_hash = ?"));
		s.setLong(1, vecHash);

		long id;
		int count;
		try (ResultSet rs = s.executeQuery()) {
			if (!rs.next()) {
				throw new SQLException("Unknown vector hash");
			}
			id = rs.getLong(1);
			count = rs.getInt(2);
		}
		vectorStore.update(
			new VectorStoreEntry(id, vec, count, vectorFactory.getSelfSignificance(vec)));
		return id;

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Query the table for duplicate vec_hash values: 'SELECT vec_hash, COUNT(*) FROM h2_vectable GROUP BY vec_hash HAVING COUNT(*) > 1' to identify and resolve duplicates.
  2. If duplicates exist, remove or merge them so each vec_hash is unique.
  3. Verify the unique index h2_vectable_index exists on the table: 'SELECT * FROM information_schema.indexes WHERE index_name = h2_vectable_index'.
  4. If the index was dropped, recreate it.
  5. If hash collisions are the root cause, investigate calcUniqueHash() for collisions and consider rebuilding the database.

Example fix

// before
int rc = s.executeUpdate();
if (rc > 1) {
    throw new SQLException("Unexpected updated row count: " + rc);
}

// after (detect and report duplicates before failing)
int rc = s.executeUpdate();
if (rc > 1) {
    throw new SQLException(String.format(
        "Data integrity violation: %d rows share vec_hash %d; " +
        "run: SELECT id FROM h2_vectable WHERE vec_hash = %d", rc, vecHash, vecHash));
}
Defensive patterns

Strategy: validation

Validate before calling

// Check for duplicate vec_hash values before update operations
try (Statement st = db.createStatement();
        ResultSet rs = st.executeQuery(
            "SELECT vec_hash, COUNT(*) as cnt FROM " + TABLE_NAME +
            " GROUP BY vec_hash HAVING COUNT(*) > 1")) {
    if (rs.next()) {
        throw new SQLException(String.format(
            "Duplicate vec_hash detected: %d appears %d times",
            rs.getLong(1), rs.getInt(2)));
    }
}

Try / catch

try {
    return vectorTable.updateVector(vec, countDiff);
} catch (SQLException e) {
    if (e.getMessage().startsWith("Unexpected updated row count:")) {
        // Data integrity violation: resolve duplicates first
        long hash = vec.calcUniqueHash();
        resolveDuplicateVecHashes(db, hash);
        return vectorTable.updateVector(vec, countDiff); // retry
    }
    throw e;
}

Prevention

When it happens

Trigger: Occurs in updateVector() when 'UPDATE h2_vectable SET count = count + ? WHERE vec_hash = ?' returns rc > 1. This means multiple rows share the same vec_hash value despite the unique index h2_vectable_index. This can happen if: the unique index was dropped or corrupted, two different LSHVectors produce a hash collision in calcUniqueHash(), or a prior insert bypassed the unique constraint.

Common situations: Hash collision in calcUniqueHash() where two genuinely different vectors produce the same hash (extremely rare but possible). The unique index was manually dropped. Database corruption caused duplicate entries. A bug in a prior version allowed duplicate vec_hash values to be inserted.

Related errors


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/8c260946e1aec9a0. Report an issue: GitHub.