45 CidMap
PersistStore uses a CidMap to associate a non-zero integer Class Identifier
(CID) with each fully qualified class name for the IPersistable objects in the store.
These classes are always concrete, implement interface IPersistable, have a registered ReflectedClass
and have a reflected default constructor.
Note the implication: changing the fully qualified name of an IPersistable class
introduces schema evolution issues for a PersistStore.
Note that a fully qualified class name could possiblly include angled brackets for template classes.
As an example one of the txPersistStore unit tests produces the following mapping between CID
and fully qualified class name:
| CID | fully qualified class name |
|---|---|
| 1 | RootWithBTree |
| 2 | ceda::BPlusTree |
| 3 | ceda::LeafNode |
A CID is 1 plus the index into an array of strings.
In memory a CID is represented using the platform dependent type ssize_t.
It is serialised using variable length serialisation.
That means it is often serialised using only 1 or 2 bytes in each IPersistable object on disk.
This allows IPersistable objects to store just the CID to uniquely identify their type.
The CID is used to create an object instance of the required type during deserialisation.
If an invalid CID integer is read then a BadCidException exception is thrown.
This is never supposed to happen, if it does then the store is corrupt.
Note that the CidMap is not itself an IPersistable object with an OID,
because that is too incestuous a design! [The CidMap would be needed to help deserialise itself].
Together with the TypeOpsMap, the CidMap persists in a single serial element in a
PersistStore, with OID = 00000001.00000000.
When a PersistStore is opened the CidMap is read into memory.
There is support for applying a name translation map.
This maps old fully qualified class names to new fully qualified class names.
If it is found that a name has been repeated then a RepeatedClassNameInCidMapException exception is thrown.
This is never supposed to happen, if it does then the store is corrupt.
Serialising an object
When an IPersistable object is to be written to the store, we need to first write the CID.
That suggests we might want to cache the CID in the ReflectedClass.
However there are three reasons why we don't:
- It breaks the layering - because
ReflectedClassis in cxObject and doesn't know anything about cxPersistStore. - A completely read only
ReflectedClasscan go into the code segment rather than the data segment. This will reduce bloat and allow DLLs to load more quickly. - A process can open multiple persistent stores at the same time, and persistent stores can assign different CIDs to a
given
ReflectedClass. Therefore most generally there is a one to many mapping betweenReflectedClassandCID.
Therefore we choose to store a map from ReflectedClass* to CID in the CidMap.
It is unlikely that this will create a bottleneck, even when writing lots of small objects to disk.
Deserialising an object
After reading the CID from the archive, we need to find the ReflectedClass in order to create an
instance.
This is very fast because we simply index with the CID into a std::deque<Entry> and an Entry caches
a pointer to the ReflectedClass.
Thread safety
In order to support independent reading and writing of objects in the one persistent store, the
CidMap is protected by a std::mutex.
GetCid()
This function is called each time a persistent object is to be written to disk. Therefore it should be reasonably efficient.
GetCid() looks up m_rcToCid which is a map from ReflectedClass* to CID.
If the given reflected class is not found then a new entry must be created in both
m_cidToReflectedClass and m_rcToCid.
Lazy binding of name to reflected class
Previouly there was a more complex implementation of the CidMap using a std::deque
where an Entry only looks up the ReflectedClass on demand.
struct Entry
{
Entry(const ReflectedClass& rc) : m_name(rc.m_name), m_rc(&rc) {}
Entry() : m_rc(nullptr) {}
// Fully qualified class name
xstring m_name;
// Cache the lookup into the ReflectedClassRegistry
mutable const ReflectedClass* m_rc;
};
// Indexed by CID, and provides the fully qualified class name
std::deque<Entry> m_entrys;
In theory that might allow for a store to be opened before some of the DLLs that are required to read some of the
objects in the store.
However that seems rather complex, so for simplicity it is now assumed that all required libraries are opened
before a store is opened. That allows the mapping from CID to ReflectedClass to
be calculated at the time the CidMap is first deserialised.
Using a class name translation table
The following declarations in IPersistStore.h are relevant to a form of schema evolution of a PersistStore
involving renaming of classes.
$struct+ ClassNameTranslation
{
ConstStringZ oldName;
ConstStringZ newName;
};
$struct+ ClassNameTranslationTable
{
const ClassNameTranslation* entries;
ssize_t count;
};
A ClassNameTranslationTable can optionally be passed to CidMap::Deserialise() in order
to allow for the class names read from disk to be translated before they are looked up in the
ReflectedClass registry.
todo: It would be preferable to provide support for renaming of classes in the xc++ language and
this information is reflected.
We could allow for alternative names to be specified where a class is defined,
and this causes the ReflectedClass to be registered under all those names.
That would eliminate the need for class name translation tables.
Removal of redundant CidMap entries?
Can we afford to perform instance counting of all the objects in the store? The downside is that the map will be marked as dirty all the time. However it could actually be nice to have the usage statistics. If not then we can't afford to ever delete entries in the CidMap.
CidMap.h
Source: Ceda/cxPersistStore/src/CidMap.h
CidMap.cpp
Source: Ceda/cxPersistStore/src/CidMap.cpp