QSF is only a part of qof_book_merge. Processing any QSF file generates a second QofBook in a second QofSession. The data from the QSF file then needs to be imported into the active QofBook for that application using qof_book_merge. Every QSF operation requires a complimentary qof_book_merge.
However, qof_book_merge can be used without QSF. Any time that a QOF application has two QofBook's when one is needed, qof_book_merge can be used. The following sections now centre on the merge process itself.
The rule set for qof_book_merge needs to be open and extendable. The difficulty with this method is handling errors. If the rule set is wrong, errors at compile and debug time could be difficult to trace because qof_book_merge needs to rely on the rule set to decide which variables can be assigned to which structs. These errors will appear the same as errors arising from invalid values in existing structures. In order to allow qof_book_merge to run smoothly in the final program, the rule set needs to be definitive and robust. Each object declares its own parameters in the qofclass object definitions.
These parameters include:
The QOF object type. A unique name for the object that identifies all other objects of the same type in any QofBook.
A full list of all useful parameters held in the object. Note that this list is crucial to qof_book_merge but is actually defined by the object developer. qof_book_merge can only work with objects that describe themselves to QOF clearly and with all necessary parameters sensibly defined. This is the price of a generic merge.
The QOF type of each parameter. The type is essential if qof_book_merge is to know how to compare and merge the parameter data into the target QofBook.
Templates for functions. To be used to get and set suitable parameters. This was the hardest part of the merge operation for me to understand initially. I was unused to typed function pointers and with GnuCash using C, not C++, casting a function pointer to retrieve a parameter value from an object without knowing in advance what object is being queried, seemed like a black art. I missed the virtual keyword like never before!
The merge code needs to determine the type of object, the relative importance of the data and to set a sequence for the query that precedes the match. In any merge, some parameter data is inconsequential to the final result of the merge. Values that relate only to the import environment and values that are calculated from other parts of the import environment need to be recalculated in the final merge; the actual value of the calculation in the import is irrelevant to the final calculation after the merge. What matters is that all relevant components of the calculation are retained and merged into any existing components in the final book. Merging books necessarily causes the balance of merged accounts to be different from the balance of the import or the pre-merge book. The final value should be consistent with the pre-merge values (by addition and/or subtraction), but cannot be set explicitly, it must be determined from the real data as it exists after the merge.
A simple premise is therefore used to determine the relative importance of parameters within an object:
|If a parameter cannot be set, it is ignored by the merge.|
(The object is still compared using other parameters).
This is simply because a value that does not have a set function declared in the object definition should be defined as a calculated value or similar that should never be set. e.g. An account holds many transactions, each with a value. Although the value of a transaction clearly does need to be set, the balance of the account is a calculated value, dependent on the transactions contained within the account. Setting an account balance artificially would generate invalid data. So although the balance of an account can be retrieved (it has a get function), the lack of a set function determines that the balance parameter will not be compared as part of the merge. The comparison is based on the other parameters for that object. Two identical accounts are still identical objects, even if they contain different transactions and therefore a different balance. The transactions differ, the account does not. To consider a bank account or credit card account, each month the transactions and the balance change but the account is still the same account.
This clearly illustrates the need for objects to be described sensibly and logically! It also allows qof_book_merge to be used with other kinds of objects in the wider QOF design - the parameters that determine the details of the merge are set by the implementation of the code, rather than the code itself, subject to the implementation remaining a valid QOF environment.
In all rules, the prescence or lack of a GUID must be of primary importance in any merge because an exact match in GUID allows qof_book_merge to import the attached data, overwriting that section of the existing QofBook.
qof_book_merge only accepts input data as a QofBook* structure and therefore all import objects will be assigned a new GUID if one does not exist already when the data is inserted into the import QofBook. If the GUID in the imported data does not match the target QofBook*, qof_book_merge must use the rule set to proceed to match the contained data. A sequence of equality checks are required against existing data. In allowing fast processing of these checks, the rule set must balance the desire to not generate thousands of collision queries for the user to resolve against the need to limit the amount of manual editing of the final file.