diff --git a/javers-core/src/main/java/org/javers/core/json/typeadapter/commit/CdoSnapshotTypeAdapter.java b/javers-core/src/main/java/org/javers/core/json/typeadapter/commit/CdoSnapshotTypeAdapter.java index f813418de..da3680acd 100644 --- a/javers-core/src/main/java/org/javers/core/json/typeadapter/commit/CdoSnapshotTypeAdapter.java +++ b/javers-core/src/main/java/org/javers/core/json/typeadapter/commit/CdoSnapshotTypeAdapter.java @@ -26,6 +26,7 @@ class CdoSnapshotTypeAdapter extends JsonTypeAdapterTemplate { static final String INITIAL_NAME_LEGACY = "initial"; static final String TYPE_NAME = "type"; static final String CHANGED_NAME = "changedProperties"; + static final String VERSION = "version"; private TypeMapper typeMapper; @@ -44,6 +45,7 @@ public CdoSnapshot fromJson(JsonElement json, JsonDeserializationContext context JsonObject stateObject = (JsonObject)jsonObject.get(STATE_NAME); GlobalId cdoId = context.deserialize(jsonObject.get(GLOBAL_CDO_ID), GlobalId.class); + Long version = context.deserialize(jsonObject.get(VERSION), Long.class); DuckType duckType = new DuckType(cdoId.getTypeName(), extractPropertyNames(stateObject)); ManagedType managedType = typeMapper.getJaversManagedType(duckType, ManagedType.class); @@ -57,6 +59,7 @@ public CdoSnapshot fromJson(JsonElement json, JsonDeserializationContext context return builder .withState(snapshotState) + .withVersion(version) .withCommitMetadata(commitMetadata) .withChangedProperties(changedProperties) .build(); @@ -106,6 +109,7 @@ public JsonElement toJson(CdoSnapshot snapshot, JsonSerializationContext context jsonObject.add(STATE_NAME, context.serialize(snapshot.getState())); jsonObject.add(CHANGED_NAME, context.serialize(snapshot.getChanged())); jsonObject.add(TYPE_NAME, context.serialize(snapshot.getType().name())); + jsonObject.add(VERSION, context.serialize(snapshot.getVersion())); return jsonObject; } diff --git a/javers-core/src/main/java/org/javers/core/metamodel/object/CdoSnapshot.java b/javers-core/src/main/java/org/javers/core/metamodel/object/CdoSnapshot.java index 745aceea6..5578d90f0 100644 --- a/javers-core/src/main/java/org/javers/core/metamodel/object/CdoSnapshot.java +++ b/javers-core/src/main/java/org/javers/core/metamodel/object/CdoSnapshot.java @@ -26,6 +26,7 @@ public final class CdoSnapshot extends Cdo { private final CdoSnapshotState state; private final SnapshotType type; private final List changed; + private final long version; /** * should be assembled by {@link CdoSnapshotBuilder} @@ -35,13 +36,15 @@ public final class CdoSnapshot extends Cdo { CdoSnapshotState state, SnapshotType type, List changed, - ManagedType managedType) { + ManagedType managedType, + long version) { super(globalId, managedType); Validate.argumentsAreNotNull(state, commitMetadata, type, managedType); this.state = state; this.commitMetadata = commitMetadata; this.type = type; this.changed = changed; + this.version = version; } /** @@ -121,4 +124,14 @@ public boolean isTerminal() { public SnapshotType getType() { return type; } + + /** + * Object version number.
+ * Initial snapshot of given object has version == 1, next has version == 2. + * + * @since 1.4.3 + */ + public long getVersion() { + return version; + } } diff --git a/javers-core/src/main/java/org/javers/core/metamodel/object/CdoSnapshotBuilder.java b/javers-core/src/main/java/org/javers/core/metamodel/object/CdoSnapshotBuilder.java index 4e85002b3..4941b0188 100644 --- a/javers-core/src/main/java/org/javers/core/metamodel/object/CdoSnapshotBuilder.java +++ b/javers-core/src/main/java/org/javers/core/metamodel/object/CdoSnapshotBuilder.java @@ -26,6 +26,7 @@ public class CdoSnapshotBuilder { private boolean markAllAsChanged; private List changed = Collections.EMPTY_LIST; private ManagedType managedType; + private long version = 1; private CdoSnapshotBuilder(GlobalId globalId, ManagedType managedType) { Validate.argumentsAreNotNull(globalId, managedType); @@ -57,20 +58,26 @@ public CdoSnapshotBuilder withState(CdoSnapshotState state) { return this; } + public CdoSnapshotBuilder withVersion(Long version) { + this.version = (version == null) ? this.version : version; + return this; + } + public CdoSnapshot build() { if (state == null) { state = stateBuilder.build(); } - if (previous != null){ + if (previous != null) { changed = state.differentValues(previous.getState()); + version = previous.getVersion() + 1; } if (markAllAsChanged){ changed = new ArrayList<>(state.getProperties()); } - return new CdoSnapshot(globalId, commitMetadata, state, type, changed, managedType); + return new CdoSnapshot(globalId, commitMetadata, state, type, changed, managedType, version); } public CdoSnapshotBuilder withType(SnapshotType type) { diff --git a/javers-core/src/test/groovy/org/javers/core/JaversRepositoryE2ETest.groovy b/javers-core/src/test/groovy/org/javers/core/JaversRepositoryE2ETest.groovy index 686f7c412..e0cf1fdc4 100644 --- a/javers-core/src/test/groovy/org/javers/core/JaversRepositoryE2ETest.groovy +++ b/javers-core/src/test/groovy/org/javers/core/JaversRepositoryE2ETest.groovy @@ -601,4 +601,16 @@ class JaversRepositoryE2ETest extends Specification { (4..2).collect{new LocalDateTime(2015,12,1,it,0)} ] } + + def "should increment Entity snapshot version number"(){ + when: + javers.commit("author", new SnapshotEntity(id: 1, intProperty: 1)) + javers.commit("author", new SnapshotEntity(id: 1, intProperty: 2)) + + then: + def snapshots = javers.findSnapshots(byInstanceId(1, SnapshotEntity).build()) + snapshots[0].version == 2 + snapshots[1].version == 1 + + } } \ No newline at end of file diff --git a/javers-core/src/test/groovy/org/javers/core/json/typeadapter/CdoSnapshotTypeAdapterTest.groovy b/javers-core/src/test/groovy/org/javers/core/json/typeadapter/CdoSnapshotTypeAdapterTest.groovy index 95e95b4c0..3a9d38b1f 100644 --- a/javers-core/src/test/groovy/org/javers/core/json/typeadapter/CdoSnapshotTypeAdapterTest.groovy +++ b/javers-core/src/test/groovy/org/javers/core/json/typeadapter/CdoSnapshotTypeAdapterTest.groovy @@ -5,9 +5,9 @@ import groovy.json.JsonSlurper import org.javers.core.commit.CommitId import org.javers.core.commit.CommitMetadata import org.javers.core.metamodel.object.CdoSnapshot -import org.javers.repository.jql.ValueObjectIdDTO import org.javers.core.model.DummyUser import org.javers.core.model.DummyUserDetails +import org.javers.repository.jql.ValueObjectIdDTO import org.javers.test.builder.DummyUserDetailsBuilder import org.joda.time.LocalDateTime import spock.lang.Specification @@ -29,7 +29,6 @@ class CdoSnapshotTypeAdapterTest extends Specification { when: def jsonText = javers.jsonConverter.toJson(snapshot) - println jsonText then: def json = new JsonSlurper().parseText(jsonText) @@ -41,6 +40,7 @@ class CdoSnapshotTypeAdapterTest extends Specification { json.globalId.entity == "org.javers.core.model.DummyUser" json.globalId.cdoId == "kaz" json.type == "INITIAL" + json.version == 1 } def "should serialize state with primitive values in CdoSnapshot"() { @@ -148,8 +148,8 @@ class CdoSnapshotTypeAdapterTest extends Specification { state { } changedProperties changed + version 5 } - println json.toString() when: def snapshot = javersTestAssembly().jsonConverter.fromJson(json.toString(), CdoSnapshot) @@ -159,6 +159,32 @@ class CdoSnapshotTypeAdapterTest extends Specification { snapshot.globalId == instanceId("kaz",DummyUser) snapshot.initial == true snapshot.changed.collect{it} as Set == ["name", "age"] as Set + snapshot.version == 5L + } + + def "should deserialize CdoSnapshot.version to 1 when version field is missing"() { + + given: + def json = new JsonBuilder() + json { + commitMetadata { + commitId "1.0" + author "author" + dateTime "2000-01-01T12:00:00" + } + globalId { + entity "org.javers.core.model.DummyUser" + cdoId "kaz" + } + state { + } + } + + when: + def snapshot = javersTestAssembly().jsonConverter.fromJson(json.toString(), CdoSnapshot) + + then: + snapshot.version == 1 } def "should deserialize CdoSnapshot state with primitive values"() { diff --git a/javers-core/src/test/groovy/org/javers/core/snapshot/SnapshotFactoryTest.groovy b/javers-core/src/test/groovy/org/javers/core/snapshot/SnapshotFactoryTest.groovy index a3a6018d5..7bdc74baa 100644 --- a/javers-core/src/test/groovy/org/javers/core/snapshot/SnapshotFactoryTest.groovy +++ b/javers-core/src/test/groovy/org/javers/core/snapshot/SnapshotFactoryTest.groovy @@ -59,6 +59,29 @@ class SnapshotFactoryTest extends Specification{ updateSnapshot.changed == ["arrayOfIntegers"] } + def "should update the version of the snapshot"() { + given: + def cdoWrapper = javers.createCdoWrapper(new SnapshotEntity(id: 1)) + def prevSnapshot = snapshotFactory.createInitial(cdoWrapper, someCommitMetadata()) + + when: + def updateSnapshot = snapshotFactory.createUpdate(cdoWrapper, prevSnapshot, someCommitMetadata()) + + then: + updateSnapshot.version == 2L + } + + def "should initialize the version of the first snapshot"() { + given: + def cdoWrapper = javers.createCdoWrapper(new SnapshotEntity(id: 1)) + + when: + def snapshot = snapshotFactory.createInitial(cdoWrapper, someCommitMetadata()) + + then: + snapshot.version == 1L + } + def "should mark changed and added properties of update snapshot"() { given: def ref = new SnapshotEntity(id:2) diff --git a/javers-persistence-sql/src/main/java/org/javers/repository/sql/finders/CdoSnapshotObjectMapper.java b/javers-persistence-sql/src/main/java/org/javers/repository/sql/finders/CdoSnapshotObjectMapper.java index bb0e772fd..11dfaa462 100644 --- a/javers-persistence-sql/src/main/java/org/javers/repository/sql/finders/CdoSnapshotObjectMapper.java +++ b/javers-persistence-sql/src/main/java/org/javers/repository/sql/finders/CdoSnapshotObjectMapper.java @@ -27,6 +27,7 @@ class CdoSnapshotObjectMapper implements ObjectMapper { private static final String STATE_NAME = "state"; private static final String TYPE_NAME = "type"; private static final String CHANGED_NAME = "changedProperties"; + private static final String VERSION = "version"; public CdoSnapshotObjectMapper(JsonConverter jsonConverter, Optional providedGlobalId) { @@ -42,6 +43,7 @@ public CdoSnapshot createObject(ResultSet resultSet) throws SQLException { json.add(STATE_NAME, jsonConverter.fromJsonToJsonElement(resultSet.getString(SNAPSHOT_STATE))); json.add(CHANGED_NAME, assembleChangedPropNames(resultSet)); json.addProperty(TYPE_NAME, resultSet.getString(SNAPSHOT_TYPE)); + json.addProperty(VERSION, resultSet.getLong(VERSION)); if (providedGlobalId.isPresent()){ json.add(GLOBAL_CDO_ID, jsonConverter.toJsonElement(providedGlobalId.get())); diff --git a/javers-persistence-sql/src/main/java/org/javers/repository/sql/finders/SnapshotFilter.java b/javers-persistence-sql/src/main/java/org/javers/repository/sql/finders/SnapshotFilter.java index 1f314f37a..bc94ff22d 100644 --- a/javers-persistence-sql/src/main/java/org/javers/repository/sql/finders/SnapshotFilter.java +++ b/javers-persistence-sql/src/main/java/org/javers/repository/sql/finders/SnapshotFilter.java @@ -21,6 +21,7 @@ abstract class SnapshotFilter { static final String BASE_FIELDS = SNAPSHOT_STATE + ", " + SNAPSHOT_TYPE + ", " + + VERSION + ", " + SNAPSHOT_CHANGED + ", " + COMMIT_AUTHOR + ", " + COMMIT_COMMIT_DATE + ", " + diff --git a/javers-persistence-sql/src/main/java/org/javers/repository/sql/repositories/CdoSnapshotRepository.java b/javers-persistence-sql/src/main/java/org/javers/repository/sql/repositories/CdoSnapshotRepository.java index d03bd35ba..13184132c 100644 --- a/javers-persistence-sql/src/main/java/org/javers/repository/sql/repositories/CdoSnapshotRepository.java +++ b/javers-persistence-sql/src/main/java/org/javers/repository/sql/repositories/CdoSnapshotRepository.java @@ -31,6 +31,7 @@ public void save(long commitIdPk, List cdoSnapshots) { private long insertSnapshot(long globalIdPk, long commitIdPk, CdoSnapshot cdoSnapshot) { InsertQuery query = javersPolyJDBC.query().insert().into(SNAPSHOT_TABLE_NAME) .value(SNAPSHOT_TYPE, cdoSnapshot.getType().toString()) + .value(VERSION, cdoSnapshot.getVersion()) .value(SNAPSHOT_GLOBAL_ID_FK, globalIdPk) .value(SNAPSHOT_COMMIT_FK, commitIdPk) .value(SNAPSHOT_STATE, jsonConverter.toJson(cdoSnapshot.getState())) diff --git a/javers-persistence-sql/src/main/java/org/javers/repository/sql/schema/FixedSchemaFactory.java b/javers-persistence-sql/src/main/java/org/javers/repository/sql/schema/FixedSchemaFactory.java index 28144e406..46628fe73 100644 --- a/javers-persistence-sql/src/main/java/org/javers/repository/sql/schema/FixedSchemaFactory.java +++ b/javers-persistence-sql/src/main/java/org/javers/repository/sql/schema/FixedSchemaFactory.java @@ -39,6 +39,7 @@ public class FixedSchemaFactory { public static final String SNAPSHOT_COMMIT_FK = "commit_fk"; public static final String SNAPSHOT_GLOBAL_ID_FK = "global_id_fk"; public static final String SNAPSHOT_TYPE = "type"; + public static final String VERSION = "version"; public static final String SNAPSHOT_TABLE_PK_SEQ = "jv_snapshot_pk_seq"; public static final String SNAPSHOT_STATE = "state"; public static final String SNAPSHOT_CHANGED = "changed_properties"; //since v.1.2 @@ -48,6 +49,7 @@ private Schema snapshotTableSchema(Dialect dialect, String tableName){ RelationBuilder relationBuilder = schema.addRelation(tableName); primaryKey(tableName, SNAPSHOT_PK, schema, relationBuilder); relationBuilder.withAttribute().string(SNAPSHOT_TYPE).withMaxLength(200).and() + .withAttribute().longAttr(VERSION).and() .withAttribute().text(SNAPSHOT_STATE).and() .withAttribute().text(SNAPSHOT_CHANGED).and(); foreignKey(tableName, SNAPSHOT_GLOBAL_ID_FK, GLOBAL_ID_TABLE_NAME, GLOBAL_ID_PK, relationBuilder, schema); diff --git a/javers-persistence-sql/src/main/java/org/javers/repository/sql/schema/JaversSchemaManager.java b/javers-persistence-sql/src/main/java/org/javers/repository/sql/schema/JaversSchemaManager.java index 597514148..11d1e5257 100644 --- a/javers-persistence-sql/src/main/java/org/javers/repository/sql/schema/JaversSchemaManager.java +++ b/javers-persistence-sql/src/main/java/org/javers/repository/sql/schema/JaversSchemaManager.java @@ -10,10 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Types; +import java.sql.*; import java.util.Map; /** @@ -45,6 +42,7 @@ public void ensureSchema() { } alterCommitIdColumnIfNeeded(); + addSnapshotVersionColumnIfNeeded(); TheCloser.close(schemaManager, schemaInspector); } @@ -76,7 +74,29 @@ private void alterCommitIdColumnIfNeeded() { } } - private boolean executeSQL(String sql){ + /** + * JaVers 1.4.2 to 1.4.3 schema migration + */ + private void addSnapshotVersionColumnIfNeeded() { + + if (!columnExists("jv_snapshot", "version")) { + logger.warn("column jv_snapshot.version not exists, running ALTER TABLE ..."); + + if ( dialect instanceof PostgresDialect || + dialect instanceof MysqlDialect || + dialect instanceof H2Dialect || + dialect instanceof MsSqlDialect) { + executeSQL("ALTER TABLE jv_snapshot ADD COLUMN version BIGINT"); + } else if (dialect instanceof OracleDialect) { + executeSQL("ALTER TABLE jv_snapshot ADD version NUMBER"); + } else { + logger.error("\nno DB schema migration script for {} :(\nplease contact with JaVers team, javers@javers.org", + dialect.getCode()); + } + } + } + + private boolean executeSQL(String sql) { try { Statement stmt = connectionProvider.getConnection().createStatement(); @@ -105,7 +125,29 @@ private int getTypeOf(String tableName, String colName) { } } - private void ensureTable(String tableName, Schema schema){ + private boolean columnExists(String tableName, String colName) { + try { + Statement stmt = connectionProvider.getConnection().createStatement(); + + ResultSet res = stmt.executeQuery("select * from " + tableName + " where 1<0"); + ResultSetMetaData metaData = res.getMetaData(); + + for (int i = 1; i <= metaData.getColumnCount(); i++) { + if (metaData.getColumnName(i).equalsIgnoreCase(colName)) { + return true; + } + } + + res.close(); + stmt.close(); + + return false; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private void ensureTable(String tableName, Schema schema) { if (schemaInspector.relationExists(tableName)) { return; }