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;
|
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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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()]));
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package net.helenus.mapping;
|
||||||
|
|
||||||
public enum HelenusEntityType {
|
public enum HelenusEntityType {
|
||||||
TABLE,
|
TABLE,
|
||||||
|
VIEW,
|
||||||
TUPLE,
|
TUPLE,
|
||||||
UDT;
|
UDT;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
|
|
|
@ -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