From 828c456814ca9aa7a58cb057e5c8848e4aeaa594 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Tue, 10 Oct 2017 13:39:59 -0400 Subject: [PATCH] Add MaterialziedView entity support and the beginnings of a CoveringIndex (which is another name for MaterializedView). --- .../core/querybuilder/IsNotNullClause.java | 33 ++++++++ .../schemabuilder/CreateMaterializedView.java | 47 +++++++++++ .../schemabuilder/DropMaterializedView.java | 49 ++++++++++++ .../java/net/helenus/core/SchemaUtil.java | 65 +++++++++++++++ .../net/helenus/core/SessionInitializer.java | 60 ++++++++++---- .../net/helenus/core/TableOperations.java | 30 +++++-- .../core/operation/SelectOperation.java | 17 +++- .../helenus/core/reflect/DslExportable.java | 4 +- .../core/reflect/DslInvocationHandler.java | 4 +- .../helenus/mapping/HelenusEntityType.java | 1 + .../helenus/mapping/HelenusMappingEntity.java | 5 ++ .../java/net/helenus/mapping/MappingUtil.java | 28 ++++++- .../mapping/annotation/CoveringIndex.java | 53 ++++++++++++ .../mapping/annotation/MaterializedView.java | 53 ++++++++++++ .../test/integration/core/views/Cyclist.java | 24 ++++++ .../integration/core/views/CyclistsByAge.java | 20 +++++ .../core/views/MaterializedViewTest.java | 80 +++++++++++++++++++ 17 files changed, 541 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/datastax/driver/core/querybuilder/IsNotNullClause.java create mode 100644 src/main/java/com/datastax/driver/core/schemabuilder/CreateMaterializedView.java create mode 100644 src/main/java/com/datastax/driver/core/schemabuilder/DropMaterializedView.java create mode 100644 src/main/java/net/helenus/mapping/annotation/CoveringIndex.java create mode 100644 src/main/java/net/helenus/mapping/annotation/MaterializedView.java create mode 100644 src/test/java/net/helenus/test/integration/core/views/Cyclist.java create mode 100644 src/test/java/net/helenus/test/integration/core/views/CyclistsByAge.java create mode 100644 src/test/java/net/helenus/test/integration/core/views/MaterializedViewTest.java 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..7f7b3b1 --- /dev/null +++ b/src/main/java/com/datastax/driver/core/querybuilder/IsNotNullClause.java @@ -0,0 +1,33 @@ +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..48282d2 --- /dev/null +++ b/src/main/java/com/datastax/driver/core/schemabuilder/CreateMaterializedView.java @@ -0,0 +1,47 @@ +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; + + public CreateMaterializedView(String keyspaceName, String viewName, Select.Where selection, String primaryKey) { + super(keyspaceName, viewName); + this.viewName = viewName; + this.selection = selection; + this.primaryKey = primaryKey; + } + + 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); + 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..1ef6349 --- /dev/null +++ b/src/main/java/com/datastax/driver/core/schemabuilder/DropMaterializedView.java @@ -0,0 +1,49 @@ +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/core/SchemaUtil.java b/src/main/java/net/helenus/core/SchemaUtil.java index e2bb681..3864ffe 100644 --- a/src/main/java/net/helenus/core/SchemaUtil.java +++ b/src/main/java/net/helenus/core/SchemaUtil.java @@ -17,16 +17,25 @@ package net.helenus.core; import com.datastax.driver.core.*; import com.datastax.driver.core.IndexMetadata; +import com.datastax.driver.core.querybuilder.Clause; +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 com.google.common.collect.Iterables; +import net.helenus.core.reflect.HelenusPropertyNode; import net.helenus.mapping.*; import net.helenus.mapping.ColumnType; import net.helenus.mapping.type.OptionalColumnMetadata; import net.helenus.support.CqlUtil; import net.helenus.support.HelenusMappingException; +import static com.datastax.driver.core.querybuilder.QueryBuilder.raw; + public final class SchemaUtil { private SchemaUtil() {} @@ -143,6 +152,62 @@ 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); + } + String tableName = Helenus.entity(entity.getMappingInterface().getInterfaces()[0]) + .getName().toCql(); + Select.Where where = selection.from(tableName).where(); + List p = new ArrayList(props.size()); + List c = 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)); + 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)) : "") + + ")"; + + return new CreateMaterializedView(keyspace, viewName, where, primaryKey); + } + + 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 d7f0d7e..5e31f50 100644 --- a/src/main/java/net/helenus/core/SessionInitializer.java +++ b/src/main/java/net/helenus/core/SessionInitializer.java @@ -19,13 +19,6 @@ import brave.Tracer; import com.codahale.metrics.MetricRegistry; import com.datastax.driver.core.*; import com.google.common.util.concurrent.MoreExecutors; -import java.io.IOException; -import java.io.PrintStream; -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; @@ -36,6 +29,13 @@ import net.helenus.support.Either; import net.helenus.support.HelenusException; import net.helenus.support.PackageUtil; +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + public final class SessionInitializer extends AbstractSessionOperations { private final Session session; @@ -276,7 +276,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); }); @@ -286,13 +286,21 @@ 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.TABLE) - .forEach(e -> tableOps.dropTable(e)); + .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() + .filter(e -> e.getType() == HelenusEntityType.TABLE) + .forEach(e -> tableOps.dropTable(e)); eachUserTypeInReverseOrder(userTypeOps, e -> userTypeOps.dropUserType(e)); @@ -306,6 +314,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: @@ -316,16 +330,30 @@ public final class SessionInitializer extends AbstractSessionOperations { .stream() .filter(e -> e.getType() == HelenusEntityType.TABLE) .forEach(e -> tableOps.validateTable(getTableMetadata(e), e)); - break; + + break; case UPDATE: eachUserTypeInOrder(userTypeOps, e -> userTypeOps.updateUserType(getUserType(e), e)); - sessionRepository + 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..0df3982 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,7 @@ 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,17 +64,36 @@ public final class TableOperations { } public void updateTable(TableMetadata tmd, HelenusEntity entity) { - if (tmd == null) { createTable(entity); - return; + return; } executeBatch(SchemaUtil.alterTable(tmd, entity, dropUnusedColumns)); executeBatch(SchemaUtil.alterIndexes(tmd, entity, dropUnusedIndexes)); } - private void executeBatch(List list) { + 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; + } + + executeBatch(SchemaUtil.alterTable(tmd, entity, dropUnusedColumns)); + executeBatch(SchemaUtil.alterIndexes(tmd, entity, dropUnusedIndexes)); + } + + + private void executeBatch(List list) { list.forEach( s -> { diff --git a/src/main/java/net/helenus/core/operation/SelectOperation.java b/src/main/java/net/helenus/core/operation/SelectOperation.java index 05403d1..542f7e3 100644 --- a/src/main/java/net/helenus/core/operation/SelectOperation.java +++ b/src/main/java/net/helenus/core/operation/SelectOperation.java @@ -48,6 +48,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 +130,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); @@ -255,7 +269,8 @@ public final class SelectOperation extends AbstractFilterStreamOperation 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 implements InvocationHandler { && args.length == 1 && args[0] instanceof Metadata) { if (metadata == null) { - this.setMetadata((Metadata) args[0]); + this.setCassandraMetadataForHelenusSesion((Metadata) args[0]); } return null; } 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 f1d57c3..8837cb4 100644 --- a/src/main/java/net/helenus/mapping/HelenusMappingEntity.java +++ b/src/main/java/net/helenus/mapping/HelenusMappingEntity.java @@ -145,6 +145,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); @@ -161,6 +164,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..6a9554c --- /dev/null +++ b/src/main/java/net/helenus/mapping/annotation/CoveringIndex.java @@ -0,0 +1,53 @@ +package net.helenus.mapping.annotation; + +import net.helenus.core.Getter; + +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..6a5b103 --- /dev/null +++ b/src/main/java/net/helenus/mapping/annotation/MaterializedView.java @@ -0,0 +1,53 @@ +/* + * 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/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..672a509 --- /dev/null +++ b/src/test/java/net/helenus/test/integration/core/views/Cyclist.java @@ -0,0 +1,24 @@ +package net.helenus.test.integration.core.views; + +import net.helenus.mapping.annotation.ClusteringColumn; +import net.helenus.mapping.annotation.CoveringIndex; +import net.helenus.mapping.annotation.PartitionKey; +import net.helenus.mapping.annotation.Table; + +import java.util.Date; +import java.util.UUID; + +@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..ddc371d --- /dev/null +++ b/src/test/java/net/helenus/test/integration/core/views/CyclistsByAge.java @@ -0,0 +1,20 @@ +package net.helenus.test.integration.core.views; + +import net.helenus.mapping.annotation.ClusteringColumn; +import net.helenus.mapping.annotation.Index; +import net.helenus.mapping.annotation.MaterializedView; +import net.helenus.mapping.annotation.PartitionKey; + +import java.util.Date; +import java.util.UUID; + +@MaterializedView +public interface CyclistsByAge extends Cyclist { + @PartitionKey + UUID cid(); + @ClusteringColumn + 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..5100bfd --- /dev/null +++ b/src/test/java/net/helenus/test/integration/core/views/MaterializedViewTest.java @@ -0,0 +1,80 @@ +/* + * 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 java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import net.helenus.test.integration.core.views.Cyclist; +import net.helenus.test.integration.core.views.CyclistsByAge; +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; + +// 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 +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(); + + } +}