[New] Allow creating elements with same ID, but different types

This commit is contained in:
Robert von Burg 2023-07-21 09:05:24 +02:00
parent d40c63032c
commit 14cafe3546
Signed by: eitch
GPG Key ID: 75DB9C85C74331F7
13 changed files with 512 additions and 45 deletions

View File

@ -99,7 +99,7 @@ public abstract class CachedElementMap<T extends StrolchRootElement> extends Tra
updateVersion(tx, element, false);
// first perform cached change
super.internalUpdate(tx, element);
super.internalUpdate(element);
// last is to perform DB changes
getDbDao(tx).update(element);
@ -111,7 +111,7 @@ public abstract class CachedElementMap<T extends StrolchRootElement> extends Tra
// first perform cached change
for (T t : elements) {
updateVersion(tx, t, false);
internalUpdate(tx, t);
internalUpdate(t);
}
// last is to perform DB changes
@ -261,7 +261,7 @@ public abstract class CachedElementMap<T extends StrolchRootElement> extends Tra
return null;
} else {
T previous = getBy(tx, type, id, elementVersion.getPreviousVersion(), true);
super.internalUpdate(tx, previous);
super.internalUpdate(previous);
getDbDao(tx).removeVersion(current);
return previous;
}

View File

@ -15,13 +15,6 @@
*/
package li.strolch.agent.impl;
import static li.strolch.model.StrolchModelConstants.TEMPLATE;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import li.strolch.agent.api.ElementMap;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.exception.StrolchElementNotFoundException;
@ -36,6 +29,13 @@ import li.strolch.persistence.api.StrolchTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static li.strolch.model.StrolchModelConstants.TEMPLATE;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@ -94,8 +94,7 @@ public abstract class TransientElementMap<T extends StrolchRootElement> implemen
if (t == null)
return null;
@SuppressWarnings("unchecked")
T clone = (T) t.getClone();
@SuppressWarnings("unchecked") T clone = (T) t.getClone();
clone.setId(StrolchAgent.getUniqueId());
clone.setType(type);
return clone;
@ -126,8 +125,7 @@ public abstract class TransientElementMap<T extends StrolchRootElement> implemen
if (tx.isReadOnly() && !type.equals(TEMPLATE))
return t;
@SuppressWarnings("unchecked")
T clone = (T) t.getClone(true);
@SuppressWarnings("unchecked") T clone = (T) t.getClone(true);
return clone;
}
@ -175,8 +173,7 @@ public abstract class TransientElementMap<T extends StrolchRootElement> implemen
return stream.collect(Collectors.toList());
return stream.map(t -> {
@SuppressWarnings("unchecked")
T clone = (T) t.getClone(true);
@SuppressWarnings("unchecked") T clone = (T) t.getClone(true);
return clone;
}).collect(Collectors.toList());
}
@ -191,8 +188,7 @@ public abstract class TransientElementMap<T extends StrolchRootElement> implemen
return new ArrayList<>(byType.values());
return byType.values().stream().map(t -> {
@SuppressWarnings("unchecked")
T clone = (T) t.getClone(true);
@SuppressWarnings("unchecked") T clone = (T) t.getClone(true);
return clone;
}).collect(Collectors.toList());
}
@ -252,8 +248,7 @@ public abstract class TransientElementMap<T extends StrolchRootElement> implemen
* Special method used when starting the container to cache the values. Not to be used anywhere else but from the
* {@link CachedRealm}
*
* @param elements
* the elements to insert
* @param elements the elements to insert
*/
synchronized void insertAll(List<T> elements) {
elements.forEach(this::internalInsert);
@ -264,8 +259,8 @@ public abstract class TransientElementMap<T extends StrolchRootElement> implemen
// assert no object already exists with this id
if (byType.containsKey(element.getId())) {
String msg = "An element already exists with the id \"{0}\". Elements of the same class must always have a unique id, regardless of their type!";
msg = MessageFormat.format(msg, element.getId());
String msg = "An element already exists with the id {0} and type {1}";
msg = MessageFormat.format(msg, element.getId(), element.getType());
throw new StrolchPersistenceException(msg);
}
@ -300,20 +295,22 @@ public abstract class TransientElementMap<T extends StrolchRootElement> implemen
public synchronized void update(StrolchTransaction tx, T element) {
element.setVersion(getBy(tx, element.getType(), element.getId(), true).getVersion());
Version.updateVersionFor(element, 0, tx.getUsername(), false);
internalUpdate(tx, element);
internalUpdate(element);
}
protected void internalUpdate(StrolchTransaction tx, T element) {
protected void internalUpdate(T element) {
Map<String, T> byType = this.elementMap.get(element.getType());
if (byType == null) {
String msg = "The element does not yet exist with the type \"{0}\" and id \"{1}\". Use add() for new objects!";
String msg
= "The element does not yet exist with the type \"{0}\" and id \"{1}\". Use add() for new objects!";
msg = MessageFormat.format(msg, element.getType(), element.getId());
throw new StrolchPersistenceException(msg);
}
// assert object already exists with this id
if (!byType.containsKey(element.getId())) {
String msg = "The element does not yet exist with the type \"{0}\" and id \"{1}\". Use add() for new objects!";
String msg
= "The element does not yet exist with the type \"{0}\" and id \"{1}\". Use add() for new objects!";
msg = MessageFormat.format(msg, element.getType(), element.getId());
throw new StrolchPersistenceException(msg);
}
@ -329,7 +326,7 @@ public abstract class TransientElementMap<T extends StrolchRootElement> implemen
for (T element : elements) {
element.setVersion(getBy(tx, element.getType(), element.getId(), true).getVersion());
Version.updateVersionFor(element, 0, tx.getUsername(), false);
internalUpdate(tx, element);
internalUpdate(element);
}
}

View File

@ -0,0 +1,5 @@
DROP TABLE IF EXISTS archive_resources;
DROP TABLE IF EXISTS archive_orders;
DROP TABLE IF EXISTS archive_activities;
DROP INDEX IF EXISTS ids_archive_orders_date;

View File

@ -0,0 +1,103 @@
-- ORDERS
CREATE TABLE IF NOT EXISTS archive_orders (
id varchar(255) not null,
version integer not null,
created_by varchar(255) not null,
created_at timestamp with time zone not null,
updated_at timestamp with time zone not null,
deleted boolean,
latest boolean not null,
name varchar(255),
type varchar(255),
state order_state,
date timestamp with time zone,
asxml xml,
asjson json,
PRIMARY KEY (type, id, version)
);
DROP INDEX IF EXISTS ids_archive_orders_date;
CREATE INDEX ids_archive_orders_date
ON archive_orders (date NULLS LAST)
;
-- RESOURCES
CREATE TABLE IF NOT EXISTS archive_resources (
id varchar(255) not null,
version integer not null,
created_by varchar(255) not null,
created_at timestamp with time zone not null,
updated_at timestamp with time zone not null,
deleted boolean not null,
latest boolean not null,
name varchar(255) not null,
type varchar(255) not null,
asxml xml,
asjson json,
PRIMARY KEY (type, id, version)
);
-- ACTIVITIES
CREATE TABLE IF NOT EXISTS archive_activities (
id varchar(255) not null,
version integer not null,
created_by varchar(255) not null,
created_at timestamp with time zone not null,
updated_at timestamp with time zone not null,
deleted boolean not null,
latest boolean not null,
name varchar(255) not null,
type varchar(255) not null,
state order_state,
asxml xml,
asjson json,
PRIMARY KEY (type, id, version)
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.1.0',
'archive',
'Initial schema version',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.2.0',
'archive',
'Added updated_at column',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.3.0',
'archive',
'Added resources and activities',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.3.1',
'archive',
'create index on date archive_orders table',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.3.2',
'archive',
'add type column to primary key',
CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,18 @@
-- update primary keys
alter table archive_resources drop constraint archive_resources_pkey;
alter table archive_orders drop constraint archive_orders_pkey;
alter table archive_activities drop constraint archive_activities_pkey;
alter table archive_resources add constraint archive_resources_pkey primary key (type, id, version);
alter table archive_orders add constraint archive_orders_pkey primary key (type, id, version);
alter table archive_activities add constraint archive_activities_pkey primary key (type, id, version);
INSERT INTO db_version
(version, app, description, created)
values(
'0.3.2',
'archive',
'add type column to primary key',
CURRENT_TIMESTAMP
);

View File

@ -1,2 +1,2 @@
# Property file defining what the currently expected version is supposed to be
db_version=0.3.1
db_version=0.3.2

View File

@ -0,0 +1,18 @@
DROP TABLE IF EXISTS resources;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS activities;
DROP TABLE IF EXISTS audits;
DROP TABLE IF EXISTS operations_log;
DROP TABLE IF EXISTS operations_log_values;
DROP TABLE IF EXISTS db_version;
DROP TYPE IF EXISTS order_state;
DROP TYPE IF EXISTS access_type;
DROP TYPE IF EXISTS log_severity_type;
DROP TYPE IF EXISTS log_state_type;
DROP INDEX IF EXISTS ids_orders_date;

View File

@ -0,0 +1,235 @@
-- DB_VERSION
CREATE TABLE IF NOT EXISTS db_version (
id serial primary key not null,
app varchar(255) not null,
version varchar(255) not null,
description varchar(255) not null,
created timestamp with time zone not null
);
-- RESOURCES
CREATE TABLE IF NOT EXISTS resources (
id varchar(255) not null,
version integer not null,
created_by varchar(255) not null,
created_at timestamp with time zone not null,
updated_at timestamp with time zone not null,
deleted boolean not null,
latest boolean not null,
name varchar(255) not null,
type varchar(255) not null,
asxml xml,
asjson json,
PRIMARY KEY (type, id, version)
);
-- ORDERS
CREATE TYPE order_state AS ENUM ('CREATED', 'PLANNING', 'PLANNED', 'EXECUTION', 'STOPPED', 'WARNING', 'ERROR', 'EXECUTED', 'CLOSED');
CREATE TABLE IF NOT EXISTS orders (
id varchar(255) not null,
version integer not null,
created_by varchar(255) not null,
created_at timestamp with time zone not null,
updated_at timestamp with time zone not null,
deleted boolean,
latest boolean not null,
name varchar(255),
type varchar(255),
state order_state,
date timestamp with time zone,
asxml xml,
asjson json,
PRIMARY KEY (type, id, version)
);
DROP INDEX IF EXISTS ids_orders_date;
CREATE INDEX ids_orders_date
ON orders (date NULLS LAST)
;
-- ACTIVITIES
CREATE TABLE IF NOT EXISTS activities (
id varchar(255) not null,
version integer not null,
created_by varchar(255) not null,
created_at timestamp with time zone not null,
updated_at timestamp with time zone not null,
deleted boolean not null,
latest boolean not null,
name varchar(255) not null,
type varchar(255) not null,
state order_state,
asxml xml,
asjson json,
PRIMARY KEY (type, id, version)
);
-- AUDITS
CREATE TYPE access_type AS ENUM ('READ', 'CREATE', 'UPDATE', 'DELETE');
CREATE TABLE IF NOT EXISTS audits (
id bigint PRIMARY KEY,
username varchar(255) NOT NULL,
firstname varchar(255) NOT NULL,
lastname varchar(255) NOT NULL,
date timestamp with time zone NOT NULL,
element_type varchar(255) NOT NULL,
element_sub_type varchar(255) NOT NULL,
element_accessed varchar(255) NOT NULL,
new_version timestamp with time zone,
action varchar(255) NOT NULL,
access_type access_type NOT NULL
);
-- Operations Log
CREATE TYPE log_severity_type AS ENUM ('Info', 'Notification', 'Warning', 'Error', 'Exception');
CREATE TYPE log_state_type AS ENUM ('Active', 'Inactive', 'Information');
CREATE TABLE IF NOT EXISTS operations_log (
id varchar(255) PRIMARY KEY,
realm varchar(255),
dateTime timestamp with time zone,
username varchar(255),
severity log_severity_type,
state log_state_type,
locator varchar(1024),
bundle varchar(255),
key varchar(255),
message text,
stacktrace text
);
CREATE TABLE IF NOT EXISTS operations_log_values (
id varchar(255),
key varchar(255),
value text
);
-- set version
INSERT INTO db_version
(version, app, description, created)
values(
'0.1.0',
'strolch',
'Initial schema version',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.2.0',
'strolch',
'Added new table for audits',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.2.1',
'strolch',
'Added new column app to table table version',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.3.0',
'strolch',
'Added new column element_sub_type to table audits',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.4.0',
'strolch',
'Added new table activities',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.5.0',
'strolch',
'Added versioning to root elements',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.5.1',
'strolch',
'Added state column to activity, and added new states',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.6.0',
'strolch',
'Added json column to all tables',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.7.0',
'strolch',
'Added persisting of operations log',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.8.0',
'strolch',
'Added updated_at column to all tables',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.9.0',
'strolch',
'Added log_state column to operations_log',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.9.1',
'strolch',
'Added bundle column to operations_log',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.9.2',
'strolch',
'create index on date orders table',
CURRENT_TIMESTAMP
);
INSERT INTO db_version
(version, app, description, created)
values(
'0.9.3',
'strolch',
'add type column to primary key',
CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,18 @@
-- update primary keys
alter table resources drop constraint resources_pkey;
alter table orders drop constraint orders_pkey;
alter table activities drop constraint activities_pkey;
alter table resources add constraint resources_pkey primary key (type, id, version);
alter table orders add constraint orders_pkey primary key (type, id, version);
alter table activities add constraint activities_pkey primary key (type, id, version);
INSERT INTO db_version
(version, app, description, created)
values(
'0.9.3',
'strolch',
'add type column to primary key',
CURRENT_TIMESTAMP
);

View File

@ -1,2 +1,2 @@
# Property file defining what the currently expected version is supposed to be
db_version=0.9.2
db_version=0.9.3

View File

@ -15,11 +15,6 @@
*/
package li.strolch.testbase.runtime;
import static li.strolch.model.ModelGenerator.*;
import static org.junit.Assert.*;
import java.util.*;
import li.strolch.agent.api.ActivityMap;
import li.strolch.agent.impl.DataStoreMode;
import li.strolch.model.AbstractStrolchElement;
@ -30,6 +25,11 @@ import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.Certificate;
import li.strolch.runtime.privilege.PrivilegeHandler;
import java.util.*;
import static li.strolch.model.ModelGenerator.*;
import static org.junit.Assert.*;
@SuppressWarnings("nls")
public class ActivityModelTestRunner {
@ -158,6 +158,30 @@ public class ActivityModelTestRunner {
Activity activity = tx.getActivityBy(TYPE, ID);
assertNull("Should not read Activity with id " + ID, activity);
}
// create with same ID, but different types
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Activity act = createActivity("non-unique-id", "NonUnique1", "NonUnique1", TimeOrdering.SERIES);
tx.add(act);
tx.commitOnClose();
}
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Activity act = createActivity("non-unique-id", "NonUnique2", "NonUnique2", TimeOrdering.SERIES);
tx.add(act);
tx.commitOnClose();
}
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Activity act1 = tx.getActivityBy("NonUnique1", "non-unique-id");
Activity act2 = tx.getActivityBy("NonUnique2", "non-unique-id");
assertNotNull(act1);
assertNotNull(act2);
assertEquals("NonUnique1", act1.getName());
assertEquals("NonUnique2", act2.getName());
}
}
public void runBulkOperationTests() {
@ -165,10 +189,10 @@ public class ActivityModelTestRunner {
// create 15 activities
List<Activity> activities = new ArrayList<>();
activities.addAll(createActivities(0, 5, "@", "My Activity", "MyType1", TimeOrdering.SERIES));
activities
.addAll(createActivities(activities.size(), 5, "@", "Other Activity", "MyType2", TimeOrdering.SERIES));
activities.addAll(createActivities(activities.size(), 5, "@", "Further Activity", "MyType3",
TimeOrdering.SERIES));
activities.addAll(
createActivities(activities.size(), 5, "@", "Other Activity", "MyType2", TimeOrdering.SERIES));
activities.addAll(
createActivities(activities.size(), 5, "@", "Further Activity", "MyType3", TimeOrdering.SERIES));
// sort them so we know which activity our objects are
Comparator<Activity> comparator = Comparator.comparing(AbstractStrolchElement::getId);

View File

@ -15,12 +15,6 @@
*/
package li.strolch.testbase.runtime;
import static li.strolch.model.ModelGenerator.*;
import static org.junit.Assert.*;
import java.time.LocalDate;
import java.util.*;
import li.strolch.agent.api.OrderMap;
import li.strolch.agent.impl.DataStoreMode;
import li.strolch.model.Order;
@ -33,6 +27,12 @@ import li.strolch.privilege.model.Certificate;
import li.strolch.runtime.privilege.PrivilegeHandler;
import li.strolch.utils.collections.DateRange;
import java.time.LocalDate;
import java.util.*;
import static li.strolch.model.ModelGenerator.*;
import static org.junit.Assert.*;
@SuppressWarnings("nls")
public class OrderModelTestRunner {
@ -149,7 +149,7 @@ public class OrderModelTestRunner {
assertEquals("Expect 1 Orders from _20190501 inc to _2020 inc", 1, size);
DateRange dateRange = new DateRange().from(_2017, true).to(_20190401, true);
String[] types = { "QTestType1", "QTestType2", "QTestType3" };
String[] types = {"QTestType1", "QTestType2", "QTestType3"};
size = dao.querySize(dateRange, types);
assertEquals("Expect 2 Orders from _2017 inc to _20190401 inc by types", 2, size);
@ -223,6 +223,30 @@ public class OrderModelTestRunner {
Order order = tx.getOrderBy(TYPE, ID);
assertNull("Should not read Order with id " + ID, order);
}
// create with same ID, but different types
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Order order = createOrder("non-unique-id", "NonUnique1", "NonUnique1");
tx.add(order);
tx.commitOnClose();
}
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Order order = createOrder("non-unique-id", "NonUnique2", "NonUnique2");
tx.add(order);
tx.commitOnClose();
}
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Order order1 = tx.getOrderBy("NonUnique1", "non-unique-id");
Order order2 = tx.getOrderBy("NonUnique2", "non-unique-id");
assertNotNull(order1);
assertNotNull(order2);
assertEquals("NonUnique1", order1.getName());
assertEquals("NonUnique2", order2.getName());
}
}
public void runBulkOperationTests() {

View File

@ -148,6 +148,31 @@ public class ResourceModelTestRunner {
Resource resource = tx.getResourceBy(TYPE, ID);
assertNull("Should not read Resource with id " + ID, resource);
}
// create with same ID, but different types
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Resource res = createResource("non-unique-id", "NonUnique1", "NonUnique1");
tx.add(res);
tx.commitOnClose();
}
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Resource res = createResource("non-unique-id", "NonUnique2", "NonUnique2");
tx.add(res);
tx.commitOnClose();
}
try (StrolchTransaction tx = this.runtimeMock.getRealm(this.realmName)
.openTx(this.certificate, "test", false)) {
Resource res1 = tx.getResourceBy("NonUnique1", "non-unique-id");
Resource res2 = tx.getResourceBy("NonUnique2", "non-unique-id");
assertNotNull(res1);
assertNotNull(res2);
assertEquals("NonUnique1", res1.getName());
assertEquals("NonUnique2", res2.getName());
}
}
public void runBulkOperationTests() {