Merge branch 'develop' into gburd/wip-facet-cache

This commit is contained in:
Greg Burd 2017-10-12 16:29:11 -04:00
commit d369a5b862
24 changed files with 593 additions and 23 deletions

7
bin/format.sh Executable file
View 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

Binary file not shown.

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -182,11 +182,11 @@ public final class HelenusSession extends AbstractSessionOperations implements C
return metadata; return metadata;
} }
public synchronized <T extends UnitOfWork> T begin() { public synchronized UnitOfWork begin() {
return begin(null); return begin(null);
} }
public synchronized <T extends UnitOfWork> T begin(T parent) { public synchronized UnitOfWork begin(UnitOfWork parent) {
try { try {
Class<? extends UnitOfWork> clazz = unitOfWorkClass; Class<? extends UnitOfWork> clazz = unitOfWorkClass;
Constructor<? extends UnitOfWork> ctor = Constructor<? extends UnitOfWork> ctor =
@ -195,7 +195,7 @@ public final class HelenusSession extends AbstractSessionOperations implements C
if (parent != null) { if (parent != null) {
parent.addNestedUnitOfWork(uow); parent.addNestedUnitOfWork(uow);
} }
return (T) uow.begin(); return uow.begin();
} catch (NoSuchMethodException } catch (NoSuchMethodException
| InvocationTargetException | InvocationTargetException
| InstantiationException | InstantiationException

View file

@ -17,16 +17,22 @@ package net.helenus.core;
import com.datastax.driver.core.*; import com.datastax.driver.core.*;
import com.datastax.driver.core.IndexMetadata; 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.*;
import com.datastax.driver.core.schemabuilder.Create.Options; import com.datastax.driver.core.schemabuilder.Create.Options;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import net.helenus.core.reflect.HelenusPropertyNode;
import net.helenus.mapping.*; import net.helenus.mapping.*;
import net.helenus.mapping.ColumnType; import net.helenus.mapping.ColumnType;
import net.helenus.mapping.annotation.ClusteringColumn;
import net.helenus.mapping.type.OptionalColumnMetadata; import net.helenus.mapping.type.OptionalColumnMetadata;
import net.helenus.support.CqlUtil; import net.helenus.support.CqlUtil;
import net.helenus.support.HelenusMappingException; import net.helenus.support.HelenusMappingException;
public final class SchemaUtil { public final class SchemaUtil {
private SchemaUtil() {} private SchemaUtil() {}
@ -143,6 +149,79 @@ public final class SchemaUtil {
return SchemaBuilder.dropType(type.getTypeName()).ifExists(); 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) { public static SchemaStatement createTable(HelenusEntity entity) {
if (entity.getType() != HelenusEntityType.TABLE) { if (entity.getType() != HelenusEntityType.TABLE) {

View file

@ -277,7 +277,7 @@ public final class SessionInitializer extends AbstractSessionOperations {
} }
DslExportable dsl = (DslExportable) Helenus.dsl(iface); DslExportable dsl = (DslExportable) Helenus.dsl(iface);
dsl.setMetadata(session.getCluster().getMetadata()); dsl.setCassandraMetadataForHelenusSesion(session.getCluster().getMetadata());
sessionRepository.add(dsl); sessionRepository.add(dsl);
}); });
@ -287,8 +287,16 @@ public final class SessionInitializer extends AbstractSessionOperations {
switch (autoDdl) { switch (autoDdl) {
case CREATE_DROP: case CREATE_DROP:
// Drop tables first, otherwise a `DROP TYPE ...` will fail as the type is still referenced // Drop view first, otherwise a `DROP TABLE ...` will fail as the type is still referenced
// by a table. // 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 sessionRepository
.entities() .entities()
.stream() .stream()
@ -307,6 +315,12 @@ public final class SessionInitializer extends AbstractSessionOperations {
.filter(e -> e.getType() == HelenusEntityType.TABLE) .filter(e -> e.getType() == HelenusEntityType.TABLE)
.forEach(e -> tableOps.createTable(e)); .forEach(e -> tableOps.createTable(e));
sessionRepository
.entities()
.stream()
.filter(e -> e.getType() == HelenusEntityType.VIEW)
.forEach(e -> tableOps.createView(e));
break; break;
case VALIDATE: case VALIDATE:
@ -317,16 +331,29 @@ public final class SessionInitializer extends AbstractSessionOperations {
.stream() .stream()
.filter(e -> e.getType() == HelenusEntityType.TABLE) .filter(e -> e.getType() == HelenusEntityType.TABLE)
.forEach(e -> tableOps.validateTable(getTableMetadata(e), e)); .forEach(e -> tableOps.validateTable(getTableMetadata(e), e));
break; break;
case UPDATE: case UPDATE:
eachUserTypeInOrder(userTypeOps, e -> userTypeOps.updateUserType(getUserType(e), e)); eachUserTypeInOrder(userTypeOps, e -> userTypeOps.updateUserType(getUserType(e), e));
sessionRepository
.entities()
.stream()
.filter(e -> e.getType() == HelenusEntityType.VIEW)
.forEach(e -> tableOps.dropView(e));
sessionRepository sessionRepository
.entities() .entities()
.stream() .stream()
.filter(e -> e.getType() == HelenusEntityType.TABLE) .filter(e -> e.getType() == HelenusEntityType.TABLE)
.forEach(e -> tableOps.updateTable(getTableMetadata(e), e)); .forEach(e -> tableOps.updateTable(getTableMetadata(e), e));
sessionRepository
.entities()
.stream()
.filter(e -> e.getType() == HelenusEntityType.VIEW)
.forEach(e -> tableOps.createView(e));
break; break;
} }

View file

@ -35,14 +35,11 @@ public final class TableOperations {
} }
public void createTable(HelenusEntity entity) { public void createTable(HelenusEntity entity) {
sessionOps.execute(SchemaUtil.createTable(entity), true); sessionOps.execute(SchemaUtil.createTable(entity), true);
executeBatch(SchemaUtil.createIndexes(entity)); executeBatch(SchemaUtil.createIndexes(entity));
} }
public void dropTable(HelenusEntity entity) { public void dropTable(HelenusEntity entity) {
sessionOps.execute(SchemaUtil.dropTable(entity), true); sessionOps.execute(SchemaUtil.dropTable(entity), true);
} }
@ -50,7 +47,10 @@ public final class TableOperations {
if (tmd == null) { if (tmd == null) {
throw new HelenusException( 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); List<SchemaStatement> list = SchemaUtil.alterTable(tmd, entity, dropUnusedColumns);
@ -67,7 +67,31 @@ public final class TableOperations {
} }
public void updateTable(TableMetadata tmd, HelenusEntity entity) { 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) { if (tmd == null) {
createTable(entity); createTable(entity);
return; return;

View file

@ -21,13 +21,11 @@ import com.datastax.driver.core.ResultSet;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import net.helenus.core.AbstractSessionOperations; import net.helenus.core.AbstractSessionOperations;
import net.helenus.core.Filter; import net.helenus.core.Filter;
import net.helenus.core.Helenus; import net.helenus.core.Helenus;
@ -173,5 +171,4 @@ public abstract class AbstractOptionalOperation<E, O extends AbstractOptionalOpe
if (uow == null) return async(); if (uow == null) return async();
return CompletableFuture.<Optional<E>>supplyAsync(() -> sync(uow)); return CompletableFuture.<Optional<E>>supplyAsync(() -> sync(uow));
} }
} }

View file

@ -83,9 +83,18 @@ public final class InsertOperation<T> extends AbstractOperation<T, InsertOperati
Set<String> keys = (mutations == null) ? null : mutations; Set<String> keys = (mutations == null) ? null : mutations;
for (HelenusProperty prop : properties) { 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); Object value = BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop);
value = sessionOps.getValuePreparer().prepareColumnValue(value, prop); value = sessionOps.getValuePreparer().prepareColumnValue(value, prop);

View file

@ -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;
import com.datastax.driver.core.querybuilder.Select.Selection; import com.datastax.driver.core.querybuilder.Select.Selection;
import com.datastax.driver.core.querybuilder.Select.Where; import com.datastax.driver.core.querybuilder.Select.Where;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
@ -47,6 +48,7 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
protected List<Ordering> ordering = null; protected List<Ordering> ordering = null;
protected Integer limit = null; protected Integer limit = null;
protected boolean allowFiltering = false; protected boolean allowFiltering = false;
protected String alternateTableName = null;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public SelectOperation(AbstractSessionOperations sessionOperations) { public SelectOperation(AbstractSessionOperations sessionOperations) {
@ -128,6 +130,19 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
return new CountOperation(sessionOps, entity); 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() { public SelectFirstOperation<E> single() {
limit(1); limit(1);
return new SelectFirstOperation<E>(this); return new SelectFirstOperation<E>(this);
@ -231,6 +246,7 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
+ prop.getEntity().getMappingInterface()); + prop.getEntity().getMappingInterface());
} }
/* TODO: is this useful information to gather when caching?
if (cached) { if (cached) {
switch (prop.getProperty().getColumnType()) { switch (prop.getProperty().getColumnType()) {
case PARTITION_KEY: case PARTITION_KEY:
@ -249,13 +265,15 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
break; break;
} }
} }
*/
} }
if (entity == null) { if (entity == null) {
throw new HelenusMappingException("no entity or table to select data"); 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()) { if (ordering != null && !ordering.isEmpty()) {
select.orderBy(ordering.toArray(new Ordering[ordering.size()])); select.orderBy(ordering.toArray(new Ordering[ordering.size()]));

View file

@ -22,11 +22,11 @@ public interface DslExportable {
public static final String GET_ENTITY_METHOD = "getHelenusMappingEntity"; public static final String GET_ENTITY_METHOD = "getHelenusMappingEntity";
public static final String GET_PARENT_METHOD = "getParentDslHelenusPropertyNode"; 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(); HelenusEntity getHelenusMappingEntity();
HelenusPropertyNode getParentDslHelenusPropertyNode(); HelenusPropertyNode getParentDslHelenusPropertyNode();
void setMetadata(Metadata metadata); void setCassandraMetadataForHelenusSesion(Metadata metadata);
} }

View file

@ -59,7 +59,7 @@ public class DslInvocationHandler<E> implements InvocationHandler {
this.classLoader = classLoader; this.classLoader = classLoader;
} }
public void setMetadata(Metadata metadata) { public void setCassandraMetadataForHelenusSesion(Metadata metadata) {
if (metadata != null) { if (metadata != null) {
this.metadata = metadata; this.metadata = metadata;
entity = init(metadata); entity = init(metadata);
@ -130,7 +130,7 @@ public class DslInvocationHandler<E> implements InvocationHandler {
&& args.length == 1 && args.length == 1
&& args[0] instanceof Metadata) { && args[0] instanceof Metadata) {
if (metadata == null) { if (metadata == null) {
this.setMetadata((Metadata) args[0]); this.setCassandraMetadataForHelenusSesion((Metadata) args[0]);
} }
return null; return null;
} }

View file

@ -17,6 +17,7 @@ package net.helenus.mapping;
public enum HelenusEntityType { public enum HelenusEntityType {
TABLE, TABLE,
VIEW,
TUPLE, TUPLE,
UDT; UDT;
} }

View file

@ -185,6 +185,9 @@ public final class HelenusMappingEntity implements HelenusEntity {
case TABLE: case TABLE:
return MappingUtil.getTableName(iface, true); return MappingUtil.getTableName(iface, true);
case VIEW:
return MappingUtil.getViewName(iface, true);
case TUPLE: case TUPLE:
return IdentityName.of(MappingUtil.getDefaultEntityName(iface), false); return IdentityName.of(MappingUtil.getDefaultEntityName(iface), false);
@ -201,6 +204,8 @@ public final class HelenusMappingEntity implements HelenusEntity {
if (null != iface.getDeclaredAnnotation(Table.class)) { if (null != iface.getDeclaredAnnotation(Table.class)) {
return HelenusEntityType.TABLE; return HelenusEntityType.TABLE;
} else if (null != iface.getDeclaredAnnotation(MaterializedView.class)) {
return HelenusEntityType.VIEW;
} else if (null != iface.getDeclaredAnnotation(Tuple.class)) { } else if (null != iface.getDeclaredAnnotation(Tuple.class)) {
return HelenusEntityType.TUPLE; return HelenusEntityType.TUPLE;
} else if (null != iface.getDeclaredAnnotation(UDT.class)) { } else if (null != iface.getDeclaredAnnotation(UDT.class)) {

View file

@ -25,10 +25,7 @@ import javax.validation.ConstraintValidator;
import net.helenus.core.Getter; import net.helenus.core.Getter;
import net.helenus.core.Helenus; import net.helenus.core.Helenus;
import net.helenus.core.reflect.*; import net.helenus.core.reflect.*;
import net.helenus.mapping.annotation.Index; import net.helenus.mapping.annotation.*;
import net.helenus.mapping.annotation.Table;
import net.helenus.mapping.annotation.Tuple;
import net.helenus.mapping.annotation.UDT;
import net.helenus.support.DslPropertyException; import net.helenus.support.DslPropertyException;
import net.helenus.support.HelenusMappingException; import net.helenus.support.HelenusMappingException;
@ -172,6 +169,28 @@ public final class MappingUtil {
return udt != null; 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) { public static IdentityName getTableName(Class<?> iface, boolean required) {
String tableName = null; String tableName = null;
@ -222,6 +241,7 @@ public final class MappingUtil {
} }
if (iface.getDeclaredAnnotation(Table.class) != null if (iface.getDeclaredAnnotation(Table.class) != null
|| iface.getDeclaredAnnotation(MaterializedView.class) != null
|| iface.getDeclaredAnnotation(UDT.class) != null || iface.getDeclaredAnnotation(UDT.class) != null
|| iface.getDeclaredAnnotation(Tuple.class) != null) { || iface.getDeclaredAnnotation(Tuple.class) != null) {

View file

@ -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 "";
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}
}