Soft delete

Soft delete means that instead of deleting a record physically from the database, it is marked as deleted instead. Rows that have NULL or our beginning-of-time value (which in SuperOffice is 1.1.1760) are considered active; rows that have a different date are considered deleted.

When a contact or person is deleted in SuperOffice it gets the deletedDate set, and when a request is deleted it sets the status of the ticket to Deleted and updates last_changed. This removes it from the UI and puts it in the Recycle Bin. What happens behind the scenes is that when the application code searches for a record in for example the person-table, this code will appendAND (deletedDate IS NULL OR deletedDate = 1.1.1760) to the query (for each table that has soft-delete). As a result, such rows disappear from the results and are never given back to the application – which in practice is the same as deleting them.

GDPR

GDPR places limits on keeping irrelevant information, and soft-deleted records are no exception. Therefore, after a time – which will default to 14 days unless specifically set in System preference >> Retain deleted data – a periodic background process will really delete soft-deleted records that are older than the “retention time”.

Directly dependent records such as email or phone are deleted. Other tables have independent reasons to exist: a meeting still happened, even if no longer have a valid person reference, so there we just zero the appointment.person_id.

For those using the APIs at any level, the interception code will change it into an Update automatically. Search for something through the API, and the filtering conditions will be appended. For those who work directly with the database, it’s recommended switching to an API approach for this reason.

🛈 Tip
If the incoming request includes select for deletedDate > 1.1.176, this will switch off the automatic filtering conditions for all tables in the query.

Searching for soft-deleted records

In some scenarios it might be useful to search for information which has been soft-deleted. It exists 3 ArchiveProviders, for each of the 3 entitites, for this purpose:

Database Mirroring and Travel

Database Mirroring and Travel will both replicate the soft delete as the update it really is. Mirroring will also replicate the deep delete that happens later, but Travel will not. The reason is that the deep delete, being a real consistency cleanup, needs to work with whatever is actually in the database. Only the code local to the receiving database can know that, so we use the same strategy as when replicating a Move/Merge operation. The initial operation (update deletedDate) is replicated; and the consequence in the form of a deep delete are recreated on the receiving database, after the proper interval, using whatever data is present at the time.

Hard delete

As explained above the soft-delete does not actually remove the records from the database. Physical deletion happens after the retention time, and is described as a hard-delete.

Relation Action

The Relation Action describes the type of edit is being done, and what it entails.

Relation ActionDescription
DeleteRecordDeletes the entire record from the table when the condition is met.
ZeroForeignKeySets the foreign key to 0 instead of deleting the record.
IgnoreTakes no action on the record; it remains unaffected.

Hard-delete of Contact

When a hard-delete occurs there is a lot of information that gets edited based on the Relation Action

DeleteRecord

TableFieldNotes
addressowner_id (atype_idx = 1)
addressowner_id (atype_idx = 2)
binaryobjectlinkownertable
company_domaincompany_id
contactinterestcontact_id
emailcontact_id
favouritetable_id
foreignkeytable_id
historytable_id
mergemovelogtablenumber
phoneowner_id (ptype_idx = 1)
phoneowner_id (ptype_idx = 3)
relationsdestination_table
relationssource_table
s_shipment_addrcontact_id
statusvaluecontact_id
textowner_id (type = 1)
textowner_id (type = 10)
urlcontact_id

ZeroForeignKey

TableFieldNotes
appointmentcontact_id
chat_sessioncontact_id
contactmother_id
email_itemcontact_id
form_submissioncontact_id
invoicecompany_id
invoice_sumcompany_id
personcontact_idcontact -> person cascade is handled elsewhere
projectmembercontact_id
salecontact_id
salehistcontact_id
salestakeholdercontact_idon person delete, but contact maps to just zero
selectionmembercontact_idextra pass later to delete c=0, p=0 records

Ignore

TableFieldNotes
countervaluecontact_idwill be regenerated away
datarighttableid
freetextindexownertable_id
freetextindextable_id
importobjecttableid
ownercontactlinkcontact_idcontact records are never deleted; deleting is a mistake
relationtargetdestination_table
relationtargetsource_table
satellitecontact_idcontact records are never deleted; deleting is a mistake
sl_vendorcontact_idcontact records are never deleted; deleting is a mistake
traveltransactionlogtablenumber
🛈 Note
CustomFields and ExtraTables also gets cleaned up.

Hard-delete of Person

When a hard-delete occurs there is a lot of information that gets edited based on the Relation Action.

DeleteRecord

TableFieldNotes
addressowner_id (atype_idx = 16387)person parent
addressowner_id (atype_idx = 1)contact parent
addressowner_id (atype_idx = 2)contact parent
binaryobjectlinkownertable
consentpersonperson_id
credentialspersonid
emailcontact_idcontact parent
emailperson_id
email_itemperson_id
favouritetable_id
foreignkeytable_id
historytable_id
login_customercustomer_id
message_customerscustomer_id
mergemovelogtablenumber
personinterestperson_id
phoneowner_id (ptype_idx = 1)contact parent
phoneowner_id (ptype_idx = 16385)
phoneowner_id (ptype_idx = 16387)
phoneowner_id (ptype_idx = 16388)
phoneowner_id (ptype_idx = 16389)
phoneowner_id (ptype_idx = 16390)
phoneowner_id (ptype_idx = 3)contact parent (no value ‘2’ exists)
projectmemberperson_id
salestakeholderperson_id
s_bounce_shipmentcustomer_id
s_link_customercustomer_id
s_list_customercustomer_id
s_sent_messagecustomer_id
s_shipment_addrcustomer_id
selectionmemberperson_id
shipmenttypereservationperson_id
smscustomer_id
statusvalueperson_id
temporarykeyperson_id
textowner_id (type = 2)
textowner_id (type = 3)
ticket_customerscustomer_id
urlcontact_idcontact parent
urlperson_id
user_candidateperson_id
relationsdestination_table
relationssource_table

ZeroForeignKey

TableFieldNotes
appointmentinvitedpersonid
appointmentperson_id
chat_sessioncustomer_id
contactsupportpersonid
ej_messagecustomer_id
form_submissionperson_id
invoicecustomer_id
invoice_sumcustomer_id
kb_entry_commentcustomer_id
saleperson_id
salehistperson_id
ticketcust_id
ticket_log_actioncustomer_id

Ignore

TableFieldNotes
associateperson_id
countervalueperson_id
datarighttableid
freetextindexownertable_id
freetextindextable_id
importobjecttableid
relationtargetdestination_table
relationtargetsource_table
traveltransactionlogtablenumber
🛈 Note
CustomFields and ExtraTables also gets cleaned up.

Hard-delete Request

The following tables are affected by a hard-delete of a request

Summary

For the person, contact and ticket tables, a delete through any API becomes an update to a date field; and any select automatically gets conditions that make such rows disappear. A background process will periodically clean up soft-deleted records that are too old.

For users, it means that delete operations can be undone. For DBAs, the deep delete means a more consistent database.