Merge branch 'develop' into gburd/wip-facet-cache
This commit is contained in:
commit
d369a5b862
24 changed files with 593 additions and 23 deletions
7
bin/format.sh
Executable file
7
bin/format.sh
Executable file
|
@ -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
|
||||
|
BIN
lib/google-java-format-1.3-all-deps.jar
Normal file
BIN
lib/google-java-format-1.3-all-deps.jar
Normal file
Binary file not shown.
|
@ -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<Object> variables, CodecRegistry codecRegistry) {
|
||||
Utils.appendName(name, sb).append(" IS NOT NULL");
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean containsBindMarker() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<String> 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();
|
||||
}
|
||||
}
|
|
@ -182,11 +182,11 @@ public final class HelenusSession extends AbstractSessionOperations implements C
|
|||
return metadata;
|
||||
}
|
||||
|
||||
public synchronized <T extends UnitOfWork> T begin() {
|
||||
public synchronized UnitOfWork begin() {
|
||||
return begin(null);
|
||||
}
|
||||
|
||||
public synchronized <T extends UnitOfWork> T begin(T parent) {
|
||||
public synchronized UnitOfWork begin(UnitOfWork parent) {
|
||||
try {
|
||||
Class<? extends UnitOfWork> clazz = unitOfWorkClass;
|
||||
Constructor<? extends UnitOfWork> ctor =
|
||||
|
@ -195,7 +195,7 @@ public final class HelenusSession extends AbstractSessionOperations implements C
|
|||
if (parent != null) {
|
||||
parent.addNestedUnitOfWork(uow);
|
||||
}
|
||||
return (T) uow.begin();
|
||||
return uow.begin();
|
||||
} catch (NoSuchMethodException
|
||||
| InvocationTargetException
|
||||
| InstantiationException
|
||||
|
|
|
@ -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,79 @@ 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<HelenusPropertyNode> props = new ArrayList<HelenusPropertyNode>();
|
||||
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<String> p = new ArrayList<String>(props.size());
|
||||
List<String> c = new ArrayList<String>(props.size());
|
||||
List<String> o = new ArrayList<String>(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)
|
||||
.ifNotExists();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -277,7 +277,7 @@ public final class SessionInitializer extends AbstractSessionOperations {
|
|||
}
|
||||
|
||||
DslExportable dsl = (DslExportable) Helenus.dsl(iface);
|
||||
dsl.setMetadata(session.getCluster().getMetadata());
|
||||
dsl.setCassandraMetadataForHelenusSesion(session.getCluster().getMetadata());
|
||||
sessionRepository.add(dsl);
|
||||
});
|
||||
|
||||
|
@ -287,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()
|
||||
|
@ -307,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:
|
||||
|
@ -317,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<SchemaStatement> 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;
|
||||
|
|
|
@ -21,13 +21,11 @@ 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.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import net.helenus.core.AbstractSessionOperations;
|
||||
import net.helenus.core.Filter;
|
||||
import net.helenus.core.Helenus;
|
||||
|
@ -173,5 +171,4 @@ public abstract class AbstractOptionalOperation<E, O extends AbstractOptionalOpe
|
|||
if (uow == null) return async();
|
||||
return CompletableFuture.<Optional<E>>supplyAsync(() -> sync(uow));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -83,9 +83,18 @@ public final class InsertOperation<T> extends AbstractOperation<T, InsertOperati
|
|||
Set<String> keys = (mutations == null) ? null : mutations;
|
||||
|
||||
for (HelenusProperty prop : properties) {
|
||||
boolean addProp = false;
|
||||
|
||||
if (keys == null || keys.contains(prop.getPropertyName())) {
|
||||
switch (prop.getColumnType()) {
|
||||
case PARTITION_KEY:
|
||||
case CLUSTERING_COLUMN:
|
||||
addProp = true;
|
||||
break;
|
||||
default:
|
||||
addProp = (keys == null || keys.contains(prop.getPropertyName()));
|
||||
}
|
||||
|
||||
if (addProp) {
|
||||
Object value = BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop);
|
||||
value = sessionOps.getValuePreparer().prepareColumnValue(value, prop);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ 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;
|
||||
|
@ -47,6 +48,7 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
|
|||
protected List<Ordering> ordering = null;
|
||||
protected Integer limit = null;
|
||||
protected boolean allowFiltering = false;
|
||||
protected String alternateTableName = null;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public SelectOperation(AbstractSessionOperations sessionOperations) {
|
||||
|
@ -128,6 +130,19 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
|
|||
return new CountOperation(sessionOps, entity);
|
||||
}
|
||||
|
||||
public <V extends E> SelectOperation<E> from(Class<V> materializedViewClass) {
|
||||
Objects.requireNonNull(materializedViewClass);
|
||||
HelenusEntity entity = Helenus.entity(materializedViewClass);
|
||||
this.alternateTableName = entity.getName().toCql();
|
||||
this.props.clear();
|
||||
entity
|
||||
.getOrderedProperties()
|
||||
.stream()
|
||||
.map(p -> new HelenusPropertyNode(p, Optional.empty()))
|
||||
.forEach(p -> this.props.add(p));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SelectFirstOperation<E> single() {
|
||||
limit(1);
|
||||
return new SelectFirstOperation<E>(this);
|
||||
|
@ -231,6 +246,7 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
|
|||
+ prop.getEntity().getMappingInterface());
|
||||
}
|
||||
|
||||
/* TODO: is this useful information to gather when caching?
|
||||
if (cached) {
|
||||
switch (prop.getProperty().getColumnType()) {
|
||||
case PARTITION_KEY:
|
||||
|
@ -249,13 +265,15 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
|
|||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if (entity == null) {
|
||||
throw new HelenusMappingException("no entity or table to select data");
|
||||
}
|
||||
|
||||
Select select = selection.from(entity.getName().toCql());
|
||||
String tableName = alternateTableName == null ? entity.getName().toCql() : alternateTableName;
|
||||
Select select = selection.from(tableName);
|
||||
|
||||
if (ordering != null && !ordering.isEmpty()) {
|
||||
select.orderBy(ordering.toArray(new Ordering[ordering.size()]));
|
||||
|
|
|
@ -22,11 +22,11 @@ public interface DslExportable {
|
|||
|
||||
public static final String GET_ENTITY_METHOD = "getHelenusMappingEntity";
|
||||
public static final String GET_PARENT_METHOD = "getParentDslHelenusPropertyNode";
|
||||
public static final String SET_METADATA_METHOD = "setMetadata";
|
||||
public static final String SET_METADATA_METHOD = "setCassandraMetadataForHelenusSesion";
|
||||
|
||||
HelenusEntity getHelenusMappingEntity();
|
||||
|
||||
HelenusPropertyNode getParentDslHelenusPropertyNode();
|
||||
|
||||
void setMetadata(Metadata metadata);
|
||||
void setCassandraMetadataForHelenusSesion(Metadata metadata);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ public class DslInvocationHandler<E> implements InvocationHandler {
|
|||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public void setMetadata(Metadata metadata) {
|
||||
public void setCassandraMetadataForHelenusSesion(Metadata metadata) {
|
||||
if (metadata != null) {
|
||||
this.metadata = metadata;
|
||||
entity = init(metadata);
|
||||
|
@ -130,7 +130,7 @@ public class DslInvocationHandler<E> implements InvocationHandler {
|
|||
&& args.length == 1
|
||||
&& args[0] instanceof Metadata) {
|
||||
if (metadata == null) {
|
||||
this.setMetadata((Metadata) args[0]);
|
||||
this.setCassandraMetadataForHelenusSesion((Metadata) args[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package net.helenus.mapping;
|
|||
|
||||
public enum HelenusEntityType {
|
||||
TABLE,
|
||||
VIEW,
|
||||
TUPLE,
|
||||
UDT;
|
||||
}
|
||||
|
|
|
@ -185,6 +185,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);
|
||||
|
||||
|
@ -201,6 +204,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)) {
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>A corresponding materialized view will be created based on the underline @Table for the
|
||||
* specific column.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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 "";
|
||||
}
|
|
@ -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
|
||||
*
|
||||
* <p>MaterializedView annotation is used to define different mapping to some other Table interface
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Default value is false, we are quoting only selected names.
|
||||
*
|
||||
* @return true if name have to be quoted
|
||||
*/
|
||||
boolean forceQuote() default false;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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
|
||||
// https://cassandra-zone.com/materialized-views/
|
||||
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();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue