diff --git a/deploy.sh b/bin/deploy.sh similarity index 100% rename from deploy.sh rename to bin/deploy.sh diff --git a/bin/format.sh b/bin/format.sh new file mode 100755 index 0000000..10b2bf0 --- /dev/null +++ b/bin/format.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +for f in $(find ./src -name \*.java); do + echo Formatting $f + java -jar ./lib/google-java-format-1.3-all-deps.jar --replace $f +done + diff --git a/sign.sh b/bin/sign.sh similarity index 100% rename from sign.sh rename to bin/sign.sh diff --git a/lib/google-java-format-1.3-all-deps.jar b/lib/google-java-format-1.3-all-deps.jar new file mode 100644 index 0000000..859a3ca Binary files /dev/null and b/lib/google-java-format-1.3-all-deps.jar differ diff --git a/pom.xml b/pom.xml index dac750d..71feca8 100644 --- a/pom.xml +++ b/pom.xml @@ -4,8 +4,8 @@ 4.0.0 net.helenus - helenus-core - 2.0.40-SNAPSHOT + helenus-net-core + 2.1 jar helenus diff --git a/src/main/java/com/datastax/driver/core/querybuilder/IsNotNullClause.java b/src/main/java/com/datastax/driver/core/querybuilder/IsNotNullClause.java new file mode 100644 index 0000000..9ff2596 --- /dev/null +++ b/src/main/java/com/datastax/driver/core/querybuilder/IsNotNullClause.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 The Helenus Authors + * + * 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 com.datastax.driver.core.querybuilder; + +import com.datastax.driver.core.CodecRegistry; +import java.util.List; + +public class IsNotNullClause extends Clause { + + final String name; + + public IsNotNullClause(String name) { + this.name = name; + } + + @Override + String name() { + return name; + } + + @Override + Object firstValue() { + return null; + } + + @Override + void appendTo(StringBuilder sb, List variables, CodecRegistry codecRegistry) { + Utils.appendName(name, sb).append(" IS NOT NULL"); + } + + @Override + boolean containsBindMarker() { + return false; + } +} diff --git a/src/main/java/com/datastax/driver/core/schemabuilder/CreateMaterializedView.java b/src/main/java/com/datastax/driver/core/schemabuilder/CreateMaterializedView.java new file mode 100644 index 0000000..919becc --- /dev/null +++ b/src/main/java/com/datastax/driver/core/schemabuilder/CreateMaterializedView.java @@ -0,0 +1,53 @@ +package com.datastax.driver.core.schemabuilder; + +import com.datastax.driver.core.CodecRegistry; +import com.datastax.driver.core.querybuilder.Select; + +public class CreateMaterializedView extends Create { + + private String viewName; + private Select.Where selection; + private String primaryKey; + private String clustering; + + public CreateMaterializedView( + String keyspaceName, String viewName, Select.Where selection, String primaryKey, String clustering) { + super(keyspaceName, viewName); + this.viewName = viewName; + this.selection = selection; + this.primaryKey = primaryKey; + this.clustering = clustering; + } + + public String getQueryString(CodecRegistry codecRegistry) { + return buildInternal(); + } + + public String buildInternal() { + StringBuilder createStatement = + new StringBuilder(STATEMENT_START).append("CREATE MATERIALIZED VIEW"); + if (ifNotExists) { + createStatement.append(" IF NOT EXISTS"); + } + createStatement.append(" "); + if (keyspaceName.isPresent()) { + createStatement.append(keyspaceName.get()).append("."); + } + createStatement.append(viewName); + createStatement.append(" AS "); + createStatement.append(selection.getQueryString()); + createStatement.setLength(createStatement.length() - 1); + createStatement.append(" "); + createStatement.append(primaryKey); + if (clustering != null) { + createStatement.append(" ").append(clustering); + } + createStatement.append(";"); + + return createStatement.toString(); + } + + public String toString() { + return buildInternal(); + } +} diff --git a/src/main/java/com/datastax/driver/core/schemabuilder/DropMaterializedView.java b/src/main/java/com/datastax/driver/core/schemabuilder/DropMaterializedView.java new file mode 100644 index 0000000..7eca05d --- /dev/null +++ b/src/main/java/com/datastax/driver/core/schemabuilder/DropMaterializedView.java @@ -0,0 +1,53 @@ +package com.datastax.driver.core.schemabuilder; + +import com.google.common.base.Optional; + +public class DropMaterializedView extends Drop { + + enum DroppedItem { + TABLE, + TYPE, + INDEX, + MATERIALIZED_VIEW + } + + private Optional keyspaceName = Optional.absent(); + private String itemName; + private boolean ifExists = true; + private final String itemType = "MATERIALIZED VIEW"; + + public DropMaterializedView(String keyspaceName, String viewName) { + this(keyspaceName, viewName, DroppedItem.MATERIALIZED_VIEW); + } + + private DropMaterializedView(String keyspaceName, String viewName, DroppedItem itemType) { + super(keyspaceName, viewName, Drop.DroppedItem.TABLE); + validateNotEmpty(keyspaceName, "Keyspace name"); + this.keyspaceName = Optional.fromNullable(keyspaceName); + this.itemName = viewName; + } + + /** + * Add the 'IF EXISTS' condition to this DROP statement. + * + * @return this statement. + */ + public Drop ifExists() { + this.ifExists = true; + return this; + } + + @Override + public String buildInternal() { + StringBuilder dropStatement = new StringBuilder("DROP " + itemType + " "); + if (ifExists) { + dropStatement.append("IF EXISTS "); + } + if (keyspaceName.isPresent()) { + dropStatement.append(keyspaceName.get()).append("."); + } + + dropStatement.append(itemName); + return dropStatement.toString(); + } +} diff --git a/src/main/java/net/helenus/config/GetterMethodDetector.java b/src/main/java/net/helenus/config/GetterMethodDetector.java index 2d1f908..60a9ec0 100644 --- a/src/main/java/net/helenus/config/GetterMethodDetector.java +++ b/src/main/java/net/helenus/config/GetterMethodDetector.java @@ -16,6 +16,7 @@ package net.helenus.config; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.function.Function; import net.helenus.mapping.annotation.Transient; @@ -33,6 +34,10 @@ public enum GetterMethodDetector implements Function { return false; } + if (Modifier.isStatic(method.getModifiers())) { + return false; + } + // Methods marked "Transient" are not mapped, skip them. if (method.getDeclaredAnnotation(Transient.class) != null) { return false; diff --git a/src/main/java/net/helenus/core/AbstractAuditedEntityDraft.java b/src/main/java/net/helenus/core/AbstractAuditedEntityDraft.java index 5ec76d3..a9a09e2 100644 --- a/src/main/java/net/helenus/core/AbstractAuditedEntityDraft.java +++ b/src/main/java/net/helenus/core/AbstractAuditedEntityDraft.java @@ -1,41 +1,38 @@ package net.helenus.core; -import net.helenus.core.reflect.MapExportable; - import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; - +import net.helenus.core.reflect.MapExportable; public abstract class AbstractAuditedEntityDraft extends AbstractEntityDraft { - public AbstractAuditedEntityDraft(MapExportable entity) { - super(entity); + public AbstractAuditedEntityDraft(MapExportable entity) { + super(entity); - Date in = new Date(); - LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault()); - Date now = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); + Date in = new Date(); + LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault()); + Date now = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); - String who = getCurrentAuditor(); + String who = getCurrentAuditor(); - if (entity == null) { - if (who != null) { - set("createdBy", who); - } - set("createdAt", now); - } - if (who != null) { - set("modifiedBy", who); - } - set("modifiedAt", now); + if (entity == null) { + if (who != null) { + set("createdBy", who); + } + set("createdAt", now); } - - protected String getCurrentAuditor() { - return null; + if (who != null) { + set("modifiedBy", who); } + set("modifiedAt", now); + } - public Date createdAt() { - return (Date) get("createdAt", Date.class); - } + protected String getCurrentAuditor() { + return null; + } + public Date createdAt() { + return (Date) get("createdAt", Date.class); + } } diff --git a/src/main/java/net/helenus/core/AbstractEntityDraft.java b/src/main/java/net/helenus/core/AbstractEntityDraft.java index 54a3edf..fbccb3b 100644 --- a/src/main/java/net/helenus/core/AbstractEntityDraft.java +++ b/src/main/java/net/helenus/core/AbstractEntityDraft.java @@ -1,151 +1,159 @@ package net.helenus.core; -import java.util.*; - import com.google.common.primitives.Primitives; - +import java.util.*; import net.helenus.core.reflect.DefaultPrimitiveTypes; import net.helenus.core.reflect.Drafted; import net.helenus.core.reflect.MapExportable; import net.helenus.mapping.MappingUtil; - public abstract class AbstractEntityDraft implements Drafted { - private final Map backingMap = new HashMap(); - private final Set mutatedSet = new HashSet(); - private final MapExportable entity; - private final Map entityMap; + private final Map backingMap = new HashMap(); + private final MapExportable entity; + private final Map entityMap; + public AbstractEntityDraft(MapExportable entity) { + this.entity = entity; + this.entityMap = entity != null ? entity.toMap() : new HashMap(); + } - public AbstractEntityDraft(MapExportable entity) { - this.entity = entity; - this.entityMap = entity != null ? entity.toMap() : new HashMap(); + public abstract Class getEntityClass(); + + public E build() { + return Helenus.map(getEntityClass(), toMap()); + } + + @SuppressWarnings("unchecked") + protected T get(Getter getter, Class returnType) { + return (T) get(this.methodNameFor(getter), returnType); + } + + @SuppressWarnings("unchecked") + protected T get(String key, Class returnType) { + T value = (T) backingMap.get(key); + + if (value == null) { + value = (T) entityMap.get(key); + if (value == null) { + + if (Primitives.allPrimitiveTypes().contains(returnType)) { + + DefaultPrimitiveTypes type = DefaultPrimitiveTypes.lookup(returnType); + if (type == null) { + throw new RuntimeException("unknown primitive type " + returnType); + } + + return (T) type.getDefaultValue(); + } + } } - public abstract Class getEntityClass(); + return value; + } - public E build() { return Helenus.map(getEntityClass(), toMap()); } + protected Object set(Getter getter, Object value) { + return set(this.methodNameFor(getter), value); + } - protected T get(String key, Class returnType) { - T value = (T) entityMap.get(key); - - if (value == null) { - - if (Primitives.allPrimitiveTypes().contains(returnType)) { - - DefaultPrimitiveTypes type = DefaultPrimitiveTypes.lookup(returnType); - if (type == null) { - throw new RuntimeException("unknown primitive type " + returnType); - } - - return (T) type.getDefaultValue(); - } - } - - return value; + protected Object set(String key, Object value) { + if (key == null || value == null) { + return null; } - protected Object set(String key, Object value) { + backingMap.put(key, value); + return value; + } - if (key == null || value == null) { - return null; - } + @SuppressWarnings("unchecked") + protected T mutate(Getter getter, T value) { + return (T) mutate(this.methodNameFor(getter), value); + } + protected Object mutate(String key, Object value) { + Objects.requireNonNull(key); + + if (value == null) { + return null; + } + + if (entity != null) { + Map map = entity.toMap(); + + if (map.containsKey(key) && !value.equals(map.get(key))) { backingMap.put(key, value); - mutatedSet.add(key); return value; + } + + return map.get(key); + } else { + backingMap.put(key, value); + + return null; } + } - protected Object mutate(String key, Object value) { - Objects.requireNonNull(key); + private String methodNameFor(Getter getter) { + return MappingUtil.resolveMappingProperty(getter).getProperty().getPropertyName(); + } - if (value == null) { - return null; - } + public Object unset(Getter getter) { + return unset(methodNameFor(getter)); + } - if (entity != null) { - Map map = entity.toMap(); - - if (map.containsKey(key) && !value.equals(map.get(key))) { - backingMap.put(key, value); - mutatedSet.add(key); - return value; - } - - return map.get(key); - } else { - backingMap.put(key, value); - mutatedSet.add(key); - - return null; - } + public Object unset(String key) { + if (key != null) { + Object value = backingMap.get(key); + backingMap.put(key, null); + return value; } + return null; + } - private String methodNameFor(Getter getter) { - return MappingUtil.resolveMappingProperty(getter) - .getProperty() - .getPropertyName(); + public boolean reset(Getter getter, T desiredValue) { + return this.reset(this.methodNameFor(getter), desiredValue); + } + + public boolean reset(String key, T desiredValue) { + if (key != null && desiredValue != null) { + @SuppressWarnings("unchecked") + T currentValue = (T) backingMap.get(key); + if (currentValue == null || !currentValue.equals(desiredValue)) { + set(key, desiredValue); + return true; + } } + return false; + } - public Object unset(Getter getter) { - return unset(methodNameFor(getter)); + @Override + public Map toMap() { + return toMap(entityMap); + } + + public Map toMap(Map entityMap) { + Map combined; + if (entityMap != null && entityMap.size() > 0) { + combined = new HashMap(entityMap.size()); + for (String key : entityMap.keySet()) { + combined.put(key, entityMap.get(key)); + } + } else { + combined = new HashMap(backingMap.size()); } - - public Object unset(String key) { - if (key != null) { - Object value = backingMap.get(key); - backingMap.put(key, null); - mutatedSet.add(key); - return value; - } - return null; + for (String key : mutated()) { + combined.put(key, backingMap.get(key)); } + return combined; + } - public boolean reset(Getter getter, T desiredValue) { - return this.reset(methodNameFor(getter), desiredValue); - } - - public boolean reset(String key, T desiredValue) { - if (key != null && desiredValue != null) { - @SuppressWarnings("unchecked") - T currentValue = (T) backingMap.get(key); - if (currentValue != null && !currentValue.equals(desiredValue)) { - return set(key, desiredValue) != null; - } - } - return false; - } - - @Override - public Map toMap() { - return toMap(entityMap); - } - - public Map toMap(MapentityMap) { - Map combined; - if (entityMap != null && entityMap.size() > 0) { - combined = new HashMap(entityMap.size()); - for (String key : entityMap.keySet()) { - combined.put(key, entityMap.get(key)); - } - } else { - combined = new HashMap(backingMap.size()); - } - for (String key : mutatedSet) { - combined.put(key, backingMap.get(key)); - } - return combined; - } - - @Override - public Set mutated() { - return mutatedSet; - } - - @Override - public String toString() { - return backingMap.toString(); - } + @Override + public Set mutated() { + return backingMap.keySet(); + } + @Override + public String toString() { + return backingMap.toString(); + } } diff --git a/src/main/java/net/helenus/core/AbstractSessionOperations.java b/src/main/java/net/helenus/core/AbstractSessionOperations.java index 920fabd..5721051 100644 --- a/src/main/java/net/helenus/core/AbstractSessionOperations.java +++ b/src/main/java/net/helenus/core/AbstractSessionOperations.java @@ -22,7 +22,6 @@ import com.datastax.driver.core.querybuilder.BuiltStatement; import com.google.common.util.concurrent.ListenableFuture; import java.io.PrintStream; import java.util.concurrent.Executor; - import net.helenus.mapping.value.ColumnValuePreparer; import net.helenus.mapping.value.ColumnValueProvider; import net.helenus.support.HelenusException; @@ -51,6 +50,8 @@ public abstract class AbstractSessionOperations { public abstract ConsistencyLevel getDefaultConsistencyLevel(); + public abstract boolean getDefaultQueryIdempotency(); + public PreparedStatement prepare(RegularStatement statement) { try { log(statement, false); @@ -122,5 +123,4 @@ public abstract class AbstractSessionOperations { void printCql(String cql) { getPrintStream().println(cql); } - } diff --git a/src/main/java/net/helenus/core/AbstractUnitOfWork.java b/src/main/java/net/helenus/core/AbstractUnitOfWork.java index dfb1ff7..c621770 100644 --- a/src/main/java/net/helenus/core/AbstractUnitOfWork.java +++ b/src/main/java/net/helenus/core/AbstractUnitOfWork.java @@ -17,10 +17,8 @@ package net.helenus.core; import com.diffplug.common.base.Errors; import com.google.common.collect.TreeTraverser; - import java.util.*; - /** Encapsulates the concept of a "transaction" as a unit-of-work. */ public abstract class AbstractUnitOfWork implements UnitOfWork, AutoCloseable { private final List> nested = new ArrayList<>(); @@ -70,22 +68,25 @@ public abstract class AbstractUnitOfWork implements UnitOfW return null; } - public Map> getCache() { return cache; } + public Map> getCache() { + return cache; + } private Iterator> getChildNodes() { return nested.iterator(); } - /** - * Checks to see if the work performed between calling begin and now can be committed or not. - * - * @return a function from which to chain work that only happens when commit is successful - * @throws E when the work overlaps with other concurrent writers. - */ + /** + * Checks to see if the work performed between calling begin and now can be committed or not. + * + * @return a function from which to chain work that only happens when commit is successful + * @throws E when the work overlaps with other concurrent writers. + */ public PostCommitFunction commit() throws E { // All nested UnitOfWork should be committed (not aborted) before calls to commit, check. boolean canCommit = true; - TreeTraverser> traverser = TreeTraverser.using(node -> node::getChildNodes); + TreeTraverser> traverser = + TreeTraverser.using(node -> node::getChildNodes); for (AbstractUnitOfWork uow : traverser.postOrderTraversal(this)) { if (this != uow) { canCommit &= (!uow.aborted && uow.committed); @@ -112,7 +113,8 @@ public abstract class AbstractUnitOfWork implements UnitOfW if (parentCache.containsKey(key)) { // merge the sets Set ps = parentCache.get(key); - ps.addAll(cache.get(key)); //TODO(gburd): review this, likely not correct in all cases as-is. + ps.addAll( + cache.get(key)); //TODO(gburd): review this, likely not correct in all cases as-is. } else { // add the missing set parentCache.put(key, cache.get(key)); @@ -122,9 +124,12 @@ public abstract class AbstractUnitOfWork implements UnitOfW // Apply all post-commit functions for if (parent == null) { - traverser.postOrderTraversal(this).forEach(uow -> { - uow.applyPostCommitFunctions(); - }); + traverser + .postOrderTraversal(this) + .forEach( + uow -> { + uow.applyPostCommitFunctions(); + }); return new PostCommitFunction(this, null); } } @@ -137,11 +142,15 @@ public abstract class AbstractUnitOfWork implements UnitOfW /* Explicitly discard the work and mark it as as such in the log. */ public void abort() { - TreeTraverser> traverser = TreeTraverser.using(node -> node::getChildNodes); - traverser.postOrderTraversal(this).forEach(uow -> { - uow.committed = false; - uow.aborted = true; - }); + TreeTraverser> traverser = + TreeTraverser.using(node -> node::getChildNodes); + traverser + .postOrderTraversal(this) + .forEach( + uow -> { + uow.committed = false; + uow.aborted = true; + }); // log.record(txn::abort) // cache.invalidateSince(txn::start time) } @@ -165,5 +174,4 @@ public abstract class AbstractUnitOfWork implements UnitOfW public boolean hasCommitted() { return committed; } - } diff --git a/src/main/java/net/helenus/core/CommitThunk.java b/src/main/java/net/helenus/core/CommitThunk.java index c200061..ff50f4a 100644 --- a/src/main/java/net/helenus/core/CommitThunk.java +++ b/src/main/java/net/helenus/core/CommitThunk.java @@ -1,8 +1,7 @@ package net.helenus.core; -import java.util.function.Function; @FunctionalInterface public interface CommitThunk { - void apply(); + void apply(); } diff --git a/src/main/java/net/helenus/core/Helenus.java b/src/main/java/net/helenus/core/Helenus.java index 26a453b..c0d9bc6 100644 --- a/src/main/java/net/helenus/core/Helenus.java +++ b/src/main/java/net/helenus/core/Helenus.java @@ -140,7 +140,14 @@ public final class Helenus { } public static HelenusEntity entity(Class iface) { - return entity(iface, metadataForEntity.get(iface)); + Metadata metadata = metadataForEntity.get(iface); + if (metadata == null) { + HelenusSession session = session(); + if (session != null) { + metadata = session.getMetadata(); + } + } + return entity(iface, metadata); } public static HelenusEntity entity(Class iface, Metadata metadata) { diff --git a/src/main/java/net/helenus/core/HelenusSession.java b/src/main/java/net/helenus/core/HelenusSession.java index e2a2104..601a773 100644 --- a/src/main/java/net/helenus/core/HelenusSession.java +++ b/src/main/java/net/helenus/core/HelenusSession.java @@ -15,10 +15,21 @@ */ package net.helenus.core; +import static net.helenus.core.Query.eq; + import brave.Tracer; -import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.datastax.driver.core.*; +import java.io.Closeable; +import java.io.PrintStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.function.Function; import net.helenus.core.operation.*; import net.helenus.core.reflect.Drafted; import net.helenus.core.reflect.HelenusPropertyNode; @@ -33,19 +44,6 @@ import net.helenus.support.Fun.Tuple6; import net.helenus.support.HelenusException; import net.helenus.support.HelenusMappingException; -import java.io.Closeable; -import java.io.PrintStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.function.Function; - -import static net.helenus.core.Query.eq; - public final class HelenusSession extends AbstractSessionOperations implements Closeable { private final int MAX_CACHE_SIZE = 10000; @@ -56,6 +54,7 @@ public final class HelenusSession extends AbstractSessionOperations implements C private volatile String usingKeyspace; private volatile boolean showCql; private final ConsistencyLevel defaultConsistencyLevel; + private final boolean defaultQueryIdempotency; private final MetricRegistry metricRegistry; private final Tracer zipkinTracer; private final PrintStream printStream; @@ -68,31 +67,32 @@ public final class HelenusSession extends AbstractSessionOperations implements C private final StatementColumnValuePreparer valuePreparer; private final Metadata metadata; - HelenusSession( - Session session, - String usingKeyspace, - CodecRegistry registry, - boolean showCql, - PrintStream printStream, - SessionRepositoryBuilder sessionRepositoryBuilder, - Executor executor, - boolean dropSchemaOnClose, - ConsistencyLevel consistencyLevel, - Class unitOfWorkClass, - MetricRegistry metricRegistry, - Tracer tracer) { + Session session, + String usingKeyspace, + CodecRegistry registry, + boolean showCql, + PrintStream printStream, + SessionRepositoryBuilder sessionRepositoryBuilder, + Executor executor, + boolean dropSchemaOnClose, + ConsistencyLevel consistencyLevel, + boolean defaultQueryIdempotency, + Class unitOfWorkClass, + MetricRegistry metricRegistry, + Tracer tracer) { this.session = session; this.registry = registry == null ? CodecRegistry.DEFAULT_INSTANCE : registry; this.usingKeyspace = - Objects.requireNonNull( - usingKeyspace, "keyspace needs to be selected before creating session"); + Objects.requireNonNull( + usingKeyspace, "keyspace needs to be selected before creating session"); this.showCql = showCql; this.printStream = printStream; this.sessionRepository = sessionRepositoryBuilder.build(); this.executor = executor; this.dropSchemaOnClose = dropSchemaOnClose; this.defaultConsistencyLevel = consistencyLevel; + this.defaultQueryIdempotency = defaultQueryIdempotency; this.unitOfWorkClass = unitOfWorkClass; this.metricRegistry = metricRegistry; this.zipkinTracer = tracer; @@ -168,46 +168,59 @@ public final class HelenusSession extends AbstractSessionOperations implements C return metricRegistry; } + @Override public ConsistencyLevel getDefaultConsistencyLevel() { return defaultConsistencyLevel; } + @Override + public boolean getDefaultQueryIdempotency() { + return defaultQueryIdempotency; + } + public Metadata getMetadata() { return metadata; } public synchronized UnitOfWork begin() { - return begin(null); + return begin(null); } public synchronized UnitOfWork begin(UnitOfWork parent) { try { Class clazz = unitOfWorkClass; - Constructor ctor = clazz.getConstructor(HelenusSession.class, UnitOfWork.class); + Constructor ctor = + clazz.getConstructor(HelenusSession.class, UnitOfWork.class); UnitOfWork uow = ctor.newInstance(this, parent); if (parent != null) { parent.addNestedUnitOfWork(uow); } return uow.begin(); - } - catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { - throw new HelenusException(String.format("Unable to instantiate {} as a UnitOfWork.", unitOfWorkClass.getSimpleName()), e); + } catch (NoSuchMethodException + | InvocationTargetException + | InstantiationException + | IllegalAccessException e) { + throw new HelenusException( + String.format( + "Unable to instantiate {} as a UnitOfWork.", unitOfWorkClass.getSimpleName()), + e); } } public SelectOperation select(E pojo) { - Objects.requireNonNull(pojo, "supplied object must be a dsl for a registered entity but cannot be null"); + Objects.requireNonNull( + pojo, "supplied object must be a dsl for a registered entity but cannot be null"); ColumnValueProvider valueProvider = getValueProvider(); HelenusEntity entity = Helenus.resolve(pojo); Class entityClass = entity.getMappingInterface(); return new SelectOperation( - this, - entity, - (r) -> { - Map map = new ValueProviderMap(r, valueProvider, entity); - return (E) Helenus.map(entityClass, map); - }); + this, + entity, + (r) -> { + Map map = new ValueProviderMap(r, valueProvider, entity); + return (E) Helenus.map(entityClass, map); + }); } public SelectOperation select(Class entityClass) { @@ -216,12 +229,12 @@ public final class HelenusSession extends AbstractSessionOperations implements C HelenusEntity entity = Helenus.entity(entityClass); return new SelectOperation( - this, - entity, - (r) -> { - Map map = new ValueProviderMap(r, valueProvider, entity); - return (E) Helenus.map(entityClass, map); - }); + this, + entity, + (r) -> { + Map map = new ValueProviderMap(r, valueProvider, entity); + return (E) Helenus.map(entityClass, map); + }); } public SelectOperation select() { @@ -234,7 +247,8 @@ public final class HelenusSession extends AbstractSessionOperations implements C } public SelectOperation selectAll(E pojo) { - Objects.requireNonNull(pojo, "supplied object must be a dsl for a registered entity but cannot be null"); + Objects.requireNonNull( + pojo, "supplied object must be a dsl for a registered entity but cannot be null"); HelenusEntity entity = Helenus.resolve(pojo); return new SelectOperation(this, entity); } @@ -406,51 +420,62 @@ public final class HelenusSession extends AbstractSessionOperations implements C public UpdateOperation update(Drafted drafted) { if (drafted instanceof AbstractEntityDraft == false) { - throw new HelenusMappingException("update of draft objects that don't inherit from AbstractEntityDraft is not yet supported"); + throw new HelenusMappingException( + "update of draft objects that don't inherit from AbstractEntityDraft is not yet supported"); } - AbstractEntityDraft draft = (AbstractEntityDraft)drafted; + AbstractEntityDraft draft = (AbstractEntityDraft) drafted; UpdateOperation update = new UpdateOperation(this, draft); Map map = draft.toMap(); Set mutatedProperties = draft.mutated(); HelenusEntity entity = Helenus.entity(draft.getEntityClass()); // Add all the mutated values contained in the draft. - entity.getOrderedProperties().forEach(property -> { - switch (property.getColumnType()) { - case PARTITION_KEY: - case CLUSTERING_COLUMN: - break; - default: - String propertyName = property.getPropertyName(); - if (mutatedProperties.contains(propertyName)) { - Object value = map.get(propertyName); - Getter getter = new Getter() { - @Override - public Object get() { - throw new DslPropertyException(new HelenusPropertyNode(property, Optional.empty())); + entity + .getOrderedProperties() + .forEach( + property -> { + switch (property.getColumnType()) { + case PARTITION_KEY: + case CLUSTERING_COLUMN: + break; + default: + String propertyName = property.getPropertyName(); + if (mutatedProperties.contains(propertyName)) { + Object value = map.get(propertyName); + Getter getter = + new Getter() { + @Override + public Object get() { + throw new DslPropertyException( + new HelenusPropertyNode(property, Optional.empty())); + } + }; + update.set(getter, value); + } } - }; - update.set(getter, value); - } - } - }); + }); // Add the partition and clustering keys if they were in the draft (normally the case). - entity.getOrderedProperties().forEach(property -> { - switch (property.getColumnType()) { - case PARTITION_KEY: - case CLUSTERING_COLUMN: - String propertyName = property.getPropertyName(); - Object value = map.get(propertyName); - Getter getter = new Getter() { - @Override - public Object get() { - throw new DslPropertyException(new HelenusPropertyNode(property, Optional.empty())); - } - }; - update.where(getter, eq(value)); - } - }); + entity + .getOrderedProperties() + .forEach( + property -> { + switch (property.getColumnType()) { + case PARTITION_KEY: + case CLUSTERING_COLUMN: + String propertyName = property.getPropertyName(); + Object value = map.get(propertyName); + Getter getter = + new Getter() { + @Override + public Object get() { + throw new DslPropertyException( + new HelenusPropertyNode(property, Optional.empty())); + } + }; + update.where(getter, eq(value)); + } + }); return update; } @@ -473,9 +498,14 @@ public final class HelenusSession extends AbstractSessionOperations implements C } public InsertOperation insert(T pojo) { - Objects.requireNonNull(pojo, "supplied object must be either an instance of the entity class or a dsl for it, but cannot be null"); + Objects.requireNonNull( + pojo, + "supplied object must be either an instance of the entity class or a dsl for it, but cannot be null"); HelenusEntity entity = null; - try { entity = Helenus.resolve(pojo); } catch (HelenusMappingException e) {} + try { + entity = Helenus.resolve(pojo); + } catch (HelenusMappingException e) { + } if (entity != null) { return new InsertOperation(this, entity.getMappingInterface(), true); } else { @@ -483,7 +513,9 @@ public final class HelenusSession extends AbstractSessionOperations implements C } } - public InsertOperation insert(Drafted draft) { return insert(draft.build(), draft.mutated()); } + public InsertOperation insert(Drafted draft) { + return insert(draft.build(), draft.mutated()); + } private InsertOperation insert(T pojo, Set mutations) { Objects.requireNonNull(pojo, "pojo is empty"); @@ -507,9 +539,14 @@ public final class HelenusSession extends AbstractSessionOperations implements C } public InsertOperation upsert(T pojo) { - Objects.requireNonNull(pojo, "supplied object must be either an instance of the entity class or a dsl for it, but cannot be null"); + Objects.requireNonNull( + pojo, + "supplied object must be either an instance of the entity class or a dsl for it, but cannot be null"); HelenusEntity entity = null; - try { entity = Helenus.resolve(pojo); } catch (HelenusMappingException e) {} + try { + entity = Helenus.resolve(pojo); + } catch (HelenusMappingException e) { + } if (entity != null) { return new InsertOperation(this, entity.getMappingInterface(), false); } else { @@ -582,5 +619,4 @@ public final class HelenusSession extends AbstractSessionOperations implements C break; } } - } diff --git a/src/main/java/net/helenus/core/PostCommitFunction.java b/src/main/java/net/helenus/core/PostCommitFunction.java index 22835c5..3ba2b8b 100644 --- a/src/main/java/net/helenus/core/PostCommitFunction.java +++ b/src/main/java/net/helenus/core/PostCommitFunction.java @@ -1,30 +1,29 @@ package net.helenus.core; - -import java.util.Objects; import java.util.*; +import java.util.Objects; public class PostCommitFunction implements java.util.function.Function { - private final UnitOfWork uow; - private final List postCommit; + private final UnitOfWork uow; + private final List postCommit; - PostCommitFunction(UnitOfWork uow, List postCommit) { - this.uow = uow; - this.postCommit = postCommit; - } + PostCommitFunction(UnitOfWork uow, List postCommit) { + this.uow = uow; + this.postCommit = postCommit; + } - public void andThen(CommitThunk after) { - Objects.requireNonNull(after); - if (postCommit == null) { - after.apply(); - } else { - postCommit.add(after); - } + public void andThen(CommitThunk after) { + Objects.requireNonNull(after); + if (postCommit == null) { + after.apply(); + } else { + postCommit.add(after); } + } - @Override - public R apply(T t) { - return null; - } + @Override + public R apply(T t) { + return null; + } } diff --git a/src/main/java/net/helenus/core/SchemaUtil.java b/src/main/java/net/helenus/core/SchemaUtil.java index e2bb681..6a228e9 100644 --- a/src/main/java/net/helenus/core/SchemaUtil.java +++ b/src/main/java/net/helenus/core/SchemaUtil.java @@ -17,16 +17,22 @@ package net.helenus.core; import com.datastax.driver.core.*; import com.datastax.driver.core.IndexMetadata; +import com.datastax.driver.core.querybuilder.IsNotNullClause; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.schemabuilder.*; import com.datastax.driver.core.schemabuilder.Create.Options; import java.util.*; import java.util.stream.Collectors; +import net.helenus.core.reflect.HelenusPropertyNode; import net.helenus.mapping.*; import net.helenus.mapping.ColumnType; +import net.helenus.mapping.annotation.ClusteringColumn; import net.helenus.mapping.type.OptionalColumnMetadata; import net.helenus.support.CqlUtil; import net.helenus.support.HelenusMappingException; + public final class SchemaUtil { private SchemaUtil() {} @@ -143,6 +149,78 @@ public final class SchemaUtil { return SchemaBuilder.dropType(type.getTypeName()).ifExists(); } + public static SchemaStatement createMaterializedView( + String keyspace, String viewName, HelenusEntity entity) { + if (entity.getType() != HelenusEntityType.VIEW) { + throw new HelenusMappingException("expected view entity " + entity); + } + + if (entity == null) { + throw new HelenusMappingException("no entity or table to select data"); + } + + List props = new ArrayList(); + entity + .getOrderedProperties() + .stream() + .map(p -> new HelenusPropertyNode(p, Optional.empty())) + .forEach(p -> props.add(p)); + + Select.Selection selection = QueryBuilder.select(); + + for (HelenusPropertyNode prop : props) { + String columnName = prop.getColumnName(); + selection = selection.column(columnName); + } + Class iface = entity.getMappingInterface(); + String tableName = Helenus.entity(iface.getInterfaces()[0]).getName().toCql(); + Select.Where where = selection.from(tableName).where(); + List p = new ArrayList(props.size()); + List c = new ArrayList(props.size()); + List o = new ArrayList(props.size()); + + for (HelenusPropertyNode prop : props) { + String columnName = prop.getColumnName(); + switch (prop.getProperty().getColumnType()) { + case PARTITION_KEY: + p.add(columnName); + where = where.and(new IsNotNullClause(columnName)); + break; + + case CLUSTERING_COLUMN: + c.add(columnName); + where = where.and(new IsNotNullClause(columnName)); + + ClusteringColumn clusteringColumn = prop.getProperty().getGetterMethod().getAnnotation(ClusteringColumn.class); + if (clusteringColumn != null && clusteringColumn.ordering() != null) { + o.add(columnName + " " + clusteringColumn.ordering().cql()); + } + break; + default: + break; + } + } + + String primaryKey = + "PRIMARY KEY (" + + ((p.size() > 1) ? "(" + String.join(", ", p) + ")" : p.get(0)) + + ((c.size() > 0) + ? ", " + ((c.size() > 1) ? "(" + String.join(", ", c) + ")" : c.get(0)) + : "") + + ")"; + + String clustering = ""; + if (o.size() > 0) { + clustering = "WITH CLUSTERING ORDER BY (" + String.join(", ", o) + ")"; + } + return new CreateMaterializedView(keyspace, viewName, where, primaryKey, clustering); + } + + public static SchemaStatement dropMaterializedView( + String keyspace, String viewName, HelenusEntity entity) { + return new DropMaterializedView(keyspace, viewName); + } + public static SchemaStatement createTable(HelenusEntity entity) { if (entity.getType() != HelenusEntityType.TABLE) { diff --git a/src/main/java/net/helenus/core/SessionInitializer.java b/src/main/java/net/helenus/core/SessionInitializer.java index 677eb1b..f6ecfd6 100644 --- a/src/main/java/net/helenus/core/SessionInitializer.java +++ b/src/main/java/net/helenus/core/SessionInitializer.java @@ -25,10 +25,13 @@ import java.util.*; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.function.Consumer; +import net.helenus.core.reflect.DslExportable; import net.helenus.mapping.HelenusEntity; import net.helenus.mapping.HelenusEntityType; +import net.helenus.mapping.MappingUtil; import net.helenus.mapping.value.ColumnValuePreparer; import net.helenus.mapping.value.ColumnValueProvider; +import net.helenus.support.Either; import net.helenus.support.HelenusException; import net.helenus.support.PackageUtil; @@ -39,6 +42,7 @@ public final class SessionInitializer extends AbstractSessionOperations { private String usingKeyspace; private boolean showCql = false; private ConsistencyLevel consistencyLevel; + private boolean idempotent = true; private MetricRegistry metricRegistry = new MetricRegistry(); private Tracer zipkinTracer; private PrintStream printStream = System.out; @@ -52,7 +56,7 @@ public final class SessionInitializer extends AbstractSessionOperations { private KeyspaceMetadata keyspaceMetadata; - private final List initList = new ArrayList(); + private final List>> initList = new ArrayList>>(); private AutoDdl autoDdl = AutoDdl.UPDATE; SessionInitializer(Session session) { @@ -125,6 +129,15 @@ public final class SessionInitializer extends AbstractSessionOperations { return consistencyLevel; } + public SessionInitializer idempotentQueryExecution(boolean idempotent) { + this.idempotent = idempotent; + return this; + } + + public boolean getDefaultQueryIdempotency() { + return idempotent; + } + @Override public PrintStream getPrintStream() { return printStream; @@ -171,7 +184,10 @@ public final class SessionInitializer extends AbstractSessionOperations { PackageUtil.getClasses(packageName) .stream() .filter(c -> c.isInterface() && !c.isAnnotation()) - .forEach(initList::add); + .forEach( + clazz -> { + initList.add(Either.right(clazz)); + }); } catch (IOException | ClassNotFoundException e) { throw new HelenusException("fail to add package " + packageName, e); } @@ -183,7 +199,7 @@ public final class SessionInitializer extends AbstractSessionOperations { int len = dsls.length; for (int i = 0; i != len; ++i) { Object obj = Objects.requireNonNull(dsls[i], "element " + i + " is empty"); - initList.add(obj); + initList.add(Either.left(obj)); } return this; } @@ -241,6 +257,7 @@ public final class SessionInitializer extends AbstractSessionOperations { executor, autoDdl == AutoDdl.CREATE_DROP, consistencyLevel, + idempotent, unitOfWorkClass, metricRegistry, zipkinTracer); @@ -250,7 +267,19 @@ public final class SessionInitializer extends AbstractSessionOperations { Objects.requireNonNull(usingKeyspace, "please define keyspace by 'use' operator"); - initList.forEach(dsl -> sessionRepository.add(dsl)); + initList.forEach( + (either) -> { + Class iface = null; + if (either.isLeft()) { + iface = MappingUtil.getMappingInterface(either.getLeft()); + } else { + iface = either.getRight(); + } + + DslExportable dsl = (DslExportable) Helenus.dsl(iface); + dsl.setCassandraMetadataForHelenusSesion(session.getCluster().getMetadata()); + sessionRepository.add(dsl); + }); TableOperations tableOps = new TableOperations(this, dropUnusedColumns, dropUnusedIndexes); UserTypeOperations userTypeOps = new UserTypeOperations(this, dropUnusedColumns); @@ -258,8 +287,16 @@ public final class SessionInitializer extends AbstractSessionOperations { switch (autoDdl) { case CREATE_DROP: - // Drop tables first, otherwise a `DROP TYPE ...` will fail as the type is still referenced - // by a table. + // Drop view first, otherwise a `DROP TABLE ...` will fail as the type is still referenced + // by a view. + sessionRepository + .entities() + .stream() + .filter(e -> e.getType() == HelenusEntityType.VIEW) + .forEach(e -> tableOps.dropView(e)); + + // Drop tables second, before DROP TYPE otherwise a `DROP TYPE ...` will fail as the type is + // still referenced by a table. sessionRepository .entities() .stream() @@ -278,6 +315,12 @@ public final class SessionInitializer extends AbstractSessionOperations { .filter(e -> e.getType() == HelenusEntityType.TABLE) .forEach(e -> tableOps.createTable(e)); + sessionRepository + .entities() + .stream() + .filter(e -> e.getType() == HelenusEntityType.VIEW) + .forEach(e -> tableOps.createView(e)); + break; case VALIDATE: @@ -288,16 +331,29 @@ public final class SessionInitializer extends AbstractSessionOperations { .stream() .filter(e -> e.getType() == HelenusEntityType.TABLE) .forEach(e -> tableOps.validateTable(getTableMetadata(e), e)); + break; case UPDATE: eachUserTypeInOrder(userTypeOps, e -> userTypeOps.updateUserType(getUserType(e), e)); + sessionRepository + .entities() + .stream() + .filter(e -> e.getType() == HelenusEntityType.VIEW) + .forEach(e -> tableOps.dropView(e)); + sessionRepository .entities() .stream() .filter(e -> e.getType() == HelenusEntityType.TABLE) .forEach(e -> tableOps.updateTable(getTableMetadata(e), e)); + + sessionRepository + .entities() + .stream() + .filter(e -> e.getType() == HelenusEntityType.VIEW) + .forEach(e -> tableOps.createView(e)); break; } diff --git a/src/main/java/net/helenus/core/TableOperations.java b/src/main/java/net/helenus/core/TableOperations.java index f9c83d0..3c602d6 100644 --- a/src/main/java/net/helenus/core/TableOperations.java +++ b/src/main/java/net/helenus/core/TableOperations.java @@ -35,14 +35,11 @@ public final class TableOperations { } public void createTable(HelenusEntity entity) { - sessionOps.execute(SchemaUtil.createTable(entity), true); - executeBatch(SchemaUtil.createIndexes(entity)); } public void dropTable(HelenusEntity entity) { - sessionOps.execute(SchemaUtil.dropTable(entity), true); } @@ -50,7 +47,10 @@ public final class TableOperations { if (tmd == null) { throw new HelenusException( - "table not exists " + entity.getName() + "for entity " + entity.getMappingInterface()); + "table does not exists " + + entity.getName() + + "for entity " + + entity.getMappingInterface()); } List list = SchemaUtil.alterTable(tmd, entity, dropUnusedColumns); @@ -67,7 +67,31 @@ public final class TableOperations { } public void updateTable(TableMetadata tmd, HelenusEntity entity) { + if (tmd == null) { + createTable(entity); + return; + } + executeBatch(SchemaUtil.alterTable(tmd, entity, dropUnusedColumns)); + executeBatch(SchemaUtil.alterIndexes(tmd, entity, dropUnusedIndexes)); + } + + public void createView(HelenusEntity entity) { + sessionOps.execute( + SchemaUtil.createMaterializedView( + sessionOps.usingKeyspace(), entity.getName().toCql(), entity), + true); + // executeBatch(SchemaUtil.createIndexes(entity)); NOTE: Unfortunately C* 3.10 does not yet support 2i on materialized views. + } + + public void dropView(HelenusEntity entity) { + sessionOps.execute( + SchemaUtil.dropMaterializedView( + sessionOps.usingKeyspace(), entity.getName().toCql(), entity), + true); + } + + public void updateView(TableMetadata tmd, HelenusEntity entity) { if (tmd == null) { createTable(entity); return; diff --git a/src/main/java/net/helenus/core/UnitOfWork.java b/src/main/java/net/helenus/core/UnitOfWork.java index 0cfa536..f27b76c 100644 --- a/src/main/java/net/helenus/core/UnitOfWork.java +++ b/src/main/java/net/helenus/core/UnitOfWork.java @@ -15,44 +15,42 @@ */ package net.helenus.core; -import net.helenus.support.Either; import java.util.Map; import java.util.Set; public interface UnitOfWork extends AutoCloseable { - /** - * Marks the beginning of a transactional section of work. Will write a record to the shared - * write-ahead log. - * - * @return the handle used to commit or abort the work. - */ - UnitOfWork begin(); + /** + * Marks the beginning of a transactional section of work. Will write a record to the shared + * write-ahead log. + * + * @return the handle used to commit or abort the work. + */ + UnitOfWork begin(); - UnitOfWork addNestedUnitOfWork(UnitOfWork uow); + UnitOfWork addNestedUnitOfWork(UnitOfWork uow); - /** - * Checks to see if the work performed between calling begin and now can be committed or not. - * - * @return a function from which to chain work that only happens when commit is successful - * @throws E when the work overlaps with other concurrent writers. - */ - PostCommitFunction commit() throws E; + /** + * Checks to see if the work performed between calling begin and now can be committed or not. + * + * @return a function from which to chain work that only happens when commit is successful + * @throws E when the work overlaps with other concurrent writers. + */ + PostCommitFunction commit() throws E; - /** - * Explicitly abort the work within this unit of work. Any nested aborted unit of work - * will trigger the entire unit of work to commit. - */ - void abort(); + /** + * Explicitly abort the work within this unit of work. Any nested aborted unit of work will + * trigger the entire unit of work to commit. + */ + void abort(); + boolean hasAborted(); - boolean hasAborted(); + boolean hasCommitted(); - boolean hasCommitted(); + //Either> cacheLookup(String key); + Set cacheLookup(String key); - //Either> cacheLookup(String key); - Set cacheLookup(String key); - - Map> getCache(); + Map> getCache(); } diff --git a/src/main/java/net/helenus/core/UnitOfWorkImpl.java b/src/main/java/net/helenus/core/UnitOfWorkImpl.java index b9aab3b..52cae59 100644 --- a/src/main/java/net/helenus/core/UnitOfWorkImpl.java +++ b/src/main/java/net/helenus/core/UnitOfWorkImpl.java @@ -19,9 +19,8 @@ import net.helenus.support.HelenusException; class UnitOfWorkImpl extends AbstractUnitOfWork { - @SuppressWarnings("unchecked") - public UnitOfWorkImpl(HelenusSession session, UnitOfWork parent) { - super(session, (AbstractUnitOfWork) parent); - } - + @SuppressWarnings("unchecked") + public UnitOfWorkImpl(HelenusSession session, UnitOfWork parent) { + super(session, (AbstractUnitOfWork) parent); + } } diff --git a/src/main/java/net/helenus/core/operation/AbstractFilterOptionalOperation.java b/src/main/java/net/helenus/core/operation/AbstractFilterOptionalOperation.java index 8ed14af..6abfd81 100644 --- a/src/main/java/net/helenus/core/operation/AbstractFilterOptionalOperation.java +++ b/src/main/java/net/helenus/core/operation/AbstractFilterOptionalOperation.java @@ -19,11 +19,11 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; - import net.helenus.core.*; import net.helenus.mapping.HelenusProperty; -public abstract class AbstractFilterOptionalOperation> +public abstract class AbstractFilterOptionalOperation< + E, O extends AbstractFilterOptionalOperation> extends AbstractOptionalOperation { protected Map> filters = null; diff --git a/src/main/java/net/helenus/core/operation/AbstractFilterStreamOperation.java b/src/main/java/net/helenus/core/operation/AbstractFilterStreamOperation.java index 755e220..b78daf1 100644 --- a/src/main/java/net/helenus/core/operation/AbstractFilterStreamOperation.java +++ b/src/main/java/net/helenus/core/operation/AbstractFilterStreamOperation.java @@ -19,11 +19,11 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; - import net.helenus.core.*; import net.helenus.mapping.HelenusProperty; -public abstract class AbstractFilterStreamOperation> +public abstract class AbstractFilterStreamOperation< + E, O extends AbstractFilterStreamOperation> extends AbstractStreamOperation { protected Map> filters = null; diff --git a/src/main/java/net/helenus/core/operation/AbstractOperation.java b/src/main/java/net/helenus/core/operation/AbstractOperation.java index 46739b9..8eb7961 100644 --- a/src/main/java/net/helenus/core/operation/AbstractOperation.java +++ b/src/main/java/net/helenus/core/operation/AbstractOperation.java @@ -17,8 +17,6 @@ package net.helenus.core.operation; import com.codahale.metrics.Timer; import com.datastax.driver.core.ResultSet; - -import java.util.Objects; import java.util.concurrent.CompletableFuture; import net.helenus.core.AbstractSessionOperations; import net.helenus.core.UnitOfWork; @@ -40,7 +38,6 @@ public abstract class AbstractOperation> return new PreparedOperation(prepareStatement(), this); } - public E sync() { final Timer.Context context = requestLatency.time(); try { @@ -72,5 +69,4 @@ public abstract class AbstractOperation> if (uow == null) return async(); return CompletableFuture.supplyAsync(() -> sync(uow)); } - } diff --git a/src/main/java/net/helenus/core/operation/AbstractOptionalOperation.java b/src/main/java/net/helenus/core/operation/AbstractOptionalOperation.java index 93ec79d..1d34223 100644 --- a/src/main/java/net/helenus/core/operation/AbstractOptionalOperation.java +++ b/src/main/java/net/helenus/core/operation/AbstractOptionalOperation.java @@ -21,12 +21,10 @@ import com.datastax.driver.core.ResultSet; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; - import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; - import net.helenus.core.AbstractSessionOperations; import net.helenus.core.UnitOfWork; @@ -114,5 +112,4 @@ public abstract class AbstractOptionalOperation>supplyAsync(() -> sync(uow)); } - } diff --git a/src/main/java/net/helenus/core/operation/AbstractStatementOperation.java b/src/main/java/net/helenus/core/operation/AbstractStatementOperation.java index 19a0682..5177724 100644 --- a/src/main/java/net/helenus/core/operation/AbstractStatementOperation.java +++ b/src/main/java/net/helenus/core/operation/AbstractStatementOperation.java @@ -32,7 +32,8 @@ import net.helenus.support.HelenusException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class AbstractStatementOperation> extends Operation { +public abstract class AbstractStatementOperation> + extends Operation { final Logger logger = LoggerFactory.getLogger(getClass()); @@ -44,6 +45,7 @@ public abstract class AbstractStatementOperation> extends AbstractStatementOperation { @@ -113,5 +104,4 @@ public abstract class AbstractStreamOperation>supplyAsync(() -> sync(uow)); } - } diff --git a/src/main/java/net/helenus/core/operation/BoundOptionalOperation.java b/src/main/java/net/helenus/core/operation/BoundOptionalOperation.java index 4230bab..c3f5332 100644 --- a/src/main/java/net/helenus/core/operation/BoundOptionalOperation.java +++ b/src/main/java/net/helenus/core/operation/BoundOptionalOperation.java @@ -34,7 +34,9 @@ public final class BoundOptionalOperation } @Override - public Optional transform(ResultSet resultSet) { return delegate.transform(resultSet); } + public Optional transform(ResultSet resultSet) { + return delegate.transform(resultSet); + } @Override public Statement buildStatement(boolean cached) { diff --git a/src/main/java/net/helenus/core/operation/BoundStreamOperation.java b/src/main/java/net/helenus/core/operation/BoundStreamOperation.java index 4210b04..7a3cf2c 100644 --- a/src/main/java/net/helenus/core/operation/BoundStreamOperation.java +++ b/src/main/java/net/helenus/core/operation/BoundStreamOperation.java @@ -26,14 +26,17 @@ public final class BoundStreamOperation private final BoundStatement boundStatement; private final AbstractStreamOperation delegate; - public BoundStreamOperation(BoundStatement boundStatement, AbstractStreamOperation operation) { + public BoundStreamOperation( + BoundStatement boundStatement, AbstractStreamOperation operation) { super(operation.sessionOps); this.boundStatement = boundStatement; this.delegate = operation; } @Override - public String getStatementCacheKey() { return delegate.getStatementCacheKey(); } + public String getStatementCacheKey() { + return delegate.getStatementCacheKey(); + } @Override public Stream transform(ResultSet resultSet) { @@ -41,5 +44,7 @@ public final class BoundStreamOperation } @Override - public Statement buildStatement(boolean cached) { return boundStatement; } + public Statement buildStatement(boolean cached) { + return boundStatement; + } } diff --git a/src/main/java/net/helenus/core/operation/InsertOperation.java b/src/main/java/net/helenus/core/operation/InsertOperation.java index 52ad767..3ee0c8a 100644 --- a/src/main/java/net/helenus/core/operation/InsertOperation.java +++ b/src/main/java/net/helenus/core/operation/InsertOperation.java @@ -19,10 +19,9 @@ import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.querybuilder.BuiltStatement; import com.datastax.driver.core.querybuilder.Insert; import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.google.common.base.Joiner; import java.util.*; import java.util.function.Function; - -import com.google.common.base.Joiner; import net.helenus.core.AbstractSessionOperations; import net.helenus.core.Getter; import net.helenus.core.Helenus; @@ -41,7 +40,8 @@ public final class InsertOperation extends AbstractOperation> values = new ArrayList>(); + private final List> values = + new ArrayList>(); private final T pojo; private final Class resultType; private boolean ifNotExists; @@ -57,7 +57,8 @@ public final class InsertOperation extends AbstractOperation resultType, boolean ifNotExists) { + public InsertOperation( + AbstractSessionOperations sessionOperations, Class resultType, boolean ifNotExists) { super(sessionOperations); this.ifNotExists = ifNotExists; @@ -172,7 +173,7 @@ public final class InsertOperation extends AbstractOperation> converter = - prop.getReadConverter(sessionOps.getSessionRepository()); + prop.getReadConverter(sessionOps.getSessionRepository()); if (converter.isPresent()) { backingMap.put(key, converter.get().apply(backingMap.get(key))); } @@ -232,22 +233,25 @@ public final class InsertOperation extends AbstractOperation keys = new ArrayList<>(values.size()); values.forEach( - t -> { - HelenusPropertyNode prop = t._1; - switch (prop.getProperty().getColumnType()) { - case PARTITION_KEY: - case CLUSTERING_COLUMN: - keys.add(prop.getColumnName() + "==" + t._2.toString()); - break; - default: - break; - } - }); + t -> { + HelenusPropertyNode prop = t._1; + switch (prop.getProperty().getColumnType()) { + case PARTITION_KEY: + case CLUSTERING_COLUMN: + keys.add(prop.getColumnName() + "==" + t._2.toString()); + break; + default: + break; + } + }); return entity.getName() + ": " + Joiner.on(",").join(keys); } @Override public T sync(UnitOfWork uow) { + if (uow == null) { + return sync(); + } T result = super.sync(uow); Class iface = entity.getMappingInterface(); if (resultType == iface) { @@ -260,5 +264,4 @@ public final class InsertOperation extends AbstractOperation { - protected final AbstractSessionOperations sessionOps; - protected final Meter uowCacheHits; - protected final Meter uowCacheMiss; - protected final Timer requestLatency; + protected final AbstractSessionOperations sessionOps; + protected final Meter uowCacheHits; + protected final Meter uowCacheMiss; + protected final Timer requestLatency; - Operation(AbstractSessionOperations sessionOperations) { - this.sessionOps = sessionOperations; - MetricRegistry metrics = sessionOperations.getMetricRegistry(); - this.uowCacheHits = metrics.meter("net.helenus.UOW-cache-hits"); - this.uowCacheMiss = metrics.meter("net.helenus.UOW-cache-miss"); - this.requestLatency = metrics.timer("net.helenus.request-latency"); + Operation(AbstractSessionOperations sessionOperations) { + this.sessionOps = sessionOperations; + MetricRegistry metrics = sessionOperations.getMetricRegistry(); + this.uowCacheHits = metrics.meter("net.helenus.UOW-cache-hits"); + this.uowCacheMiss = metrics.meter("net.helenus.UOW-cache-miss"); + this.requestLatency = metrics.timer("net.helenus.request-latency"); + } + + public ResultSet execute( + AbstractSessionOperations session, + UnitOfWork uow, + TraceContext traceContext, + boolean showValues, + boolean cached) { + + // Start recording in a Zipkin sub-span our execution time to perform this operation. + Tracer tracer = session.getZipkinTracer(); + Span span = null; + if (tracer != null && traceContext != null) { + span = tracer.newChild(traceContext); } - public ResultSet execute(AbstractSessionOperations session, UnitOfWork uow, TraceContext traceContext, boolean showValues, boolean cached) { + try { - // Start recording in a Zipkin sub-span our execution time to perform this operation. - Tracer tracer = session.getZipkinTracer(); - Span span = null; - if (tracer != null && traceContext != null) { - span = tracer.newChild(traceContext); - } + if (span != null) { + span.name("cassandra"); + span.start(); + } - try { + Statement statement = options(buildStatement(cached)); + ResultSetFuture futureResultSet = session.executeAsync(statement, showValues); + return futureResultSet.get(); - if (span != null) { - span.name("cassandra"); - span.start(); - } + } catch (InterruptedException | ExecutionException e) { - Statement statement = options(buildStatement(cached)); - ResultSetFuture futureResultSet = session.executeAsync(statement, showValues); - return futureResultSet.get(); + throw new RuntimeException(e); - } catch (InterruptedException | ExecutionException e) { + } finally { - throw new RuntimeException(e); - - } finally { - - if (span != null) { - span.finish(); - } - - } + if (span != null) { + span.finish(); + } } + } - public Statement options(Statement statement) { return statement; } + public Statement options(Statement statement) { + return statement; + } - public Statement buildStatement(boolean cached) { return null; } - - public String getStatementCacheKey() { return null; } + public Statement buildStatement(boolean cached) { + return null; + } + public String getStatementCacheKey() { + return null; + } } diff --git a/src/main/java/net/helenus/core/operation/PreparedStreamOperation.java b/src/main/java/net/helenus/core/operation/PreparedStreamOperation.java index b622874..cd0f6be 100644 --- a/src/main/java/net/helenus/core/operation/PreparedStreamOperation.java +++ b/src/main/java/net/helenus/core/operation/PreparedStreamOperation.java @@ -17,7 +17,6 @@ package net.helenus.core.operation; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.PreparedStatement; -import java.util.regex.Pattern; public final class PreparedStreamOperation { diff --git a/src/main/java/net/helenus/core/operation/SelectFirstOperation.java b/src/main/java/net/helenus/core/operation/SelectFirstOperation.java index ff88555..d5cf01e 100644 --- a/src/main/java/net/helenus/core/operation/SelectFirstOperation.java +++ b/src/main/java/net/helenus/core/operation/SelectFirstOperation.java @@ -38,7 +38,9 @@ public final class SelectFirstOperation } @Override - public String getStatementCacheKey() { return delegate.getStatementCacheKey(); } + public String getStatementCacheKey() { + return delegate.getStatementCacheKey(); + } @Override public BuiltStatement buildStatement(boolean cached) { diff --git a/src/main/java/net/helenus/core/operation/SelectFirstTransformingOperation.java b/src/main/java/net/helenus/core/operation/SelectFirstTransformingOperation.java index 1d95256..8ef4f60 100644 --- a/src/main/java/net/helenus/core/operation/SelectFirstTransformingOperation.java +++ b/src/main/java/net/helenus/core/operation/SelectFirstTransformingOperation.java @@ -36,7 +36,9 @@ public final class SelectFirstTransformingOperation } @Override - public String getStatementCacheKey() { return delegate.getStatementCacheKey(); } + public String getStatementCacheKey() { + return delegate.getStatementCacheKey(); + } @Override public BuiltStatement buildStatement(boolean cached) { diff --git a/src/main/java/net/helenus/core/operation/SelectOperation.java b/src/main/java/net/helenus/core/operation/SelectOperation.java index 05403d1..e483659 100644 --- a/src/main/java/net/helenus/core/operation/SelectOperation.java +++ b/src/main/java/net/helenus/core/operation/SelectOperation.java @@ -23,13 +23,12 @@ import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.querybuilder.Select.Selection; import com.datastax.driver.core.querybuilder.Select.Where; +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; import java.util.*; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; - -import com.google.common.base.Joiner; -import com.google.common.collect.Iterables; import net.helenus.core.*; import net.helenus.core.reflect.HelenusPropertyNode; import net.helenus.mapping.HelenusEntity; @@ -48,6 +47,7 @@ public final class SelectOperation extends AbstractFilterStreamOperation ordering = null; protected Integer limit = null; protected boolean allowFiltering = false; + protected String alternateTableName = null; @SuppressWarnings("unchecked") public SelectOperation(AbstractSessionOperations sessionOperations) { @@ -129,6 +129,19 @@ public final class SelectOperation extends AbstractFilterStreamOperation SelectOperation from(Class materializedViewClass) { + Objects.requireNonNull(materializedViewClass); + HelenusEntity entity = Helenus.entity(materializedViewClass); + this.alternateTableName = entity.getName().toCql(); + this.allowFiltering = true; + return this; + } + + public SelectOperation from(String alternateTableName) { + this.alternateTableName = alternateTableName; + return this; + } + public SelectFirstOperation single() { limit(1); return new SelectFirstOperation(this); @@ -189,7 +202,6 @@ public final class SelectOperation extends AbstractFilterStreamOperation extends AbstractFilterStreamOperation extends AbstractFilterStreamOperation extends AbstractFilterStreamOperation transform(ResultSet resultSet) { if (rowMapper != null) { - return StreamSupport.stream(Spliterators.spliteratorUnknownSize(resultSet.iterator(), Spliterator.ORDERED), false).map(rowMapper); + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(resultSet.iterator(), Spliterator.ORDERED), false) + .map(rowMapper); } else { return (Stream) - StreamSupport.stream(Spliterators.spliteratorUnknownSize(resultSet.iterator(), Spliterator.ORDERED),false); + StreamSupport.stream( + Spliterators.spliteratorUnknownSize(resultSet.iterator(), Spliterator.ORDERED), + false); } } diff --git a/src/main/java/net/helenus/core/operation/SelectTransformingOperation.java b/src/main/java/net/helenus/core/operation/SelectTransformingOperation.java index 8883f7e..f93ca41 100644 --- a/src/main/java/net/helenus/core/operation/SelectTransformingOperation.java +++ b/src/main/java/net/helenus/core/operation/SelectTransformingOperation.java @@ -36,7 +36,9 @@ public final class SelectTransformingOperation } @Override - public String getStatementCacheKey() { return delegate.getStatementCacheKey(); } + public String getStatementCacheKey() { + return delegate.getStatementCacheKey(); + } @Override public BuiltStatement buildStatement(boolean cached) { @@ -47,5 +49,4 @@ public final class SelectTransformingOperation public Stream transform(ResultSet resultSet) { return delegate.transform(resultSet).map(fn); } - } diff --git a/src/main/java/net/helenus/core/operation/UpdateOperation.java b/src/main/java/net/helenus/core/operation/UpdateOperation.java index f04c90e..b918bc6 100644 --- a/src/main/java/net/helenus/core/operation/UpdateOperation.java +++ b/src/main/java/net/helenus/core/operation/UpdateOperation.java @@ -15,15 +15,13 @@ */ package net.helenus.core.operation; -import java.util.*; -import java.util.function.Function; - import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.querybuilder.Assignment; import com.datastax.driver.core.querybuilder.BuiltStatement; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Update; - +import java.util.*; +import java.util.function.Function; import net.helenus.core.*; import net.helenus.core.reflect.HelenusPropertyNode; import net.helenus.mapping.HelenusEntity; @@ -43,19 +41,21 @@ public final class UpdateOperation extends AbstractFilterOperation draft) { + public UpdateOperation( + AbstractSessionOperations sessionOperations, AbstractEntityDraft draft) { super(sessionOperations); this.draft = draft; this.draftMap = draft.toMap(); } - public UpdateOperation(AbstractSessionOperations sessionOperations, HelenusPropertyNode p, Object v) { + public UpdateOperation( + AbstractSessionOperations sessionOperations, HelenusPropertyNode p, Object v) { super(sessionOperations); this.draft = null; this.draftMap = null; @@ -152,7 +152,7 @@ public final class UpdateOperation extends AbstractFilterOperation list = (List)draftMap.get(key); + List list = (List) draftMap.get(key); list.add(0, value); } @@ -194,7 +194,7 @@ public final class UpdateOperation extends AbstractFilterOperation list = (List)draftMap.get(key); + List list = (List) draftMap.get(key); if (idx < 0) { list.add(0, value); } else if (idx > list.size()) { @@ -222,7 +222,7 @@ public final class UpdateOperation extends AbstractFilterOperation list = (List)draftMap.get(key); + List list = (List) draftMap.get(key); list.add(value); } @@ -579,6 +579,9 @@ public final class UpdateOperation extends AbstractFilterOperation extends AbstractFilterOperation implements InvocationHandler { - private final HelenusEntity entity; + private HelenusEntity entity = null; + private Metadata metadata = null; + + private final Class iface; + private final ClassLoader classLoader; + private final Optional parent; private final Map map = new HashMap(); @@ -48,18 +53,46 @@ public class DslInvocationHandler implements InvocationHandler { Optional parent, Metadata metadata) { - this.entity = new HelenusMappingEntity(iface, metadata); + this.metadata = metadata; this.parent = parent; + this.iface = iface; + this.classLoader = classLoader; + } - if (this.entity != null) { - for (HelenusProperty prop : entity.getOrderedProperties()) { + public void setCassandraMetadataForHelenusSesion(Metadata metadata) { + if (metadata != null) { + this.metadata = metadata; + entity = init(metadata); + } + } - map.put(prop.getGetterMethod(), prop); + private HelenusEntity init(Metadata metadata) { + HelenusEntity entity = new HelenusMappingEntity(iface, metadata); - AbstractDataType type = prop.getDataType(); - Class javaType = prop.getJavaType(); + for (HelenusProperty prop : entity.getOrderedProperties()) { - if (type instanceof UDTDataType && !UDTValue.class.isAssignableFrom(javaType)) { + map.put(prop.getGetterMethod(), prop); + + AbstractDataType type = prop.getDataType(); + Class javaType = prop.getJavaType(); + + if (type instanceof UDTDataType && !UDTValue.class.isAssignableFrom(javaType)) { + + Object childDsl = + Helenus.dsl( + javaType, + classLoader, + Optional.of(new HelenusPropertyNode(prop, parent)), + metadata); + + udtMap.put(prop.getGetterMethod(), childDsl); + } + + if (type instanceof DTDataType) { + DTDataType dataType = (DTDataType) type; + + if (dataType.getDataType() instanceof TupleType + && !TupleValue.class.isAssignableFrom(javaType)) { Object childDsl = Helenus.dsl( @@ -68,32 +101,18 @@ public class DslInvocationHandler implements InvocationHandler { Optional.of(new HelenusPropertyNode(prop, parent)), metadata); - udtMap.put(prop.getGetterMethod(), childDsl); - } - - if (type instanceof DTDataType) { - DTDataType dataType = (DTDataType) type; - - if (dataType.getDataType() instanceof TupleType - && !TupleValue.class.isAssignableFrom(javaType)) { - - Object childDsl = - Helenus.dsl( - javaType, - classLoader, - Optional.of(new HelenusPropertyNode(prop, parent)), - metadata); - - tupleMap.put(prop.getGetterMethod(), childDsl); - } + tupleMap.put(prop.getGetterMethod(), childDsl); } } } + + return entity; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + HelenusEntity entity = this.entity; String methodName = method.getName(); if ("equals".equals(methodName) && method.getParameterCount() == 1) { @@ -107,6 +126,15 @@ public class DslInvocationHandler implements InvocationHandler { return false; } + if (DslExportable.SET_METADATA_METHOD.equals(methodName) + && args.length == 1 + && args[0] instanceof Metadata) { + if (metadata == null) { + this.setCassandraMetadataForHelenusSesion((Metadata) args[0]); + } + return null; + } + if (method.getParameterCount() != 0 || method.getReturnType() == void.class) { throw new HelenusException("invalid getter method " + method); } @@ -115,6 +143,14 @@ public class DslInvocationHandler implements InvocationHandler { return hashCode(); } + if (DslExportable.GET_PARENT_METHOD.equals(methodName)) { + return parent.get(); + } + + if (entity == null) { + entity = init(metadata); + } + if ("toString".equals(methodName)) { return entity.toString(); } @@ -123,10 +159,6 @@ public class DslInvocationHandler implements InvocationHandler { return entity; } - if (DslExportable.GET_PARENT_METHOD.equals(methodName)) { - return parent.get(); - } - HelenusProperty prop = map.get(method); if (prop == null) { prop = entity.getProperty(methodName); diff --git a/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java b/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java index 7df8a51..4845c11 100644 --- a/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java +++ b/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collections; import java.util.Map; +import net.helenus.core.Helenus; import net.helenus.mapping.annotation.Transient; import net.helenus.support.HelenusException; @@ -91,6 +92,10 @@ public class MapperInvocationHandler implements InvocationHandler, Serializab return iface.getSimpleName() + ": " + src.toString(); } + if ("dsl".equals(methodName)) { + return Helenus.dsl(iface); + } + if (MapExportable.TO_MAP_METHOD.equals(methodName)) { return Collections.unmodifiableMap(src); } diff --git a/src/main/java/net/helenus/mapping/HelenusEntityType.java b/src/main/java/net/helenus/mapping/HelenusEntityType.java index 1d93991..2ef8d63 100644 --- a/src/main/java/net/helenus/mapping/HelenusEntityType.java +++ b/src/main/java/net/helenus/mapping/HelenusEntityType.java @@ -17,6 +17,7 @@ package net.helenus.mapping; public enum HelenusEntityType { TABLE, + VIEW, TUPLE, UDT; } diff --git a/src/main/java/net/helenus/mapping/HelenusMappingEntity.java b/src/main/java/net/helenus/mapping/HelenusMappingEntity.java index 75a967d..570d7b9 100644 --- a/src/main/java/net/helenus/mapping/HelenusMappingEntity.java +++ b/src/main/java/net/helenus/mapping/HelenusMappingEntity.java @@ -25,6 +25,7 @@ import net.helenus.core.Helenus; import net.helenus.core.annotation.Cacheable; import net.helenus.mapping.annotation.*; import net.helenus.support.HelenusMappingException; +import org.apache.commons.lang3.ClassUtils; public final class HelenusMappingEntity implements HelenusEntity { @@ -52,18 +53,33 @@ public final class HelenusMappingEntity implements HelenusEntity { HelenusSettings settings = Helenus.settings(); - List methods = new ArrayList(); + Map methods = new HashMap(); + for (Method m : iface.getDeclaredMethods()) { + methods.put(m.getName(), m); + } - methods.addAll(Arrays.asList(iface.getDeclaredMethods())); - for (Class c : iface.getInterfaces()) { - methods.addAll(Arrays.asList(c.getDeclaredMethods())); + for (Class c : ClassUtils.getAllInterfaces(iface)) { + if (c.getDeclaredAnnotation(Table.class) != null + || c.getDeclaredAnnotation(InheritedTable.class) != null) { + for (Method m : c.getDeclaredMethods()) { + Method o = methods.get(m.getName()); + if (o != null) { + // Prefer overridden method implementation. + if (o.getDeclaringClass().isAssignableFrom(m.getDeclaringClass())) { + methods.put(m.getName(), m); + } + } else { + methods.put(m.getName(), m); + } + } + } } List propsLocal = new ArrayList(); ImmutableMap.Builder propsBuilder = ImmutableMap.builder(); ImmutableMap.Builder methodsBuilder = ImmutableMap.builder(); - for (Method method : methods) { + for (Method method : methods.values()) { if (settings.getGetterMethodDetector().apply(method)) { @@ -130,6 +146,9 @@ public final class HelenusMappingEntity implements HelenusEntity { case TABLE: return MappingUtil.getTableName(iface, true); + case VIEW: + return MappingUtil.getViewName(iface, true); + case TUPLE: return IdentityName.of(MappingUtil.getDefaultEntityName(iface), false); @@ -146,6 +165,8 @@ public final class HelenusMappingEntity implements HelenusEntity { if (null != iface.getDeclaredAnnotation(Table.class)) { return HelenusEntityType.TABLE; + } else if (null != iface.getDeclaredAnnotation(MaterializedView.class)) { + return HelenusEntityType.VIEW; } else if (null != iface.getDeclaredAnnotation(Tuple.class)) { return HelenusEntityType.TUPLE; } else if (null != iface.getDeclaredAnnotation(UDT.class)) { diff --git a/src/main/java/net/helenus/mapping/MappingUtil.java b/src/main/java/net/helenus/mapping/MappingUtil.java index 197acd3..cf84c05 100644 --- a/src/main/java/net/helenus/mapping/MappingUtil.java +++ b/src/main/java/net/helenus/mapping/MappingUtil.java @@ -25,10 +25,7 @@ import javax.validation.ConstraintValidator; import net.helenus.core.Getter; import net.helenus.core.Helenus; import net.helenus.core.reflect.*; -import net.helenus.mapping.annotation.Index; -import net.helenus.mapping.annotation.Table; -import net.helenus.mapping.annotation.Tuple; -import net.helenus.mapping.annotation.UDT; +import net.helenus.mapping.annotation.*; import net.helenus.support.DslPropertyException; import net.helenus.support.HelenusMappingException; @@ -172,6 +169,28 @@ public final class MappingUtil { return udt != null; } + public static IdentityName getViewName(Class iface, boolean required) { + + String viewName = null; + boolean forceQuote = false; + + MaterializedView view = iface.getDeclaredAnnotation(MaterializedView.class); + + if (view != null) { + viewName = view.value(); + forceQuote = view.forceQuote(); + + } else if (required) { + throw new HelenusMappingException("entity must have annotation @Table " + iface); + } + + if (viewName == null || viewName.isEmpty()) { + viewName = getDefaultEntityName(iface); + } + + return new IdentityName(viewName, forceQuote); + } + public static IdentityName getTableName(Class iface, boolean required) { String tableName = null; @@ -222,6 +241,7 @@ public final class MappingUtil { } if (iface.getDeclaredAnnotation(Table.class) != null + || iface.getDeclaredAnnotation(MaterializedView.class) != null || iface.getDeclaredAnnotation(UDT.class) != null || iface.getDeclaredAnnotation(Tuple.class) != null) { diff --git a/src/main/java/net/helenus/mapping/annotation/CoveringIndex.java b/src/main/java/net/helenus/mapping/annotation/CoveringIndex.java new file mode 100644 index 0000000..6271e8e --- /dev/null +++ b/src/main/java/net/helenus/mapping/annotation/CoveringIndex.java @@ -0,0 +1,50 @@ +package net.helenus.mapping.annotation; + +import java.lang.annotation.*; + +/** + * CoveringIndex annotation is using under the specific column or method in entity interface + * with @Table annotation. + * + *

A corresponding materialized view will be created based on the underline @Table for the + * specific column. + * + *

This is useful when you need to perform IN or SORT/ORDER-BY queries and to do so you'll need + * different materialized table on disk in Cassandra. + * + *

For each @Table annotated interface Helenus will create/update/verify Cassandra Materialized + * Views and some indexes if needed on startup. + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface CoveringIndex { + + /** + * Defined the name of the index. By default the entity name with column name as suffix. + * + * @return name of the covering index + */ + String name() default ""; + + /** + * Set of fields in this entity to replicate in the index. + * + * @return array of the string names of the fields. + */ + String[] covering() default ""; + + /** + * Set of fields to use as the partition keys for this projection. + * + * @return array of the string names of the fields. + */ + String[] partitionKeys() default ""; + + /** + * Set of fields to use as the clustering columns for this projection. + * + * @return array of the string names of the fields. + */ + String[] clusteringColumns() default ""; +} diff --git a/src/main/java/net/helenus/mapping/annotation/MaterializedView.java b/src/main/java/net/helenus/mapping/annotation/MaterializedView.java new file mode 100644 index 0000000..112d8ce --- /dev/null +++ b/src/main/java/net/helenus/mapping/annotation/MaterializedView.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Helenus Authors + * + * 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 net.helenus.mapping.annotation; + +import java.lang.annotation.*; + +/** + * Materialized alternate view of another Entity annotation + * + *

MaterializedView annotation is used to define different mapping to some other Table interface + * + *

This is useful when you need to perform IN or SORT/ORDER-BY queries and to do so you'll need + * different materialized table on disk in Cassandra. + * + *

For each @Table annotated interface Helenus will create/update/verify Cassandra Materialized + * Views and some indexes if needed on startup. + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface MaterializedView { + + /** + * Default value is the SimpleName of the interface normalized to underscore + * + * @return name of the type + */ + String value() default ""; + + /** + * For reserved words in Cassandra we need quotation in CQL queries. This property marks that the + * name of the type needs to be quoted. + * + *

Default value is false, we are quoting only selected names. + * + * @return true if name have to be quoted + */ + boolean forceQuote() default false; +} diff --git a/src/main/java/net/helenus/mapping/javatype/AbstractCollectionJavaType.java b/src/main/java/net/helenus/mapping/javatype/AbstractCollectionJavaType.java index 61a764a..a882398 100644 --- a/src/main/java/net/helenus/mapping/javatype/AbstractCollectionJavaType.java +++ b/src/main/java/net/helenus/mapping/javatype/AbstractCollectionJavaType.java @@ -2,6 +2,7 @@ package net.helenus.mapping.javatype; public abstract class AbstractCollectionJavaType extends AbstractJavaType { - public static boolean isCollectionType() { return true; } - + public static boolean isCollectionType() { + return true; + } } diff --git a/src/main/java/net/helenus/mapping/javatype/AbstractJavaType.java b/src/main/java/net/helenus/mapping/javatype/AbstractJavaType.java index 806fc2e..abd7e21 100644 --- a/src/main/java/net/helenus/mapping/javatype/AbstractJavaType.java +++ b/src/main/java/net/helenus/mapping/javatype/AbstractJavaType.java @@ -33,7 +33,9 @@ import net.helenus.support.HelenusMappingException; public abstract class AbstractJavaType { - public static boolean isCollectionType() { return false; } + public static boolean isCollectionType() { + return false; + } public abstract Class getJavaClass(); diff --git a/src/main/java/net/helenus/mapping/javatype/MapJavaType.java b/src/main/java/net/helenus/mapping/javatype/MapJavaType.java index 4d6e38f..2a5d187 100644 --- a/src/main/java/net/helenus/mapping/javatype/MapJavaType.java +++ b/src/main/java/net/helenus/mapping/javatype/MapJavaType.java @@ -18,7 +18,6 @@ package net.helenus.mapping.javatype; import com.datastax.driver.core.*; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.util.AbstractCollection; import java.util.Map; import java.util.Optional; import java.util.function.Function; diff --git a/src/main/java/net/helenus/mapping/type/AbstractCollectionDataType.java b/src/main/java/net/helenus/mapping/type/AbstractCollectionDataType.java index d138263..7e76d92 100644 --- a/src/main/java/net/helenus/mapping/type/AbstractCollectionDataType.java +++ b/src/main/java/net/helenus/mapping/type/AbstractCollectionDataType.java @@ -4,10 +4,11 @@ import net.helenus.mapping.ColumnType; public abstract class AbstractCollectionDataType extends AbstractDataType { - public AbstractCollectionDataType(ColumnType columnType) { - super(columnType); - } - - public boolean isCollectionType() { return true; } + public AbstractCollectionDataType(ColumnType columnType) { + super(columnType); + } + public boolean isCollectionType() { + return true; + } } diff --git a/src/main/java/net/helenus/mapping/type/AbstractDataType.java b/src/main/java/net/helenus/mapping/type/AbstractDataType.java index 8be0fd1..cc65d68 100644 --- a/src/main/java/net/helenus/mapping/type/AbstractDataType.java +++ b/src/main/java/net/helenus/mapping/type/AbstractDataType.java @@ -55,6 +55,7 @@ public abstract class AbstractDataType { "wrong column type " + columnType + " for UserDefinedType in columnName " + columnName); } - public boolean isCollectionType() { return false; } - + public boolean isCollectionType() { + return false; + } } diff --git a/src/main/java/net/helenus/mapping/type/UDTKeyMapDataType.java b/src/main/java/net/helenus/mapping/type/UDTKeyMapDataType.java index b9724aa..e04685c 100644 --- a/src/main/java/net/helenus/mapping/type/UDTKeyMapDataType.java +++ b/src/main/java/net/helenus/mapping/type/UDTKeyMapDataType.java @@ -18,8 +18,6 @@ package net.helenus.mapping.type; import com.datastax.driver.core.DataType; import com.datastax.driver.core.UserType; import com.datastax.driver.core.schemabuilder.*; - -import java.util.AbstractCollection; import java.util.List; import net.helenus.mapping.ColumnType; import net.helenus.mapping.IdentityName; diff --git a/src/main/java/net/helenus/mapping/value/BeanColumnValueProvider.java b/src/main/java/net/helenus/mapping/value/BeanColumnValueProvider.java index 2381514..a87fd15 100644 --- a/src/main/java/net/helenus/mapping/value/BeanColumnValueProvider.java +++ b/src/main/java/net/helenus/mapping/value/BeanColumnValueProvider.java @@ -33,7 +33,7 @@ public enum BeanColumnValueProvider implements ColumnValueProvider { try { value = getter.invoke(bean, new Object[] {}); } catch (InvocationTargetException e) { - if (e.getCause() != null ) { + if (e.getCause() != null) { throw new HelenusException("getter threw an exception", e.getCause()); } } catch (ReflectiveOperationException e) { diff --git a/src/test/java/net/helenus/test/integration/core/collection/CollectionTest.java b/src/test/java/net/helenus/test/integration/core/collection/CollectionTest.java index 5b437eb..e838ad3 100644 --- a/src/test/java/net/helenus/test/integration/core/collection/CollectionTest.java +++ b/src/test/java/net/helenus/test/integration/core/collection/CollectionTest.java @@ -67,11 +67,8 @@ public class CollectionTest extends AbstractEmbeddedCassandraTest { // read full object - Customer actual = session.select(customer) - .where(customer::id, eq(id)) - .single() - .sync() - .orElse(null); + Customer actual = + session.select(customer).where(customer::id, eq(id)).single().sync().orElse(null); Assert.assertEquals(id, actual.id()); Assert.assertEquals(aliases, actual.aliases()); Assert.assertNull(actual.names()); @@ -90,11 +87,8 @@ public class CollectionTest extends AbstractEmbeddedCassandraTest { session.update().set(customer::aliases, expected).where(customer::id, eq(id)).sync(); - actual = session.select(customer) - .where(customer::id, eq(id)) - .single() - .sync() - .orElse(null); + actual = + session.select(customer).where(customer::id, eq(id)).single().sync().orElse(null); Assert.assertEquals(id, actual.id()); Assert.assertEquals(expected, actual.aliases()); @@ -170,11 +164,8 @@ public class CollectionTest extends AbstractEmbeddedCassandraTest { // read full object - Customer actual = session.select(customer) - .where(customer::id, eq(id)) - .single() - .sync() - .orElse(null); + Customer actual = + session.select(customer).where(customer::id, eq(id)).single().sync().orElse(null); Assert.assertEquals(id, actual.id()); Assert.assertEquals(names, actual.names()); @@ -200,11 +191,8 @@ public class CollectionTest extends AbstractEmbeddedCassandraTest { session.update().set(customer::names, expected).where(customer::id, eq(id)).sync(); - actual = session.select(customer) - .where(customer::id, eq(id)) - .single() - .sync() - .orElse(null); + actual = + session.select(customer).where(customer::id, eq(id)).single().sync().orElse(null); Assert.assertEquals(id, actual.id()); Assert.assertEquals(expected, actual.names()); @@ -306,10 +294,8 @@ public class CollectionTest extends AbstractEmbeddedCassandraTest { // read full object - Customer actual = session.select(customer) - .where(customer::id, eq(id)).single() - .sync() - .orElse(null); + Customer actual = + session.select(customer).where(customer::id, eq(id)).single().sync().orElse(null); Assert.assertEquals(id, actual.id()); Assert.assertEquals(props, actual.properties()); @@ -343,11 +329,8 @@ public class CollectionTest extends AbstractEmbeddedCassandraTest { session.update().set(customer::properties, expected).where(customer::id, eq(id)).sync(); - actual = session.select(customer) - .where(customer::id, eq(id)) - .single() - .sync() - .orElse(null); + actual = + session.select(customer).where(customer::id, eq(id)).single().sync().orElse(null); Assert.assertEquals(id, actual.id()); Assert.assertEquals(expected, actual.properties()); diff --git a/src/test/java/net/helenus/test/integration/core/draft/EntityDraftBuilderTest.java b/src/test/java/net/helenus/test/integration/core/draft/EntityDraftBuilderTest.java index 2be5381..64f9c7b 100644 --- a/src/test/java/net/helenus/test/integration/core/draft/EntityDraftBuilderTest.java +++ b/src/test/java/net/helenus/test/integration/core/draft/EntityDraftBuilderTest.java @@ -18,76 +18,72 @@ package net.helenus.test.integration.core.draft; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; - +import net.helenus.core.Helenus; +import net.helenus.core.HelenusSession; +import net.helenus.test.integration.build.AbstractEmbeddedCassandraTest; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import net.helenus.core.Helenus; -import net.helenus.core.HelenusSession; -import net.helenus.test.integration.build.AbstractEmbeddedCassandraTest; - -import static net.helenus.core.Query.eq; - public class EntityDraftBuilderTest extends AbstractEmbeddedCassandraTest { + static Supply supply; + static HelenusSession session; - static Supply supply; - static HelenusSession session; + @BeforeClass + public static void beforeTest() { + session = Helenus.init(getSession()).showCql().add(Supply.class).autoCreateDrop().get(); + supply = session.dsl(Supply.class); + } + @Test + public void testFoo() throws Exception { + Supply.Draft draft = null; - @BeforeClass - public static void beforeTest() { - session = Helenus.init(getSession()) - .showCql() - .add(Supply.class) - .autoCreateDrop() - .get(); - supply = session.dsl(Supply.class); - } - - @Test - public void testFoo() throws Exception { - Supply.Draft draft = null; - - draft = Supply.draft("APAC") - .code("WIDGET-002") - .description("Our second Widget!") - .demand(new HashMap() {{ + draft = + Supply.draft("APAC") + .code("WIDGET-002") + .description("Our second Widget!") + .demand( + new HashMap() { + { put("APAC", 100L); put("EMEA", 10000L); put("NORAM", 2000000L); - }}) - .shipments(new HashSet() {{ + } + }) + .shipments( + new HashSet() { + { add("HMS Puddle in transit to APAC, 100 units."); add("Frigate Jimmy in transit to EMEA, 10000 units."); - }}) - .suppliers(new ArrayList() {{ + } + }) + .suppliers( + new ArrayList() { + { add("Puddle, Inc."); add("Jimmy Town, LTD."); - }}); + } + }); - Supply s1 = session.insert(draft) - .sync(); + Supply s1 = session.insert(draft).sync(); - // List - Supply s2 = session.update(s1.update()) - .prepend(supply::suppliers, "Pignose Supply, LLC.") - .sync(); - Assert.assertEquals(s2.suppliers().get(0), "Pignose Supply, LLC."); + // List + Supply s2 = + session + .update(s1.update()) + .prepend(supply::suppliers, "Pignose Supply, LLC.") + .sync(); + Assert.assertEquals(s2.suppliers().get(0), "Pignose Supply, LLC."); - // Set - String shipment = "Pignose, on the way! (1M units)"; - Supply s3 = session.update(s2.update()) - .add(supply::shipments, shipment) - .sync(); - Assert.assertTrue(s3.shipments().contains(shipment)); + // Set + String shipment = "Pignose, on the way! (1M units)"; + Supply s3 = session.update(s2.update()).add(supply::shipments, shipment).sync(); + Assert.assertTrue(s3.shipments().contains(shipment)); - // Map - Supply s4 = session.update(s3.update()) - .put(supply::demand, "NORAM", 10L) - .sync(); - Assert.assertEquals((long)s4.demand().get("NORAM"), 10L); - - } + // Map + Supply s4 = session.update(s3.update()).put(supply::demand, "NORAM", 10L).sync(); + Assert.assertEquals((long) s4.demand().get("NORAM"), 10L); + } } diff --git a/src/test/java/net/helenus/test/integration/core/draft/Inventory.java b/src/test/java/net/helenus/test/integration/core/draft/Inventory.java index a4f7a12..a8b5945 100644 --- a/src/test/java/net/helenus/test/integration/core/draft/Inventory.java +++ b/src/test/java/net/helenus/test/integration/core/draft/Inventory.java @@ -1,75 +1,93 @@ package net.helenus.test.integration.core.draft; import java.util.UUID; - import net.helenus.core.AbstractAuditedEntityDraft; +import net.helenus.core.Helenus; import net.helenus.core.reflect.MapExportable; import net.helenus.mapping.annotation.*; - @Table public interface Inventory { - @PartitionKey UUID id(); - @Column("emea") @Types.Counter long EMEA(); - @Column("noram") @Types.Counter long NORAM(); - @Column("apac") @Types.Counter long APAC(); + static Inventory inventory = Helenus.dsl(Inventory.class); - @Transient static Draft draft(UUID id) { return new Draft(id); } + @PartitionKey + UUID id(); - @Transient default Draft update() { return new Draft(this); } + @Column("emea") + @Types.Counter + long EMEA(); + @Column("noram") + @Types.Counter + long NORAM(); - class Draft extends AbstractAuditedEntityDraft { + @Column("apac") + @Types.Counter + long APAC(); - // Entity/Draft pattern-enabling methods: - Draft(UUID id) { - super(null); + @Transient + static Draft draft(UUID id) { + return new Draft(id); + } - // Primary Key: - set("id", id); - } + @Transient + default Draft update() { + return new Draft(this); + } - Draft(Inventory inventory) { - super((MapExportable) inventory); - } + class Draft extends AbstractAuditedEntityDraft { - public Class getEntityClass() { return Inventory.class; } - - protected String getCurrentAuditor() { return "unknown"; } - - // Immutable properties: - public UUID id() { - return this.get("id", UUID.class); - } - - public long EMEA() { - return this.get("EMEA", long.class); - } - - public Draft EMEA(long count) { - mutate("EMEA", count); - return this; - } - - public long APAC() { - return this.get("APAC", long.class); - } - - public Draft APAC(long count) { - mutate("APAC", count); - return this; - } - - public long NORAM() { - return this.get("NORAM", long.class); - } - - public Draft NORAM(long count) { - mutate("NORAM", count); - return this; - } + // Entity/Draft pattern-enabling methods: + Draft(UUID id) { + super(null); + // Primary Key: + set(inventory::id, id); } + Draft(Inventory inventory) { + super((MapExportable) inventory); + } + + public Class getEntityClass() { + return Inventory.class; + } + + protected String getCurrentAuditor() { + return "unknown"; + } + + // Immutable properties: + public UUID id() { + return this.get(inventory::id, UUID.class); + } + + public long EMEA() { + return this.get(inventory::EMEA, long.class); + } + + public Draft EMEA(long count) { + mutate(inventory::EMEA, count); + return this; + } + + public long APAC() { + return this.get(inventory::APAC, long.class); + } + + public Draft APAC(long count) { + mutate(inventory::APAC, count); + return this; + } + + public long NORAM() { + return this.get(inventory::NORAM, long.class); + } + + public Draft NORAM(long count) { + mutate(inventory::NORAM, count); + return this; + } + } } diff --git a/src/test/java/net/helenus/test/integration/core/draft/Supply.java b/src/test/java/net/helenus/test/integration/core/draft/Supply.java index 1b09724..72c0ba7 100644 --- a/src/test/java/net/helenus/test/integration/core/draft/Supply.java +++ b/src/test/java/net/helenus/test/integration/core/draft/Supply.java @@ -1,125 +1,145 @@ package net.helenus.test.integration.core.draft; +import com.datastax.driver.core.utils.UUIDs; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; - -import com.datastax.driver.core.utils.UUIDs; - import net.helenus.core.AbstractEntityDraft; +import net.helenus.core.Helenus; import net.helenus.core.reflect.MapExportable; import net.helenus.mapping.annotation.*; - @Table public interface Supply { - @PartitionKey UUID id(); - @ClusteringColumn(ordinal=0) default String region() { return "NORAM"; } + static Supply supply = Helenus.dsl(Supply.class); - @Index(caseSensitive = false) String code(); - @Index String description(); // @IndexText == lucene index - @Index Map demand(); - @Index List suppliers(); - @Index Set shipments(); + @PartitionKey + UUID id(); - @Transient static Draft draft(String region) { return new Draft(region); } + @ClusteringColumn(ordinal = 0) + default String region() { + return "NORAM"; + } - @Transient default Draft update() { return new Draft(this); } + @Index(caseSensitive = false) + String code(); + @Index + String description(); // @IndexText == lucene index - class Draft extends AbstractEntityDraft { + @Index + Map demand(); - // Entity/Draft pattern-enabling methods: - Draft(String region) { - super(null); + @Index + List suppliers(); - // Primary Key: - set("id", UUIDs.timeBased()); - set("region", region); - } + @Index + Set shipments(); - Draft(Supply supply) { - super((MapExportable) supply); - } + @Transient + static Draft draft(String region) { + return new Draft(region); + } - public Class getEntityClass() { return Supply.class; } + @Transient + default Draft update() { + return new Draft(this); + } - // Immutable properties: - public UUID id() { - return this.get("id", UUID.class); - } + class Draft extends AbstractEntityDraft { - public String region() { - return this.get("region", String.class); - } - - // Mutable properties: - public String code() { - return this.get("code", String.class); - } - - public Draft code(String code) { - mutate("code", code); - return this; - } - - public Draft setCode(String code) { - return code(code); - } - - public String description() { - return this.get("description", String.class); - } - - public Draft description(String description) { - mutate("description", description); - return this; - } - - public Draft setDescription(String description) { - return description(description); - } - - public Map demand() { - return this.>get("demand", Map.class); - } - - public Draft demand(Map demand) { - mutate("demand", demand); - return this; - } - - public Draft setDemand(Map demand) { - return demand(demand); - } - - public List suppliers() { - return this.>get("suppliers", List.class); - } - - public Draft suppliers(List suppliers) { - mutate("suppliers", suppliers); - return this; - } - - public Draft setSuppliers(List suppliers) { - return suppliers(suppliers); - } - - public Set shipments() { - return this.>get("shipments", Set.class); - } - - public Draft shipments(Set shipments) { - mutate("shipments", shipments); - return this; - } - - public Draft setshipments(Set shipments) { - return shipments(shipments); - } + // Entity/Draft pattern-enabling methods: + Draft(String region) { + super(null); + // Primary Key: + set(supply::id, UUIDs.timeBased()); + set(supply::region, region); } + + Draft(Supply supply) { + super((MapExportable) supply); + } + + public Class getEntityClass() { + return Supply.class; + } + + // Immutable properties: + public UUID id() { + return this.get(supply::id, UUID.class); + } + + public String region() { + return this.get(supply::region, String.class); + } + + // Mutable properties: + public String code() { + return this.get(supply::code, String.class); + } + + public Draft code(String code) { + mutate(supply::code, code); + return this; + } + + public Draft setCode(String code) { + return code(code); + } + + public String description() { + return this.get(supply::description, String.class); + } + + public Draft description(String description) { + mutate(supply::description, description); + return this; + } + + public Draft setDescription(String description) { + return description(description); + } + + public Map demand() { + return this.>get(supply::demand, Map.class); + } + + public Draft demand(Map demand) { + mutate(supply::demand, demand); + return this; + } + + public Draft setDemand(Map demand) { + return demand(demand); + } + + public List suppliers() { + return this.>get(supply::suppliers, List.class); + } + + public Draft suppliers(List suppliers) { + mutate(supply::suppliers, suppliers); + return this; + } + + public Draft setSuppliers(List suppliers) { + return suppliers(suppliers); + } + + public Set shipments() { + return this.>get(supply::shipments, Set.class); + } + + public Draft shipments(Set shipments) { + mutate(supply::shipments, shipments); + return this; + } + + public Draft setshipments(Set shipments) { + return shipments(shipments); + } + } } diff --git a/src/test/java/net/helenus/test/integration/core/hierarchy/Animal.java b/src/test/java/net/helenus/test/integration/core/hierarchy/Animal.java index 3e9cde4..fe6392f 100644 --- a/src/test/java/net/helenus/test/integration/core/hierarchy/Animal.java +++ b/src/test/java/net/helenus/test/integration/core/hierarchy/Animal.java @@ -29,6 +29,9 @@ public interface Animal { @Column(ordinal = 1) boolean eatable(); + @Column + boolean warmBlodded(); + @Transient default Animal me() { return this; diff --git a/src/test/java/net/helenus/test/integration/core/hierarchy/Cat.java b/src/test/java/net/helenus/test/integration/core/hierarchy/Cat.java index aef0582..d0089e0 100644 --- a/src/test/java/net/helenus/test/integration/core/hierarchy/Cat.java +++ b/src/test/java/net/helenus/test/integration/core/hierarchy/Cat.java @@ -20,7 +20,7 @@ import net.helenus.mapping.annotation.Index; import net.helenus.mapping.annotation.Table; @Table("cats") -public interface Cat extends Animal { +public interface Cat extends Mammal { @Column(ordinal = 0) @Index(caseSensitive = false) diff --git a/src/test/java/net/helenus/test/integration/core/hierarchy/HierarchyTest.java b/src/test/java/net/helenus/test/integration/core/hierarchy/HierarchyTest.java index 0fcb039..dce32b3 100644 --- a/src/test/java/net/helenus/test/integration/core/hierarchy/HierarchyTest.java +++ b/src/test/java/net/helenus/test/integration/core/hierarchy/HierarchyTest.java @@ -53,6 +53,7 @@ public class HierarchyTest extends AbstractEmbeddedCassandraTest { Optional animal = session.select(Cat.class).where(cat::nickname, eq("garfield")).sync().findFirst(); Assert.assertTrue(animal.isPresent()); + Assert.assertTrue(animal.get().warmBlodded()); Assert.assertFalse(animal.get().eatable()); } @@ -64,7 +65,8 @@ public class HierarchyTest extends AbstractEmbeddedCassandraTest { .value(cat::nickname, "garfield") .value(cat::eatable, false) .sync(); - Optional animal = session.select(Cat.class).where(cat::nickname, eq("garfield")).single().sync(); + Optional animal = + session.select(Cat.class).where(cat::nickname, eq("garfield")).single().sync(); Assert.assertTrue(animal.isPresent()); Cat cat = animal.get(); diff --git a/src/test/java/net/helenus/test/integration/core/hierarchy/Mammal.java b/src/test/java/net/helenus/test/integration/core/hierarchy/Mammal.java new file mode 100644 index 0000000..950287a --- /dev/null +++ b/src/test/java/net/helenus/test/integration/core/hierarchy/Mammal.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 The Helenus Authors + * + * 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 net.helenus.test.integration.core.hierarchy; + +import net.helenus.mapping.annotation.InheritedTable; + +@InheritedTable +public interface Mammal extends Animal { + + default boolean warmBlodded() { + return true; + } +} diff --git a/src/test/java/net/helenus/test/integration/core/hierarchy/Pig.java b/src/test/java/net/helenus/test/integration/core/hierarchy/Pig.java index fa569dc..8d12aff 100644 --- a/src/test/java/net/helenus/test/integration/core/hierarchy/Pig.java +++ b/src/test/java/net/helenus/test/integration/core/hierarchy/Pig.java @@ -19,7 +19,7 @@ import net.helenus.mapping.annotation.Column; import net.helenus.mapping.annotation.Table; @Table("pigs") -public interface Pig extends Animal { +public interface Pig extends Mammal { @Column(ordinal = 0) String nickname(); diff --git a/src/test/java/net/helenus/test/integration/core/simple/SimpleUserTest.java b/src/test/java/net/helenus/test/integration/core/simple/SimpleUserTest.java index 9773ad1..da7997d 100644 --- a/src/test/java/net/helenus/test/integration/core/simple/SimpleUserTest.java +++ b/src/test/java/net/helenus/test/integration/core/simple/SimpleUserTest.java @@ -18,21 +18,17 @@ package net.helenus.test.integration.core.simple; import static net.helenus.core.Query.eq; import com.datastax.driver.core.ResultSet; +import java.util.*; import net.helenus.core.Helenus; import net.helenus.core.HelenusSession; import net.helenus.core.Operator; import net.helenus.core.operation.UpdateOperation; -import net.helenus.core.reflect.Drafted; -import net.helenus.mapping.HelenusEntity; import net.helenus.support.Fun; import net.helenus.test.integration.build.AbstractEmbeddedCassandraTest; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import java.util.*; - - public class SimpleUserTest extends AbstractEmbeddedCassandraTest { static User user; @@ -102,11 +98,7 @@ public class SimpleUserTest extends AbstractEmbeddedCassandraTest { // select as object - actual = session.select(user) - .where(user::id, eq(100L)) - .single() - .sync() - .orElse(null); + actual = session.select(user).where(user::id, eq(100L)).single().sync().orElse(null); assertUsers(newUser, actual); // select by columns @@ -192,11 +184,7 @@ public class SimpleUserTest extends AbstractEmbeddedCassandraTest { Assert.assertEquals("_albert", name); - User u2 = session.select(user) - .where(user::id, eq(100L)) - .single() - .sync() - .orElse(null); + User u2 = session.select(user).where(user::id, eq(100L)).single().sync().orElse(null); Assert.assertEquals(Long.valueOf(100L), u2.id()); Assert.assertEquals("albert", u2.name()); @@ -204,31 +192,27 @@ public class SimpleUserTest extends AbstractEmbeddedCassandraTest { // User greg = - session - .insert(user) - .value(user::name, "greg") - .value(user::age, 44) - .value(user::type, UserType.USER) - .value(user::id, 1234L) - .sync(); + session + .insert(user) + .value(user::name, "greg") + .value(user::age, 44) + .value(user::type, UserType.USER) + .value(user::id, 1234L) + .sync(); Optional maybeGreg = - session - .select(user) - .where(user::id, eq(1234L)) - .single() - .sync(); + session.select(user).where(user::id, eq(1234L)).single().sync(); // INSERT session - .update() - .set(user::name, null) - .set(user::age, null) - .set(user::type, null) - .where(user::id, eq(100L)) - .zipkinContext(null) - .sync(); + .update() + .set(user::name, null) + .set(user::age, null) + .set(user::type, null) + .where(user::id, eq(100L)) + .zipkinContext(null) + .sync(); Fun.Tuple3 tuple = session @@ -252,21 +236,16 @@ public class SimpleUserTest extends AbstractEmbeddedCassandraTest { public void testZipkin() throws Exception { session - .update() - .set(user::name, null) - .set(user::age, null) - .set(user::type, null) - .where(user::id, eq(100L)) - .zipkinContext(null) - .sync(); - + .update() + .set(user::name, null) + .set(user::age, null) + .set(user::type, null) + .where(user::id, eq(100L)) + .zipkinContext(null) + .sync(); UpdateOperation update = session.update(); - update - .set(user::name, null) - .zipkinContext(null) - .sync(); - + update.set(user::name, null).zipkinContext(null).sync(); } private void assertUsers(User expected, User actual) { diff --git a/src/test/java/net/helenus/test/integration/core/tuplecollection/TupleMapTest.java b/src/test/java/net/helenus/test/integration/core/tuplecollection/TupleMapTest.java index 143e4f0..a77276d 100644 --- a/src/test/java/net/helenus/test/integration/core/tuplecollection/TupleMapTest.java +++ b/src/test/java/net/helenus/test/integration/core/tuplecollection/TupleMapTest.java @@ -42,11 +42,8 @@ public class TupleMapTest extends TupleCollectionTest { // read full object - Book actual = session.select(book) - .where(book::id, Query.eq(id)) - .single() - .sync() - .orElse(null); + Book actual = + session.select(book).where(book::id, Query.eq(id)).single().sync().orElse(null); Assert.assertEquals(id, actual.id()); assertEqualMaps(writers, actual.writers()); Assert.assertNull(actual.reviewers()); @@ -77,11 +74,7 @@ public class TupleMapTest extends TupleCollectionTest { session.update().set(book::writers, expected).where(book::id, Query.eq(id)).sync(); - actual = session.select(book) - .where(book::id, Query.eq(id)) - .single() - .sync() - .orElse(null); + actual = session.select(book).where(book::id, Query.eq(id)).single().sync().orElse(null); Assert.assertEquals(id, actual.id()); assertEqualMaps(expected, actual.writers()); @@ -96,7 +89,8 @@ public class TupleMapTest extends TupleCollectionTest { expected.put(third, unk); session.update().put(book::writers, third, unk).where(book::id, Query.eq(id)).sync(); - actualMap = session.select(book::writers).where(book::id, Query.eq(id)).sync().findFirst().get()._1; + actualMap = + session.select(book::writers).where(book::id, Query.eq(id)).sync().findFirst().get()._1; assertEqualMaps(expected, actualMap); // putAll operation diff --git a/src/test/java/net/helenus/test/integration/core/unitofwork/AndThenOrderTest.java b/src/test/java/net/helenus/test/integration/core/unitofwork/AndThenOrderTest.java index 37ba96a..2b3ebcd 100644 --- a/src/test/java/net/helenus/test/integration/core/unitofwork/AndThenOrderTest.java +++ b/src/test/java/net/helenus/test/integration/core/unitofwork/AndThenOrderTest.java @@ -15,6 +15,9 @@ */ package net.helenus.test.integration.core.unitofwork; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import net.helenus.core.Helenus; import net.helenus.core.HelenusSession; import net.helenus.core.UnitOfWork; @@ -23,102 +26,125 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - - public class AndThenOrderTest extends AbstractEmbeddedCassandraTest { - static HelenusSession session; + static HelenusSession session; - @BeforeClass - public static void beforeTest() { - session = Helenus.init(getSession()) - .showCql() - .autoCreateDrop() - .get(); - } + @BeforeClass + public static void beforeTest() { + session = Helenus.init(getSession()).showCql().autoCreateDrop().get(); + } - @Test - public void testAndThenOrdering() throws Exception { - List q = new ArrayList(5); - UnitOfWork uow1, uow2, uow3, uow4, uow5; + @Test + public void testAndThenOrdering() throws Exception { + List q = new ArrayList(5); + UnitOfWork uow1, uow2, uow3, uow4, uow5; - uow5 = session.begin(); - uow3 = session.begin(uow5); - uow1 = session.begin(uow3); - uow1.commit().andThen(() -> { q.add("1"); }); - uow2 = session.begin(uow3); - uow2.commit().andThen(() -> { q.add("2"); }); - uow3.commit().andThen(() -> { q.add("3"); }); - uow4 = session.begin(uow5); - uow4.commit().andThen(() -> { q.add("4"); }); - uow5.commit().andThen(() -> { q.add("5"); }); + uow5 = session.begin(); + uow3 = session.begin(uow5); + uow1 = session.begin(uow3); + uow1.commit() + .andThen( + () -> { + q.add("1"); + }); + uow2 = session.begin(uow3); + uow2.commit() + .andThen( + () -> { + q.add("2"); + }); + uow3.commit() + .andThen( + () -> { + q.add("3"); + }); + uow4 = session.begin(uow5); + uow4.commit() + .andThen( + () -> { + q.add("4"); + }); + uow5.commit() + .andThen( + () -> { + q.add("5"); + }); - System.out.println(q); - Assert.assertTrue(Arrays.equals(q.toArray(new String[5]), new String[] {"1", "2", "3", "4", "5"})); + System.out.println(q); + Assert.assertTrue( + Arrays.equals(q.toArray(new String[5]), new String[] {"1", "2", "3", "4", "5"})); + } - } + @Test + public void testExceptionWithinAndThen() throws Exception { + List q = new ArrayList(5); + UnitOfWork uow1, uow2, uow3, uow4, uow5; - @Test - public void testExceptionWithinAndThen() throws Exception { - List q = new ArrayList(5); - UnitOfWork uow1, uow2, uow3, uow4, uow5; - - uow5 = session.begin(); - uow4 = session.begin(uow5); - try { - uow3 = session.begin(uow4); - uow1 = session.begin(uow3); - uow1.commit().andThen(() -> { + uow5 = session.begin(); + uow4 = session.begin(uow5); + try { + uow3 = session.begin(uow4); + uow1 = session.begin(uow3); + uow1.commit() + .andThen( + () -> { q.add("1"); - }); - uow2 = session.begin(uow3); - uow2.commit().andThen(() -> { + }); + uow2 = session.begin(uow3); + uow2.commit() + .andThen( + () -> { q.add("2"); - }); - uow3.commit().andThen(() -> { + }); + uow3.commit() + .andThen( + () -> { q.add("3"); - }); - uow4.commit().andThen(() -> { + }); + uow4.commit() + .andThen( + () -> { q.add("4"); + }); + throw new Exception(); + } catch (Exception e) { + uow4.abort(); + } + uow5.commit() + .andThen( + () -> { + q.add("5"); }); - throw new Exception(); - } catch(Exception e) { - uow4.abort(); - } - uow5.commit().andThen(() -> { q.add("5"); }); - System.out.println(q); - Assert.assertTrue(q.isEmpty() == true); + System.out.println(q); + Assert.assertTrue(q.isEmpty() == true); + } + @Test + public void testClosableWillAbortWhenNotCommitted() throws Exception { + UnitOfWork unitOfWork; + try (UnitOfWork uow = session.begin()) { + unitOfWork = uow; + Assert.assertFalse(uow.hasAborted()); } + Assert.assertTrue(unitOfWork.hasAborted()); + } - @Test - public void testClosableWillAbortWhenNotCommitted() throws Exception { - UnitOfWork unitOfWork; - try(UnitOfWork uow = session.begin()) { - unitOfWork = uow; - Assert.assertFalse(uow.hasAborted()); - } - Assert.assertTrue(unitOfWork.hasAborted()); - - } - - @Test - public void testClosable() throws Exception { - UnitOfWork unitOfWork; - try(UnitOfWork uow = session.begin()) { - unitOfWork = uow; - Assert.assertFalse(uow.hasAborted()); - uow.commit().andThen(() -> { + @Test + public void testClosable() throws Exception { + UnitOfWork unitOfWork; + try (UnitOfWork uow = session.begin()) { + unitOfWork = uow; + Assert.assertFalse(uow.hasAborted()); + uow.commit() + .andThen( + () -> { Assert.assertFalse(uow.hasAborted()); Assert.assertTrue(uow.hasCommitted()); - }); - } - Assert.assertFalse(unitOfWork.hasAborted()); - Assert.assertTrue(unitOfWork.hasCommitted()); + }); } - + Assert.assertFalse(unitOfWork.hasAborted()); + Assert.assertTrue(unitOfWork.hasCommitted()); + } } diff --git a/src/test/java/net/helenus/test/integration/core/unitofwork/Directory.java b/src/test/java/net/helenus/test/integration/core/unitofwork/Directory.java index 4984d9d..70cf9e9 100644 --- a/src/test/java/net/helenus/test/integration/core/unitofwork/Directory.java +++ b/src/test/java/net/helenus/test/integration/core/unitofwork/Directory.java @@ -15,16 +15,13 @@ */ package net.helenus.test.integration.core.unitofwork; -import net.helenus.mapping.annotation.*; - import com.datastax.driver.core.DataType.Name; import java.util.Set; - +import net.helenus.mapping.annotation.*; @UDT public interface Directory extends FilesystemNode { - @Types.Set(Name.TIMEUUID) - Set inodes(); - + @Types.Set(Name.TIMEUUID) + Set inodes(); } diff --git a/src/test/java/net/helenus/test/integration/core/unitofwork/File.java b/src/test/java/net/helenus/test/integration/core/unitofwork/File.java index 4e8ff6f..a0ccebd 100644 --- a/src/test/java/net/helenus/test/integration/core/unitofwork/File.java +++ b/src/test/java/net/helenus/test/integration/core/unitofwork/File.java @@ -17,11 +17,9 @@ package net.helenus.test.integration.core.unitofwork; import net.helenus.mapping.annotation.*; - @UDT public interface File extends FilesystemNode { - @Column - byte[] data(); - + @Column + byte[] data(); } diff --git a/src/test/java/net/helenus/test/integration/core/unitofwork/FileAttributes.java b/src/test/java/net/helenus/test/integration/core/unitofwork/FileAttributes.java index 6d2db6d..f2eaccc 100644 --- a/src/test/java/net/helenus/test/integration/core/unitofwork/FileAttributes.java +++ b/src/test/java/net/helenus/test/integration/core/unitofwork/FileAttributes.java @@ -20,6 +20,5 @@ import net.helenus.mapping.annotation.UDT; @UDT public interface FileAttributes { - String owner(); - + String owner(); } diff --git a/src/test/java/net/helenus/test/integration/core/unitofwork/FilesystemNode.java b/src/test/java/net/helenus/test/integration/core/unitofwork/FilesystemNode.java index a57f296..7907fbd 100644 --- a/src/test/java/net/helenus/test/integration/core/unitofwork/FilesystemNode.java +++ b/src/test/java/net/helenus/test/integration/core/unitofwork/FilesystemNode.java @@ -15,20 +15,18 @@ */ package net.helenus.test.integration.core.unitofwork; -import net.helenus.mapping.annotation.*; - import java.util.UUID; +import net.helenus.mapping.annotation.*; @Table("fs") public interface FilesystemNode { - @PartitionKey - UUID inode(); + @PartitionKey + UUID inode(); - @ClusteringColumn - String name(); - - @Column - FileAttributes attr(); + @ClusteringColumn + String name(); + @Column + FileAttributes attr(); } diff --git a/src/test/java/net/helenus/test/integration/core/unitofwork/UnitOfWorkTest.java b/src/test/java/net/helenus/test/integration/core/unitofwork/UnitOfWorkTest.java index 762c668..4e45fff 100644 --- a/src/test/java/net/helenus/test/integration/core/unitofwork/UnitOfWorkTest.java +++ b/src/test/java/net/helenus/test/integration/core/unitofwork/UnitOfWorkTest.java @@ -15,7 +15,10 @@ */ package net.helenus.test.integration.core.unitofwork; +import static net.helenus.core.Query.eq; + import com.datastax.driver.core.utils.UUIDs; +import java.util.UUID; import net.bytebuddy.utility.RandomString; import net.helenus.core.Helenus; import net.helenus.core.HelenusSession; @@ -29,167 +32,161 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import java.util.UUID; - -import static net.helenus.core.Query.eq; - - @Table @Cacheable interface Widget { - @PartitionKey - UUID id(); - @Column - String name(); -} + @PartitionKey + UUID id(); + @Column + String name(); +} public class UnitOfWorkTest extends AbstractEmbeddedCassandraTest { - static Widget widget; - static HelenusSession session; + static Widget widget; + static HelenusSession session; + @BeforeClass + public static void beforeTest() { + session = Helenus.init(getSession()).showCql().add(Widget.class).autoCreateDrop().get(); + widget = session.dsl(Widget.class); + } - @BeforeClass - public static void beforeTest() { - session = Helenus.init(getSession()) - .showCql() - .add(Widget.class) - .autoCreateDrop() - .get(); - widget = session.dsl(Widget.class); - } + @Test + public void testSelectAfterSelect() throws Exception { + Widget w1, w2; + UUID key = UUIDs.timeBased(); - @Test - public void testSelectAfterSelect() throws Exception { - Widget w1, w2; - UUID key = UUIDs.timeBased(); + // This should inserted Widget, but not cache it. + session + .insert(widget) + .value(widget::id, key) + .value(widget::name, RandomString.make(20)) + .sync(); - // This should inserted Widget, but not cache it. - session.insert(widget) - .value(widget::id, key) - .value(widget::name, RandomString.make(20)) - .sync(); + try (UnitOfWork uow = session.begin()) { - try (UnitOfWork uow = session.begin()) { + // This should read from the database and return a Widget. + w1 = + session.select(widget).where(widget::id, eq(key)).single().sync(uow).orElse(null); - // This should read from the database and return a Widget. - w1 = session.select(widget) - .where(widget::id, eq(key)) - .single() - .sync(uow) - .orElse(null); - - // This should read from the cache and get the same instance of a Widget. - w2 = session.select(widget) - .where(widget::id, eq(key)) - .single() - .sync(uow) - .orElse(null); - - uow.commit() - .andThen(() -> { - Assert.assertEquals(w1, w2); - }); - } - - } - - @Test - public void testSelectAfterNestedSelect() throws Exception { - Widget w1, w2, w3, w4; - UUID key1 = UUIDs.timeBased(); - UUID key2 = UUIDs.timeBased(); - - // This should inserted Widget, and not cache it in uow1. - try (UnitOfWork uow1 = session.begin()) { - w1 = session.insert(widget) - .value(widget::id, key1) - .value(widget::name, RandomString.make(20)) - .sync(uow1); - - try (UnitOfWork uow2 = session.begin(uow1)) { - - // This should read from uow1's cache and return the same Widget. - w2 = session.select(widget) - .where(widget::id, eq(key1)) - .single() - .sync(uow2) - .orElse(null); + // This should read from the cache and get the same instance of a Widget. + w2 = + session.select(widget).where(widget::id, eq(key)).single().sync(uow).orElse(null); + uow.commit() + .andThen( + () -> { Assert.assertEquals(w1, w2); - - w3 = session.insert(widget) - .value(widget::id, key2) - .value(widget::name, RandomString.make(20)) - .sync(uow2); - - uow2.commit() - .andThen(() -> { - Assert.assertEquals(w1, w2); - }); - } - - // This should read from the cache and get the same instance of a Widget. - w4 = session.select(widget) - .where(widget::id, eq(key2)) - .single() - .sync(uow1) - .orElse(null); - - uow1.commit() - .andThen(() -> { - Assert.assertEquals(w3, w4); - }); - } - + }); } -/* - @Test - public void testSelectAfterInsertProperlyCachesEntity() throws Exception { - Widget w1, w2, w3, w4; - UUID key = UUIDs.timeBased(); + } - try (UnitOfWork uow = session.begin()) { + @Test + public void testSelectAfterNestedSelect() throws Exception { + Widget w1, w2, w3, w4; + UUID key1 = UUIDs.timeBased(); + UUID key2 = UUIDs.timeBased(); - // This should cache the inserted Widget. - w1 = session.insert(widget) - .value(widget::id, key) - .value(widget::name, RandomString.make(20)) - .sync(uow); + // This should inserted Widget, and not cache it in uow1. + try (UnitOfWork uow1 = session.begin()) { + w1 = + session + .insert(widget) + .value(widget::id, key1) + .value(widget::name, RandomString.make(20)) + .sync(uow1); - // This should read from the cache and get the same instance of a Widget. - w2 = session.select(widget) - .where(widget::id, eq(key)) - .single() - .sync(uow) - .orElse(null); + try (UnitOfWork uow2 = session.begin(uow1)) { - uow.commit() - .andThen(() -> { - Assert.assertEquals(w1, w2); - }); - } - - // This should read the widget from the session cache and maintain object identity. - w3 = session.select(widget) - .where(widget::id, eq(key)) + // This should read from uow1's cache and return the same Widget. + w2 = + session + .select(widget) + .where(widget::id, eq(key1)) .single() - .sync() + .sync(uow2) .orElse(null); - Assert.assertEquals(w1, w3); + Assert.assertEquals(w1, w2); - // This should read the widget from the database, no object identity but values should match. - w4 = session.select(widget) - .where(widget::id, eq(key)) - .ignoreCache() - .single() - .sync() - .orElse(null); + w3 = + session + .insert(widget) + .value(widget::id, key2) + .value(widget::name, RandomString.make(20)) + .sync(uow2); - Assert.assertNotEquals(w1, w4); - Assert.assertTrue(w1.equals(w4)); + uow2.commit() + .andThen( + () -> { + Assert.assertEquals(w1, w2); + }); + } + + // This should read from the cache and get the same instance of a Widget. + w4 = + session + .select(widget) + .where(widget::id, eq(key2)) + .single() + .sync(uow1) + .orElse(null); + + uow1.commit() + .andThen( + () -> { + Assert.assertEquals(w3, w4); + }); } -*/ + } + /* + @Test + public void testSelectAfterInsertProperlyCachesEntity() throws Exception { + Widget w1, w2, w3, w4; + UUID key = UUIDs.timeBased(); + + try (UnitOfWork uow = session.begin()) { + + // This should cache the inserted Widget. + w1 = session.insert(widget) + .value(widget::id, key) + .value(widget::name, RandomString.make(20)) + .sync(uow); + + // This should read from the cache and get the same instance of a Widget. + w2 = session.select(widget) + .where(widget::id, eq(key)) + .single() + .sync(uow) + .orElse(null); + + uow.commit() + .andThen(() -> { + Assert.assertEquals(w1, w2); + }); + } + + // This should read the widget from the session cache and maintain object identity. + w3 = session.select(widget) + .where(widget::id, eq(key)) + .single() + .sync() + .orElse(null); + + Assert.assertEquals(w1, w3); + + // This should read the widget from the database, no object identity but values should match. + w4 = session.select(widget) + .where(widget::id, eq(key)) + .ignoreCache() + .single() + .sync() + .orElse(null); + + Assert.assertNotEquals(w1, w4); + Assert.assertTrue(w1.equals(w4)); + } + */ } diff --git a/src/test/java/net/helenus/test/integration/core/views/Cyclist.java b/src/test/java/net/helenus/test/integration/core/views/Cyclist.java new file mode 100644 index 0000000..0ab5a73 --- /dev/null +++ b/src/test/java/net/helenus/test/integration/core/views/Cyclist.java @@ -0,0 +1,29 @@ +package net.helenus.test.integration.core.views; + +import java.util.Date; +import java.util.UUID; +import net.helenus.mapping.annotation.ClusteringColumn; +import net.helenus.mapping.annotation.CoveringIndex; +import net.helenus.mapping.annotation.PartitionKey; +import net.helenus.mapping.annotation.Table; + +@Table +@CoveringIndex( + name = "cyclist_mv", + covering = {"age", "birthday", "country"}, + partitionKeys = {"age", "cid"}, + clusteringColumns = {} +) +public interface Cyclist { + @ClusteringColumn + UUID cid(); + + String name(); + + @PartitionKey + int age(); + + Date birthday(); + + String country(); +} diff --git a/src/test/java/net/helenus/test/integration/core/views/CyclistsByAge.java b/src/test/java/net/helenus/test/integration/core/views/CyclistsByAge.java new file mode 100644 index 0000000..02b414c --- /dev/null +++ b/src/test/java/net/helenus/test/integration/core/views/CyclistsByAge.java @@ -0,0 +1,21 @@ +package net.helenus.test.integration.core.views; + +import java.util.Date; +import java.util.UUID; + +import net.helenus.mapping.OrderingDirection; +import net.helenus.mapping.annotation.*; + +@MaterializedView +public interface CyclistsByAge extends Cyclist { + @PartitionKey + UUID cid(); + + @ClusteringColumn(ordering = OrderingDirection.ASC) + int age(); + + Date birthday(); + + @Index + String country(); +} diff --git a/src/test/java/net/helenus/test/integration/core/views/MaterializedViewTest.java b/src/test/java/net/helenus/test/integration/core/views/MaterializedViewTest.java new file mode 100644 index 0000000..a8df217 --- /dev/null +++ b/src/test/java/net/helenus/test/integration/core/views/MaterializedViewTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 The Helenus Authors + * + * 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 net.helenus.test.integration.core.views; + +import static net.helenus.core.Query.eq; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import net.helenus.core.Helenus; +import net.helenus.core.HelenusSession; +import net.helenus.test.integration.build.AbstractEmbeddedCassandraTest; +import org.junit.BeforeClass; +import org.junit.Test; + +// See: https://docs.datastax.com/en/cql/3.3/cql/cql_using/useCreateMV.html +// https://docs.datastax.com/en/cql/3.3/cql/cql_reference/cqlCreateMaterializedView.html +// https://www.datastax.com/dev/blog/materialized-view-performance-in-cassandra-3-x +public class MaterializedViewTest extends AbstractEmbeddedCassandraTest { + + static Cyclist cyclist; + static HelenusSession session; + + static Date dateFromString(String dateInString) { + SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy"); + try { + return formatter.parse(dateInString); + } catch (ParseException e) { + e.printStackTrace(); + } + return null; + } + + @BeforeClass + public static void beforeTest() { + session = + Helenus.init(getSession()) + .showCql() + .add(Cyclist.class) + .add(CyclistsByAge.class) + .autoCreateDrop() + .get(); + cyclist = session.dsl(Cyclist.class); + + session + .insert(cyclist) + .value(cyclist::cid, UUID.randomUUID()) + .value(cyclist::age, 18) + .value(cyclist::birthday, dateFromString("1997-02-08")) + .value(cyclist::country, "Netherlands") + .value(cyclist::name, "Pascal EENKHOORN") + .sync(); + } + + @Test + public void testMv() throws Exception { + session + .select(Cyclist.class) + .from(CyclistsByAge.class) + .where(cyclist::age, eq(18)) + .sync(); + } +} diff --git a/src/test/java/net/helenus/test/unit/core/dsl/Account.java b/src/test/java/net/helenus/test/unit/core/dsl/Account.java index 2725f16..10fc9fd 100644 --- a/src/test/java/net/helenus/test/unit/core/dsl/Account.java +++ b/src/test/java/net/helenus/test/unit/core/dsl/Account.java @@ -19,7 +19,6 @@ import java.util.Date; import java.util.Map; import java.util.Set; import net.helenus.core.reflect.Drafted; -import net.helenus.mapping.HelenusEntity; import net.helenus.mapping.annotation.*; @Table