Select Git revision
applyPatches.sh
-
Henrik tom Wörden authoredHenrik tom Wörden authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
test_entity_versioning.sql 13.86 KiB
/**
* This file is a part of the CaosDB Project.
*
* Copyright (C) 2020,2023 IndiScale GmbH <info@indiscale.com>
* Copyright (C) 2020,2023 Timm Fitschen <t.fitschen@indiscale.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
USE _caosdb_schema_unit_tests;
BEGIN;
CALL tap.no_plan();
-- #####################################################################
-- TEST insert_single_child_version, get_primary_parent_version and
-- delete_all_entity_versions in isolation
-- #####################################################################
-- SETUP
SET @EntityID1 = 10001;
SET @EntityID2 = 10002;
SET @EntityID3 = 10003;
SET @EntityID4 = 10004;
SET @EntityID5 = 10005;
SET @EntityID6 = 10006;
-- Disable versioning because we want to test `insert_single_child_version`
-- separately from `insertEntity` but the former is called inside the latter
-- when versioning is enabled.
DELETE FROM entity_version;
DELETE FROM transactions;
DELETE FROM feature_config WHERE _key = "ENTITY_VERSIONING";
INSERT INTO transactions (srid,seconds,nanos,username,realm) VALUES
("SRIDbla", 1234, 2345, "me", "home"),
("SRIDblub", 2345, 3465, "me", "home"),
("SRIDblieb", 3456, 4576, "you", "home");
DELETE FROM entities WHERE id > 99;
CALL entityACL(@ACLID1, "{acl1}");
CALL insertEntity(@EntityID1, "EntityName", "EntityDesc", "RECORDTYPE", "{acl1}");
SELECT entity_id INTO @InternalEntityID1 FROM name_data WHERE value="EntityName";
-- TEST insert_single_child_version
SELECT count(*) INTO @x FROM entity_version;
SELECT tap.eq(@x, 0, "no versions there yet");
CALL insert_single_child_version(@InternalEntityID1, "hashbla", "versionbla", NULL, "SRIDbla");
SELECT _ipparent INTO @x from entity_version WHERE version="versionbla";
SELECT tap.eq(@x, NULL, "no parent for the first version");
-- add a second version
SELECT count(*) INTO @x FROM entity_version;
SELECT tap.eq(@x, 1, "one version there already");
CALL insert_single_child_version(@InternalEntityID1, "hashblub", "versionblub", "versionbla", "SRIDblub");
SELECT _ipparent INTO @x from entity_version WHERE version="versionblub";
SELECT tap.eq(@x, 1, "the original entity is the parent");
-- error: parent does not exist
SELECT count(*) INTO @x FROM entity_version;
SELECT tap.eq(@x, 2, "two versions there already");
CALL tap._assert_throws(
concat("CALL insert_single_child_version(",
@InternalEntityID1, ', "hashblieb", "versionblieb", "non-existing-parent", "SRIDBlieb")'),
"non existing parent throws");
SELECT count(*) INTO @x FROM entity_version;
SELECT tap.eq(@x, 2, "still two versions there");
-- TEST get_primary_parent_version
SELECT tap.eq(get_primary_parent_version(@EntityID1, "versionblub"), "versionbla", "returns correct parent for versionblub");
SELECT tap.eq(get_primary_parent_version(@EntityID1, "versionbla"), NULL, "versionbla has no parent");
-- Reactivate when versioning's FORGET is being implemented
-- TEST delete_all_entity_versions
SELECT count(*) INTO @x FROM entity_version;
SELECT tap.ok(@x > 0, "several versions in the table");
-- CALL delete_all_entity_versions(@EntityID1);
DELETE FROM entity_version WHERE entity_id = @InternalEntityID1;
SELECT count(*) INTO @x FROM entity_version;
SELECT tap.eq(@x, 0, "no versions there any more");
-- TEARDOWN clean up
DELETE FROM name_data WHERE entity_id > 99;
DELETE FROM entity_ids WHERE internal_id > 99;
DELETE FROM entities WHERE id > 99;
-- #####################################################################
-- TEST the call of insert_single_child_version from within insertEntity
-- #####################################################################
-- SETUP switch on the feature
INSERT INTO feature_config (_key, _value) VALUES ("ENTITY_VERSIONING", "ENABLED");
SELECT count(*) INTO @x FROM entity_version;
SELECT tap.eq(@x, 0, "before insertEntity, no versions there");
-- TEST insertEntity - should produce a version w/o parent
SET @SRID = "SRIDbla";
CALL insertEntity(@EntityID2, "EntityName", "EntityDesc", "RECORDTYPE", "{acl1}");
SELECT entity_id INTO @InternalEntityID2 FROM name_data WHERE value="EntityName";
CALL insertEntity(@EntityID3, "ParentName", "ParentDesc", "RECORDTYPE", "{acl1}");
SELECT entity_id INTO @ParentID FROM name_data WHERE value="ParentName";
CALL insertIsa(@EntityID2, @EntityID3);
CALL insertEntityProperty(0, @EntityID2, 17, "null_data", NULL, NULL,
"RECOMMENDED", NULL, "DescOverride", NULL, NULL, 0);
SELECT count(*) INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2;
SELECT tap.eq(@x, 1, "after insertEntity, a version is there.");
SELECT _iversion INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2 and _ipparent is NULL;
SELECT tap.eq(@x, 1, "after insertEntity, the _iversion number is 1.");
SELECT _ipparent INTO @x from entity_version WHERE entity_id = @InternalEntityID2;
SELECT tap.eq(@x, NULL, "no parent for the freshly inserted entity");
SELECT tap.eq(count(*), 0, "no entity in archive_entities before first update")
FROM archive_entities;
-- TEST updateEntity - should produce a version with a parent
SET @SRID = "SRIDblub";
CALL deleteEntityProperties(@EntityID2);
CALL updateEntity(@EntityID2, "NewEntityName", "NewEntityDesc", "RECORD", NULL, NULL, "{acl1}");
SELECT count(*) INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2;
SELECT tap.eq(@x, 2, "after updateEntity, a second version is there.");
SELECT _iversion INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2 and _ipparent = 1;
SELECT tap.eq(@x, 2, "after updateEntity, the _iversion number incremented.");
SELECT _ipparent INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2 and _ipparent = 1;
SELECT tap.eq(@x, 1, "after updateEntity, the _pparent points to the first version");
SELECT tap.eq(count(*), 1, "after updateEntity, one entity in archive_entities")
FROM archive_entities;
-- TEST get_version_history
CALL get_version_history(@EntityID2);
-- TEST retrieveEntity
SELECT version INTO @x FROM entity_version
WHERE entity_id = @InternalEntityID2
AND _iversion = 2;
CALL retrieveEntity(@EntityID2, NULL);
CALL retrieveEntity(@EntityID2, "non-existing-version");
CALL retrieveEntity(@EntityID2, get_head_version(@EntityID2));
CALL retrieveEntity(@EntityID2, @x);
-- TEST retrieveEntityParents
CALL retrieveEntityParents(@EntityID2, NULL);
CALL retrieveEntityParents(@EntityID2, "non-existing-version");
CALL retrieveEntityParents(@EntityID2, get_head_version(@EntityID2));
CALL retrieveEntityParents(@EntityID2, @x);
-- TEST retrieveEntityProperties
CALL retrieveEntityProperties(0, @EntityID2, NULL);
CALL retrieveEntityProperties(0, @EntityID2, "non-existing-version");
CALL retrieveEntityProperties(0, @EntityID2, get_head_version(@EntityID2));
CALL retrieveEntityProperties(0, @EntityID2, @x);
-- TEST retrieveOverrides
CALL retrieveOverrides(0, @EntityID2, NULL);
CALL retrieveOverrides(0, @EntityID2, "non-existing-version");
CALL retrieveOverrides(0, @EntityID2, get_head_version(@EntityID2));
CALL retrieveOverrides(0, @EntityID2, @x);
-- and 2nd updateEntity
SET @SRID = "SRIDblieb";
CALL updateEntity(@EntityID2, "EntityName", "EntityDesc", "RECORDTYPE", NULL, NULL, "{acl1}");
SELECT count(*) INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2;
SELECT tap.eq(@x, 3, "after 2nd updateEntity, a 3rd version is there.");
SELECT _iversion INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2 and _ipparent = 2;
SELECT tap.eq(@x, 3, "after 2nd updateEntity, the _iversion number incremented again.");
SELECT _ipparent INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2 and _iversion = 3;
SELECT tap.eq(@x, 2, "after 2nd updateEntity, the _pparent points to the 2nd version");
SELECT tap.eq("SRIDblieb", srid, "correct transaction was stored") FROM entity_version WHERE entity_id = @InternalEntityID2 AND _ipparent = 2;
-- TEST deleteEntity - should remove all versions
CALL deleteIsa(@EntityID3);
CALL deleteEntity(@EntityID2);
SELECT count(*) INTO @x FROM entity_version WHERE entity_id = @InternalEntityID2;
SELECT tap.eq(@x, 0, "no versions there any more");
CALL deleteEntity(@EntityID3);
CALL tap.finish();
ROLLBACK;
-- TEST _fix_unversioned
-- turn off version, insert entity, turn on version, call _fix_unversioned
-- The inserted entity should have no entry in entity_version at first and
-- after _fix_unversioned an entry is gerated.
BEGIN;
CALL tap.no_plan();
-- setup
DELETE FROM entity_version;
DELETE FROM transactions;
-- insert entity without versioning
DELETE FROM feature_config WHERE _key = "ENTITY_VERSIONING";
CALL entityACL(@ACLID1, "{acl1}");
CALL insertEntity(@EntityID4, "EntityName", "EntityDesc", "RECORDTYPE", "{acl1}");
SELECT count(*) INTO @NumOfEntities FROM entities;
SELECT entity_id INTO @InternalEntityID4 FROM name_data WHERE value="EntityName";
SET @TheUser = "TheUser"; -- used to identify the matching entry in transaction_log
-- fill transaction_log: one entity with two updates (and one insert) and another entity with insert and delete.
INSERT INTO transaction_log (transaction, entity_id, username, realm, seconds, nanos)
-- the first entry is the one which is be found by _fix_unversioned
VALUES
("Update", @InternalEntityID4, @TheUser, "CaosDB", 23458, 254),
-- the rest is dummy data
("Update", @InternalEntityID4, "OtherUser", "CaosDB", 2345, 633), -- not the latest transaction
("Insert", @InternalEntityID4, "OtherUser", "CaosDB", 245, 633), -- not the latest transaction
("Insert", @InternalEntityID4 + 1, @TheUser, "CaosDB", 2325, 653), -- not the right entity, inserted before our target
("Delete", @InternalEntityID4 + 1, @TheUser, "CaosDB", 232526, 653); -- not the right entity, deleted after our target
SELECT tap.eq(COUNT(*), 5, "five entries in transaction_log") FROM transaction_log;
SELECT tap.eq(COUNT(*), 0, "no versioned entity there") FROM entity_version;
SELECT tap.eq(COUNT(*), 0, "no transaction there") FROM transactions;
INSERT INTO feature_config (_key, _value) VALUES ("ENTITY_VERSIONING", "ENABLED");
SELECT tap.eq(COUNT(*), 0, "still, no versioned entity there") FROM entity_version;
SELECT tap.eq(COUNT(*), 0, "still, no transaction there") FROM transactions;
-- call _fix_unversioned
CALL _fix_unversioned();
SELECT tap.eq(COUNT(*), @NumOfEntities,
"after _fix_unversioned, one entry for our test entity in entity_version")
FROM entity_version;
SELECT tap.eq(COUNT(*), 2,
"after _fix_unversioned, one entry for our test entity in transactions, one for the standard entities.")
FROM transactions;
SELECT tap.eq(entity_id, @InternalEntityID4, "versioned entity has correct id") FROM entity_version WHERE entity_id > 99;
SELECT tap.ok(srid IS NOT NULL, "srid was generated and user/time matches entries from transaction_log")
FROM transactions AS t JOIN transaction_log AS l
ON (l.seconds = t.seconds AND l.nanos = t.nanos AND l.username = t.username AND l.realm = t.realm)
WHERE l.entity_id = @InternalEntityID4444 AND l.username = @TheUser;
CALL tap.finish();
ROLLBACK;
-- TEST _fix_unversioned 2
-- Nastier case: two entities. together inserted, one got updated afterwards.
-- Both should be in the entity_version table after execution _fix_unversioned.
BEGIN;
CALL tap.no_plan();
-- setup
DELETE FROM entity_version;
DELETE FROM transactions;
-- insert entity without versioning
DELETE FROM feature_config WHERE _key = "ENTITY_VERSIONING";
CALL entityACL(@ACLID1, "{acl1}");
CALL insertEntity(@EntityID5, "EntityName1", "EntityDesc1", "RECORDTYPE", "{acl1}");
CALL insertEntity(@EntityID6, "EntityName2", "EntityDesc2", "RECORDTYPE", "{acl1}");
SELECT count(*) INTO @NumOfEntities FROM entities;
SELECT entity_id INTO @InternalEntityID5 FROM name_data WHERE value="EntityName1";
SELECT entity_id INTO @InternalEntityID6 FROM name_data WHERE value="EntityName2";
INSERT INTO transaction_log (transaction, entity_id, username, realm, seconds,
nanos)
-- the first entry is the one which will be found by _fix_unversioned
VALUES ("INSERT", @InternalEntityID5, "User", "CaosDB", 10000, 250),
("INSERT", @InternalEntityID6, "User", "CaosDB", 10000, 250),
("UPDATE", @InternalEntityID6, "User", "CaosDB", 20000, 250);
SELECT tap.eq(COUNT(*), 3, "three entries in transaction_log") FROM transaction_log;
SELECT tap.eq(COUNT(*), 0, "no versioned entity there") FROM entity_version;
SELECT tap.eq(COUNT(*), 0, "no transaction there") FROM transactions;
INSERT INTO feature_config (_key, _value) VALUES ("ENTITY_VERSIONING", "ENABLED");
SELECT tap.eq(COUNT(*), 0, "still, no versioned entity there") FROM entity_version;
SELECT tap.eq(COUNT(*), 0, "still, no transaction there") FROM transactions;
-- call _fix_unversioned
CALL _fix_unversioned();
SELECT tap.eq(COUNT(*), @NumOfEntities,
"after _fix_unversioned, one entry for our test entity in entity_version")
FROM entity_version;
SELECT tap.eq(COUNT(*), 3,
"after _fix_unversioned, one entry for each of our test entities in
transactions, one for the standard entities.")
FROM transactions;
SELECT tap.eq(seconds, 10000, "version seconds of entity 1 is correct")
FROM entity_version AS v JOIN transactions AS t
ON (v.srid = t.srid) WHERE v.entity_id = @InternalEntityID1;
SELECT tap.eq(seconds, 20000, "version seconds of entity 2 is correct")
FROM entity_version AS v JOIN transactions AS t
ON (v.srid = t.srid) WHERE v.entity_id = @InternalEntityID2;
CALL tap.finish();
ROLLBACK;