Introduction
This page describes the way we generate reverse LDIF, for each forward operation. A reverse LDIF can be applied on a server in order to revert an operation.
In this document we do not consider what is required to revert a series of operations which have ordering requirements: i.e. they must be applied in reverse order. Here we simply focus on how to generate the LDIF for each atomic operation.
Operations
Operations to generate reverse LDIF for:
Operation |
AddRequest |
DelRequest |
ModifyRequest |
ModifyDNRequest |
AddRequest
Computing the reverse LDIF for an AddRequest is easy: it's a DelRequest where we use the DN of the created entry.
An AddRequest contains those informations :
AddRequest ::= [APPLICATION 8] SEQUENCE { entry LDAPDN, attributes AttributeList } AttributeList ::= SEQUENCE OF attribute Attribute
For the added entry :
dn: cn=test, dc=example, dc=com objectclass: top objectclass: person cn: test sn: This is a test
the reverse LDIF will be :
dn: cn=test, dc=example, dc=com changetype: delete
DelRequest
To produce a reverse LDIF for a DelRequest, we must first read the deleted attribute. We will create a AddRequest based on the read deleted entry :
* read the entry to be deleted * create a revert ldif AddRequest with this deleted entry * delete the entry
Considering the existing entry :
dn: cn=test, dc=example, dc=com objectclass: top objectclass: person cn: test sn: This is a testcreatorsName: dc=admin, ou=systemcreateTimestamp: 20071010150132ZmodifiersName: dc=admin, ou=systemmodifyTimestamp: 20071010150133Z
if we have a delRequest which ldif is :
dn: cn=test, dc=example, dc=com changetype: delete
the reversed ldif should be :
dn: cn=test, dc=example, dc=comchangetype: add objectclass: top objectclass: person cn: test sn: This is a test
There is still a question regarding operational attributes: should we keep them (in the LDIF)? How do we guarantee that the creatorsName and createTimeStamp attributes are the original ones, instead of the one injected while creating the saved entry? Operational attribute state needs to be captured out of band (from the LDIF data) and is the responsibility of the change log service to manage, capture and track. This information is made available to higher level interfaces which handle all this. So at this level we need not worry: the LDIF's generated have no operational attributes in them as would be normally expected for a valid LDIF.
To be able to build this reverse ldif, we need to read the previous entry before the deletion.
ModifyRequest
This is the most complex operation. The modification is applied on a specific entry, and can impact one or more attribute, one or more value, but it can't modify an attribute which is part iof the entry RDN.
We have three kind of modifications : add, delete and replace. They are applied in the order they are found in the Modify request, so the reverse LDIF must store them in reverse order too.
Depending on the modified values, each basic operation may have some different semantic. The following table present all the possible actions :
modification |
initial entry |
imported Ldif |
resulting entry |
Comments |
Reverse LDIF |
add |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
In this case, the ou value is simply added |
dn: cn=test, ou=system |
add |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
The ou attribute and its value has been created |
dn: cn=test, ou=system |
add |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
Nothing is done. |
no reverse, void operation |
delete |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
The ou=acme corp value has been deleted |
dn: cn=test, ou=system |
delete |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
The ou attribute has been removed |
dn: cn=test, ou=system |
delete |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
As all the ou values have been removed, |
dn: cn=test, ou=system |
replace |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
The ou attributes' values are replaced |
dn: cn=test, ou=system |
replace |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
Create the ou attribute |
dn: cn=test, ou=system |
replace |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
Delete the ou attribute |
dn: cn=test, ou=system |
ModifyDNRequest
This request is used to move entries or to rename entries or to move and rename entries. Its counterpart in a ldif file is a 'changetype: moddn' or a 'changetype: modrdn' operation (moddn or modrdn are synonymous).
We will separate the ModifyDN into 3 different cases :
- A simple move operation : we change the superior, the RDN remains the same
- A rename operation : the RDN is changed
- A move and rename operation : a combinaison of both previous operations
Move operation
This is the simplest one : we change the superior, without changing the entry's attributes nor the RDN
the following entry :
cn=test, dc=example, dc=orgobjectClass: personobjectClass: topcn: test
will be transformed to the entry
cn=test, ou=systemobjectClass: personobjectClass: topcn: test
if we change the superior from dc=example,dc=com to ou=system
and the revert operation will be :
cn=test, ou=systemchangetype: moddnnewRdn: cn=test deleteoldrdn: 0 newSuperior: dc=example,dc=com
Rename operation
It's a bit more complex. We have to take care of multiple value RDN (RDN like cn=small+sn=test) and also to the existing attributes. The deleteOldRdn flag must also be take into account when dealing with the revert operation.
There are five cases :
- we change the RDN, we don't change the superior and we don't delete the old RDN : This is a rename
- we change the RDN, we don't change the superior and we delete the old RDN : This is a rename, with cleaning
- we change the RDN, we change the superior and we don't delete the old RDN : This is a move and rename
- we change the RDN, we change the superior and we delete the old RDN : This is a move and rename, with cleaning
- we don't change the RDN, we change the superior : this is a Move operation
We have also a few extra cases for 1 to 4, depending on the RDN composition (ie, wether it's a simple RDN or a composite RDN)
a.1 The initial RDN is simple, so is the target RDN (oldRdn: A, newRdn: B)
a.2 The initial RDN is simple, and the target RDN is composite, but they have different values (oldRdn: A, newRdn: B+C)
a.3 The initial RDN is simple, and the target RDN is composite, but they have overlaping values (oldRdn: A, newRdn: A+B)
a.4 The initial RDN is composite, and the taret RDN is simple, but they have different values (oldRdn: A+B, newRdn: C)
a.5 The initial RDN is composite, and the taret RDN is simple, but they have overlaping values (oldRdn: A+B, newRdn: A)
a.6 Both RDN are composite, but don't overlap (oldRdn: A+B, newRdn: C+D)
a.7 Both RDN are composite, and they overlap (oldRdn: A+B, newRdn: B+C)
All those special cases applies to each of the rename operation. We can use this algorithjm to handle them :
- Create all the attributes which are present in the newRdn but not in the oldRdn
- Suppress all the attributes which are present in the oldRdn but not in the newRdn if the deleteOldRdn flag is true
To compute the revert operation, we do the opposite :
- remove all the attributes which are present in the newRdn but not in the oldRdn
- create all the attributes which are present in the oldRdn but not in the newRdn (if they are already present, it won't be a problem)
In any case, we don't have to take care of the deleteOldRdn flag to compute the revert operation.
The following table gives an example for each of those cases applied on the initial entries :
dn: cn=test, dc=example, dc=com objectclass: top objectclass: person cn: test sn: This is a test dn: cn=test+gn=small dc=example, dc=com objectclass: top objectclass: person cn: testgn: small sn: This is a test
For a.1, the new superior will be 'ou=system', the old RDN will be 'cn=test', the new RDN will be 'cn=joe'
For a.1, the new superior will be 'ou=system', the old RDN will be 'cn=test', the new RDN will be 'cn=joe+sn=the plumber'
case |
deleteoldrdn |
new superior |
modifying ldif |
resulting entry |
reverse ldif |
1 |
no |
none |
dn: cn=test, dc=example, dc=com |
dn: cn=joe, dc=example, dc=com |
dn: cn=joe, dc=example, dc=com |
2 |
no |
none |
dn: cn=test, dc=example, dc=com |
dn: cn=joe+sn=the plumber, |
dn: cn=joe+sn=the plumber, |
3 |
no |
none |
dn: cn=test, dc=example, dc=com |
dn: cn=joe*+sn=this is a test*, dc=example, dc=com |
dn: cn=test+sn=this is a test, |
1.2 |
no |
none |
dn: cn=test, dc=example, dc=com |
dn: cn=joe+sn=the plumber, dc=example, dc=com |
dn: cn=joe+sn=the plumber, dc=example, dc=com |
2 |
yes |
none |
dn: cn=test, dc=example, dc=com |
dn: cn=joe, dc=example, dc=com |
dn: cn=joe, dc=example, dc=com |
3 |
no |
ou=system |
dn: cn=test, dc=example, dc=org |
dn: cn=joe, ou=system |
dn: cn=joe, ou=system |
4 |
yes |
ou=system |
dn: cn=test, dc=example, dc=org |
dn: cn=joe, ou=system |
dn: cn=joe, ou=system |
5 |
no |
ou=system |
dn: cn=test, dc=example, dc=org |
dn: cn=test, ou=system |
dn: cn=test, ou=system |
Computing the reverse LDIF for a ModifyDN request follows the algorithm :
if the newRdn is different from the existing RDN
then reverseLdif.deleteOldRdn = true
else reverseLdif.deleteOldRdn = false
if modifyDn.newSuperior not empty
then reverseLdif.newSuperior = modifyDn.dn minus the modifyDN.dn.getRDN
reverseLdif.newRdn = modifyDn.dn.getRDN