Background
One of the largest differences in how data is managed between SQL and ISAM is how deleted records are handled. In short:- ISAM: does mark a record as deleted. This way it is out of scope. To really get rid of the record you need to do a time consuming PACK operation.
- SQL: does really delete the record/row.
The PostgreSQL DatabaseEngine and its ISAM emulation can deal very well with all that differences if it comes to ISAM emulation of that behavior. But what if the SQL developers want to delete a record? The answer here is very simple. The SQL developer needs to deal with the ISAM semantics. If not he would delete a record leading to:
- Inconsistent data, record# are missing leading to inconsistent navigational behavior on behalf of the ISAM emulating side
- Possible corrupted orders, typical pattern 1:N relationship established via implicit ordering by record# in detail items
- Broken business logic on the Xbase++ side because record re-cycling is malfunctioning
But how to do it right from the SQL side if you really want to use more of the power of an SQL DBMS, just think about getting rid of deleted records or using constraints on the tables and database. In the following, we are starting a collection of concepts for problems you may run into if you want to have full interoperability of ISAM emulating tables and your SQL code.
// getting rid of deleted record on the SQL side
That's a simple no-brainer, PostgreSQL comes with updateable views (non-materialized). So, with the following view, you can hide all the deleted records from your SQL code.
SQL:
CREATE VIEW artikel4sql AS (SELECT sn,ver, __record FROM artikel WHERE __deleted=false);
The previous code does a few things:
- Use "4sql" as a generic postfix for all SQL views derived from an ISAM emulating table. So artikel becomes artikel4sql
- Use only the real table column plus the record#. The reason for the record# is that some ISAM tables have not been well designed. They use record# as an order, or there is no primary key in the ISAM table so you can not identify the row in SQL.
- We only use the "public" fields in the SQL view, we do not use __order columns. This way we avoid dependencies between order columns and SQL views.
- We use a WHERE clause to filter out the deleted record
// establish a unique constraint on a SQL table
Sometimes it is helpful to establish a constraint on behalf of the SQL server to avoid writing new code related to data model-specific rules outside of Xbase++.To do so we establish in the following a unique constraint for our artikel table.
SQL:
DROP TABLE IF EXISTS artikel CASCADE;
CREATE TABLE artikel ( id CHAR(5), ver CHAR(4), __deleted BOOLEAN, __record INT);
CREATE VIEW artikel4sql AS (SELECT id,ver, __record FROM artikel WHERE __deleted=false);
CREATE UNIQUE INDEX i_artikel ON artikel (id) WHERE __deleted=false;
-- added three records for testing
--
INSERT INTO artikel VALUES( 'T1', 'T1v1', false, 1 );
INSERT INTO artikel VALUES( 'T2', 'T2v1', false, 2 );
INSERT INTO artikel VALUES( 'T3', 'T3v1', false, 3 );
-- delete a record #2 and add one with the same key value, does not violate constraint,
-- W/O deletion the insert will violate the unique constraint
--
UPDATE artikel SET __deleted=true WHERE __record=2;
INSERT INTO artikel VALUES( 'T2', 'T2v2', false, 4 );
SELECT * FROM artikel4sql ORDER BY __record;
Of course, the approach above requires that you do not RECALL a deleted record as this may lead to a unique constraint violation. Also, since CDX or NTX base storage drives store duplicate keys in a table and just do not add the record to the index existing business logic may now fail with runtime errors due to a constraint violation even though your code worked very well in the past.
The bottom line here is that you can do a lot with the SQL DBMS, but any constraint/rule applied to that level (database/table) also affects how your or if your existing business logic, which is based on ISAM semantics, works.