/*
* Copyright 2015 Samppa Saarela
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.javersion.store.jdbc;
import static com.querydsl.core.group.GroupBy.groupBy;
import static com.querydsl.core.types.Ops.EQ;
import static com.querydsl.core.types.Ops.GT;
import static com.querydsl.core.types.Ops.IS_NULL;
import static com.querydsl.core.types.dsl.Expressions.constant;
import static com.querydsl.core.types.dsl.Expressions.predicate;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.javersion.core.Revision;
import org.javersion.core.VersionNode;
import org.javersion.object.ObjectVersion;
import org.javersion.path.PropertyPath;
import org.javersion.util.Check;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.querydsl.core.group.Group;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.sql.SQLQuery;
import com.querydsl.sql.dml.SQLUpdateClause;
public class DocumentVersionStoreJdbc<Id, M, V extends JDocumentVersion<Id>>
extends AbstractVersionStoreJdbc<Id, M, V, DocumentUpdateBatch<Id, M, V>, DocumentStoreOptions<Id, M, V>> {
protected final Expression<?>[] versionAndParentsSince;
/**
* No-args constructor for proxies
*/
@SuppressWarnings("unused")
public DocumentVersionStoreJdbc() {
super();
versionAndParentsSince = null;
}
public DocumentVersionStoreJdbc(DocumentStoreOptions<Id, M, V> options) {
super(options);
versionAndParentsSince = concat(versionAndParentColumns, options.sinceVersion.ordinal);
}
public void append(Id docId, VersionNode<PropertyPath, Object, M> version) {
options.transactions.writeRequired(() -> {
doAppend(ImmutableMultimap.of(docId, version));
return null;
});
}
public void append(Id docId, Iterable<VersionNode<PropertyPath, Object, M>> versions) {
options.transactions.writeRequired(() -> {
ImmutableMultimap.Builder<Id, VersionNode<PropertyPath, Object, M>> builder = ImmutableMultimap.builder();
doAppend(builder.putAll(docId, versions).build());
return null;
});
}
public void append(Multimap<Id, VersionNode<PropertyPath, Object, M>> versionsByDocId) {
options.transactions.writeRequired(() -> {
doAppend(versionsByDocId);
return null;
});
}
@Override
protected FetchResults<Id, M> doFetch(Id docId, boolean optimized) {
Check.notNull(docId, "docId");
BooleanExpression predicate = versionsOf(docId);
List<Group> versionsAndParents = fetchVersionsAndParents(optimized, predicate,
options.version.ordinal.asc());
return fetch(versionsAndParents, optimized, predicate);
}
@Override
protected List<ObjectVersion<M>> doFetchUpdates(Id docId, Revision since) {
List<Group> versionsAndParents = versionsAndParentsSince(docId, since);
if (versionsAndParents.isEmpty()) {
return ImmutableList.of();
}
Long sinceOrdinal = versionsAndParents.get(0).getOne(options.sinceVersion.ordinal);
BooleanExpression predicate = versionsOf(docId)
.and(predicate(GT, options.version.ordinal, constant(sinceOrdinal)));
FetchResults<Id, M> results = fetch(versionsAndParents, false, predicate);
return results.containsKey(docId) ? results.getVersions(docId) : ImmutableList.of();
}
@Override
public DocumentUpdateBatch<Id, M, V> updateBatch(Collection<Id> ids) {
return new DocumentUpdateBatch<>(this);
}
protected void doAppend(Multimap<Id, VersionNode<PropertyPath, Object, M>> versionsByDocId) {
DocumentUpdateBatch<Id, M, V> batch = updateBatch(versionsByDocId.keys());
for (Id docId : versionsByDocId.keySet()) {
for (VersionNode<PropertyPath, Object, M> version : versionsByDocId.get(docId)) {
batch.addVersion(docId, version);
}
}
batch.execute();
}
protected BooleanExpression versionsOf(Id docId) {
return predicate(EQ, options.version.docId, constant(docId))
.and(options.version.ordinal.isNotNull());
}
protected List<Group> versionsAndParentsSince(Id docId, Revision since) {
SQLQuery<?> qry = options.queryFactory.from(options.sinceVersion);
// Left join version version on version.ordinal > since.ordinal and version.doc_id = since.doc_id
qry.leftJoin(options.version).on(
options.version.ordinal.gt(options.sinceVersion.ordinal),
predicate(EQ, options.version.docId, options.sinceVersion.docId));
// Left join parents
qry.leftJoin(options.parent).on(options.parent.revision.eq(options.version.revision));
qry.where(options.sinceVersion.revision.eq(since),
// Return "since" row even if there is no newer versions
versionsOf(docId).or(predicate(IS_NULL, options.version.docId)));
qry.orderBy(options.version.ordinal.asc());
return verifyVersionsAndParentsSince(qry.transform(groupBy(options.version.revision).list(versionAndParentsSince)), since);
}
@Override
protected SQLUpdateClause setOrdinal(SQLUpdateClause versionUpdateBatch, long ordinal) {
return versionUpdateBatch
.set(options.version.ordinal, ordinal)
.setNull(options.version.txOrdinal);
}
@Override
protected Map<Revision, Id> getUnpublishedRevisionsForUpdate() {
return options.queryFactory
.from(options.version)
.where(options.version.txOrdinal.isNotNull())
.orderBy(options.version.txOrdinal.asc(), options.version.revision.asc())
.forUpdate()
.transform(groupBy(options.version.revision).as(options.version.docId));
}
@Override
protected void lockForMaintenance(Id docId) {
options.queryFactory
.select(options.version.revision)
.from(options.version)
.where(versionsOf(docId))
.orderBy(options.version.ordinal.asc())
.forUpdate()
.iterate()
.close();
}
}