Fix critical database issues in CardDAV sync service
Issue #1: Wrapped DELETE + INSERT operations in updateContactMultiValues in transaction to prevent inconsistent state if INSERT fails after DELETE. Issue #2: Replaced N+1 query pattern with batch INSERT statements using VALUES list for phones, emails, and addresses. Also optimized primary entry check queries to use SELECT 1 instead of COUNT(*). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -762,6 +762,7 @@ function updateContact(contactId, vcard, fillAll = false) {
|
|||||||
* @param {Object} vcard - Parsed vCard object
|
* @param {Object} vcard - Parsed vCard object
|
||||||
*/
|
*/
|
||||||
function updateContactMultiValues(contactId, vcard) {
|
function updateContactMultiValues(contactId, vcard) {
|
||||||
|
const transaction = db.get().transaction(() => {
|
||||||
// Delete non-primary entries
|
// Delete non-primary entries
|
||||||
db.get().prepare('DELETE FROM contact_phones WHERE contact_id = ? AND is_primary = 0').run(contactId);
|
db.get().prepare('DELETE FROM contact_phones WHERE contact_id = ? AND is_primary = 0').run(contactId);
|
||||||
db.get().prepare('DELETE FROM contact_emails WHERE contact_id = ? AND is_primary = 0').run(contactId);
|
db.get().prepare('DELETE FROM contact_emails WHERE contact_id = ? AND is_primary = 0').run(contactId);
|
||||||
@@ -769,6 +770,9 @@ function updateContactMultiValues(contactId, vcard) {
|
|||||||
|
|
||||||
// Insert new entries from vCard
|
// Insert new entries from vCard
|
||||||
insertContactMultiValues(contactId, vcard);
|
insertContactMultiValues(contactId, vcard);
|
||||||
|
});
|
||||||
|
|
||||||
|
transaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -779,48 +783,67 @@ function updateContactMultiValues(contactId, vcard) {
|
|||||||
function insertContactMultiValues(contactId, vcard) {
|
function insertContactMultiValues(contactId, vcard) {
|
||||||
// Check if primary entries exist
|
// Check if primary entries exist
|
||||||
const hasPrimaryPhone = db.get().prepare(
|
const hasPrimaryPhone = db.get().prepare(
|
||||||
'SELECT COUNT(*) as count FROM contact_phones WHERE contact_id = ? AND is_primary = 1'
|
'SELECT 1 FROM contact_phones WHERE contact_id = ? AND is_primary = 1'
|
||||||
).get(contactId).count > 0;
|
).get(contactId);
|
||||||
|
|
||||||
const hasPrimaryEmail = db.get().prepare(
|
const hasPrimaryEmail = db.get().prepare(
|
||||||
'SELECT COUNT(*) as count FROM contact_emails WHERE contact_id = ? AND is_primary = 1'
|
'SELECT 1 FROM contact_emails WHERE contact_id = ? AND is_primary = 1'
|
||||||
).get(contactId).count > 0;
|
).get(contactId);
|
||||||
|
|
||||||
const hasPrimaryAddress = db.get().prepare(
|
const hasPrimaryAddress = db.get().prepare(
|
||||||
'SELECT COUNT(*) as count FROM contact_addresses WHERE contact_id = ? AND is_primary = 1'
|
'SELECT 1 FROM contact_addresses WHERE contact_id = ? AND is_primary = 1'
|
||||||
).get(contactId).count > 0;
|
).get(contactId);
|
||||||
|
|
||||||
// Insert phones
|
// Batch insert phones
|
||||||
for (let i = 0; i < vcard.phones.length; i++) {
|
if (vcard.phones && vcard.phones.length > 0) {
|
||||||
const phone = vcard.phones[i];
|
const placeholders = vcard.phones.map(() => '(?, ?, ?, ?)').join(', ');
|
||||||
const isPrimary = (!hasPrimaryPhone && i === 0) ? 1 : 0;
|
const values = vcard.phones.flatMap((phone, i) => [
|
||||||
|
contactId,
|
||||||
|
phone.label || null,
|
||||||
|
phone.value,
|
||||||
|
(!hasPrimaryPhone && i === 0) ? 1 : 0
|
||||||
|
]);
|
||||||
|
|
||||||
db.get().prepare(`
|
db.get().prepare(`
|
||||||
INSERT INTO contact_phones (contact_id, label, value, is_primary)
|
INSERT INTO contact_phones (contact_id, label, value, is_primary)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES ${placeholders}
|
||||||
`).run(contactId, phone.label, phone.value, isPrimary);
|
`).run(...values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert emails
|
// Batch insert emails
|
||||||
for (let i = 0; i < vcard.emails.length; i++) {
|
if (vcard.emails && vcard.emails.length > 0) {
|
||||||
const email = vcard.emails[i];
|
const placeholders = vcard.emails.map(() => '(?, ?, ?, ?)').join(', ');
|
||||||
const isPrimary = (!hasPrimaryEmail && i === 0) ? 1 : 0;
|
const values = vcard.emails.flatMap((email, i) => [
|
||||||
|
contactId,
|
||||||
|
email.label || null,
|
||||||
|
email.value,
|
||||||
|
(!hasPrimaryEmail && i === 0) ? 1 : 0
|
||||||
|
]);
|
||||||
|
|
||||||
db.get().prepare(`
|
db.get().prepare(`
|
||||||
INSERT INTO contact_emails (contact_id, label, value, is_primary)
|
INSERT INTO contact_emails (contact_id, label, value, is_primary)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES ${placeholders}
|
||||||
`).run(contactId, email.label, email.value, isPrimary);
|
`).run(...values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert addresses
|
// Batch insert addresses
|
||||||
for (let i = 0; i < vcard.addresses.length; i++) {
|
if (vcard.addresses && vcard.addresses.length > 0) {
|
||||||
const addr = vcard.addresses[i];
|
const placeholders = vcard.addresses.map(() => '(?, ?, ?, ?, ?, ?, ?, ?)').join(', ');
|
||||||
const isPrimary = (!hasPrimaryAddress && i === 0) ? 1 : 0;
|
const values = vcard.addresses.flatMap((addr, i) => [
|
||||||
|
contactId,
|
||||||
|
addr.label || null,
|
||||||
|
addr.street,
|
||||||
|
addr.city,
|
||||||
|
addr.state,
|
||||||
|
addr.postalCode,
|
||||||
|
addr.country,
|
||||||
|
(!hasPrimaryAddress && i === 0) ? 1 : 0
|
||||||
|
]);
|
||||||
|
|
||||||
db.get().prepare(`
|
db.get().prepare(`
|
||||||
INSERT INTO contact_addresses (contact_id, label, street, city, state, postal_code, country, is_primary)
|
INSERT INTO contact_addresses (contact_id, label, street, city, state, postal_code, country, is_primary)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES ${placeholders}
|
||||||
`).run(contactId, addr.label, addr.street, addr.city, addr.state, addr.postalCode, addr.country, isPrimary);
|
`).run(...values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user