Merged DATACASS-62

This commit is contained in:
David Webb
2013-12-13 16:42:11 -05:00
parent 04547b3864
commit 8d97077c3c
16 changed files with 782 additions and 1 deletions

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.core.cql.generator;
import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull;
import org.springframework.cassandra.core.keyspace.CreateIndexSpecification;
import org.springframework.util.StringUtils;
/**
* CQL generator for generating a <code>CREATE INDEX</code> statement.
*
* @author Matthew T. Adams
* @author David Webb
*/
public class CreateIndexCqlGenerator extends IndexNameCqlGenerator<CreateIndexSpecification> {
public CreateIndexCqlGenerator(CreateIndexSpecification specification) {
super(specification);
}
public StringBuilder toCql(StringBuilder cql) {
cql = noNull(cql);
cql.append("CREATE").append(spec().isCustom() ? " CUSTOM" : "").append(" INDEX ")
.append(spec().getIfNotExists() ? "IF NOT EXISTS " : "")
.append(StringUtils.hasText(spec().getNameAsIdentifier()) ? spec().getNameAsIdentifier() : "").append(" ON ")
.append(spec().getTableNameAsIdentifier()).append(" (").append(spec().getColumnName()).append(")");
if (spec().isCustom()) {
cql.append(" USING ").append(spec().getUsing());
}
cql.append(";");
return cql;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.core.cql.generator;
import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull;
import org.springframework.cassandra.core.keyspace.DropIndexSpecification;
/**
* CQL generator for generating a <code>DROP INDEX</code> statement.
*
* @author Matthew T. Adams
* @author David Webb
*/
public class DropIndexCqlGenerator extends IndexNameCqlGenerator<DropIndexSpecification> {
public DropIndexCqlGenerator(DropIndexSpecification specification) {
super(specification);
}
public StringBuilder toCql(StringBuilder cql) {
return noNull(cql).append("DROP INDEX ")
// .append(spec().getIfExists() ? "IF EXISTS " : "")
.append(spec().getNameAsIdentifier()).append(";");
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.core.cql.generator;
import org.springframework.cassandra.core.keyspace.IndexNameSpecification;
import org.springframework.util.Assert;
public abstract class IndexNameCqlGenerator<T extends IndexNameSpecification<T>> {
public abstract StringBuilder toCql(StringBuilder cql);
private IndexNameSpecification<T> specification;
public IndexNameCqlGenerator(IndexNameSpecification<T> specification) {
setSpecification(specification);
}
protected void setSpecification(IndexNameSpecification<T> specification) {
Assert.notNull(specification);
this.specification = specification;
}
@SuppressWarnings("unchecked")
public T getSpecification() {
return (T) specification;
}
/**
* Convenient synonymous method of {@link #getSpecification()}.
*/
protected T spec() {
return getSpecification();
}
public String toCql() {
return toCql(null).toString();
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.core.keyspace;
import static org.springframework.cassandra.core.cql.CqlStringUtils.checkIdentifier;
import static org.springframework.cassandra.core.cql.CqlStringUtils.identifize;
import org.springframework.util.StringUtils;
/**
* Builder class to construct a <code>CREATE INDEX</code> specification.
*
* @author Matthew T. Adams
* @author David Webb
*/
public class CreateIndexSpecification extends IndexNameSpecification<CreateIndexSpecification> implements
IndexDescriptor {
private boolean ifNotExists = false;
private boolean custom = false;
private String tableName;
private String columnName;
private String using;
/**
* Causes the inclusion of an <code>IF NOT EXISTS</code> clause.
*
* @return this
*/
public CreateIndexSpecification ifNotExists() {
return ifNotExists(true);
}
/**
* Toggles the inclusion of an <code>IF NOT EXISTS</code> clause.
*
* @return this
*/
public CreateIndexSpecification ifNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
return this;
}
public boolean getIfNotExists() {
return ifNotExists;
}
public boolean isCustom() {
return custom;
}
public CreateIndexSpecification using(String className) {
if (StringUtils.hasText(className)) {
this.using = className;
this.custom = true;
} else {
this.using = null;
this.custom = false;
}
return this;
}
public String getUsing() {
return using;
}
public String getColumnName() {
return columnName;
}
/**
* Sets the table name.
*
* @return this
*/
@SuppressWarnings("unchecked")
public CreateIndexSpecification tableName(String tableName) {
checkIdentifier(tableName);
this.tableName = tableName;
return this;
}
public String getTableName() {
return tableName;
}
public String getTableNameAsIdentifier() {
return identifize(tableName);
}
public CreateIndexSpecification columnName(String columnName) {
this.columnName = columnName;
return this;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.core.keyspace;
/**
* Builder class that supports the construction of <code>DROP INDEX</code> specifications.
*
* @author Matthew T. Adams
* @author David Webb
*/
public class DropIndexSpecification extends IndexNameSpecification<DropIndexSpecification> {
private boolean ifExists;
/*
* In CQL 3.1 this is supported so we can uncomment the exposure then.
* In the meantime, it will always be false and tests will pass.
*/
// public DropIndexSpecification ifExists() {
// return ifExists(true);
// }
//
// public DropIndexSpecification ifExists(boolean ifExists) {
// this.ifExists = ifExists;
// return this;
// }
//
// public boolean getIfExists() {
// return ifExists;
// }
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.core.keyspace;
/**
* Describes an index.
*
* @author Matthew T. Adams
* @author David Webb
*/
public interface IndexDescriptor {
/**
* Returns the name of the index.
*/
String getName();
/**
* Returns the table name for the index
*/
String getTableName();
/**
* Returns the name of the index as an identifer or quoted identifier as appropriate.
*/
String getNameAsIdentifier();
/**
* Returns the name of the table as an identifer or quoted identifier as appropriate.
*/
String getTableNameAsIdentifier();
String getColumnName();
String getUsing();
boolean isCustom();
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.core.keyspace;
import static org.springframework.cassandra.core.cql.CqlStringUtils.checkIdentifier;
import static org.springframework.cassandra.core.cql.CqlStringUtils.identifize;
/**
* Abstract builder class to support the construction of table specifications.
*
* @author David Webb
* @param <T> The subtype of the {@link IndexNameSpecification}
*/
public abstract class IndexNameSpecification<T extends IndexNameSpecification<T>> {
/**
* The name of the index.
*/
private String name;
/**
* Sets the index name.
*
* @return this
*/
@SuppressWarnings("unchecked")
public T name(String name) {
checkIdentifier(name);
this.name = name;
return (T) this;
}
public String getName() {
return name;
}
public String getNameAsIdentifier() {
return identifize(name);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.core.keyspace;
/**
* Class that offers static methods as entry points into the fluent API for building create, drop and alter index
* specifications. These methods are most convenient when imported statically.
*
* @author Matthew T. Adams
* @author David Webb
*/
public class IndexOperations {
/**
* Entry point into the {@link CreateIndexSpecification}'s fluent API to create a index. Convenient if imported
* statically.
*/
public static CreateIndexSpecification createIndex() {
return new CreateIndexSpecification();
}
/**
* Entry point into the {@link DropIndexSpecification}'s fluent API to drop a table. Convenient if imported
* statically.
*/
public static DropIndexSpecification dropIndex() {
return new DropIndexSpecification();
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2011-2013 the original author or 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 org.springframework.cassandra.test.integration.core.cql.generator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.springframework.cassandra.core.keyspace.IndexDescriptor;
import com.datastax.driver.core.ColumnMetadata.IndexMetadata;
import com.datastax.driver.core.Session;
public class CqlIndexSpecificationAssertions {
public static double DELTA = 1e-6; // delta for comparisons of doubles
public static void assertIndex(IndexDescriptor expected, String keyspace, Session session) {
IndexMetadata imd = session.getCluster().getMetadata().getKeyspace(keyspace.toLowerCase())
.getTable(expected.getTableName()).getColumn(expected.getColumnName()).getIndex();
assertEquals(expected.getName().toLowerCase(), imd.getName().toLowerCase());
}
public static void assertNoIndex(IndexDescriptor expected, String keyspace, Session session) {
IndexMetadata imd = session.getCluster().getMetadata().getKeyspace(keyspace.toLowerCase())
.getTable(expected.getTableName()).getColumn(expected.getColumnName()).getIndex();
assertNull(imd);
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.cassandra.test.integration.core.cql.generator;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertEquals;
import java.util.List;
import java.util.Map;

View File

@@ -0,0 +1,60 @@
package org.springframework.cassandra.test.integration.core.cql.generator;
import static org.springframework.cassandra.test.integration.core.cql.generator.CqlIndexSpecificationAssertions.assertIndex;
import org.cassandraunit.CassandraCQLUnit;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.cassandra.test.integration.AbstractEmbeddedCassandraIntegrationTest;
import org.springframework.cassandra.test.unit.core.cql.generator.CreateIndexCqlGeneratorTests.BasicTest;
import org.springframework.cassandra.test.unit.core.cql.generator.CreateIndexCqlGeneratorTests.CreateIndexTest;
/**
* Integration tests that reuse unit tests.
*
* @author Matthew T. Adams
*/
public class CreateIndexCqlGeneratorIntegrationTests {
/**
* Integration test base class that knows how to do everything except instantiate the concrete unit test type T.
*
* @author Matthew T. Adams
*
* @param <T> The concrete unit test class to which this integration test corresponds.
*/
public static abstract class Base<T extends CreateIndexTest> extends AbstractEmbeddedCassandraIntegrationTest {
T unit;
public abstract T unit();
@Test
public void test() {
unit = unit();
unit.prepare();
session.execute(unit.cql);
assertIndex(unit.specification, keyspace, session);
}
}
public static class BasicIntegrationTest extends Base<BasicTest> {
/**
* This loads any test specific Cassandra objects
*/
@Rule
public CassandraCQLUnit cassandraCQLUnit = new CassandraCQLUnit(new ClassPathCQLDataSet(
"integration/cql/generator/CreateIndexCqlGeneratorIntegrationTests-BasicTest.cql", this.keyspace),
CASSANDRA_CONFIG, CASSANDRA_HOST, CASSANDRA_NATIVE_PORT);
@Override
public BasicTest unit() {
return new BasicTest();
}
}
}

View File

@@ -0,0 +1,61 @@
package org.springframework.cassandra.test.integration.core.cql.generator;
import static org.springframework.cassandra.test.integration.core.cql.generator.CqlIndexSpecificationAssertions.assertIndex;
import static org.springframework.cassandra.test.integration.core.cql.generator.CqlIndexSpecificationAssertions.assertNoIndex;
import org.cassandraunit.CassandraCQLUnit;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cassandra.test.integration.AbstractEmbeddedCassandraIntegrationTest;
import org.springframework.cassandra.test.unit.core.cql.generator.CreateIndexCqlGeneratorTests;
import org.springframework.cassandra.test.unit.core.cql.generator.DropIndexCqlGeneratorTests;
/**
* Integration tests that reuse unit tests.
*
* @author Matthew T. Adams
*/
public class IndexLifecycleCqlGeneratorIntegrationTests extends AbstractEmbeddedCassandraIntegrationTest {
Logger log = LoggerFactory.getLogger(IndexLifecycleCqlGeneratorIntegrationTests.class);
/**
* This loads any test specific Cassandra objects
*/
@Rule
public CassandraCQLUnit cassandraCQLUnit = new CassandraCQLUnit(new ClassPathCQLDataSet(
"integration/cql/generator/CreateIndexCqlGeneratorIntegrationTests-BasicTest.cql", this.keyspace),
CASSANDRA_CONFIG, CASSANDRA_HOST, CASSANDRA_NATIVE_PORT);
@Test
public void lifecycleTest() {
CreateIndexCqlGeneratorTests.BasicTest createTest = new CreateIndexCqlGeneratorTests.BasicTest();
DropIndexCqlGeneratorTests.BasicTest dropTest = new DropIndexCqlGeneratorTests.BasicTest();
DropIndexCqlGeneratorTests.IfExistsTest dropIfExists = new DropIndexCqlGeneratorTests.IfExistsTest();
createTest.prepare();
dropTest.prepare();
dropIfExists.prepare();
log.info(createTest.cql);
session.execute(createTest.cql);
assertIndex(createTest.specification, keyspace, session);
log.info(dropTest.cql);
session.execute(dropTest.cql);
assertNoIndex(createTest.specification, keyspace, session);
// log.info(dropIfExists.cql);
// session.execute(dropIfExists.cql);
//
// assertNoIndex(createTest.specification, keyspace, session);
}
}

View File

@@ -0,0 +1,60 @@
package org.springframework.cassandra.test.unit.core.cql.generator;
import static org.junit.Assert.assertTrue;
import static org.springframework.cassandra.core.keyspace.IndexOperations.createIndex;
import org.junit.Test;
import org.springframework.cassandra.core.cql.generator.CreateIndexCqlGenerator;
import org.springframework.cassandra.core.keyspace.CreateIndexSpecification;
public class CreateIndexCqlGeneratorTests {
/**
* Asserts that the preamble is first & correctly formatted in the given CQL string.
*/
public static void assertPreamble(String indexName, String tableName, String cql) {
assertTrue(cql.startsWith("CREATE INDEX " + indexName + " ON " + tableName));
}
/**
* Asserts that the given list of columns definitions are contained in the given CQL string properly.
*
* @param columnSpec IE, "(foo)"
*/
public static void assertColumn(String columnName, String cql) {
assertTrue(cql.contains("(" + columnName + ")"));
}
/**
* Convenient base class that other test classes can use so as not to repeat the generics declarations or
* {@link #generator()} method.
*/
public static abstract class CreateIndexTest extends
IndexOperationCqlGeneratorTest<CreateIndexSpecification, CreateIndexCqlGenerator> {
public CreateIndexCqlGenerator generator() {
return new CreateIndexCqlGenerator(specification);
}
}
public static class BasicTest extends CreateIndexTest {
public String name = "myindex";
public String tableName = "mytable";
public String column1 = "column1";
public CreateIndexSpecification specification() {
return createIndex().name(name).tableName(tableName).columnName(column1);
}
@Test
public void test() {
prepare();
assertPreamble(name, tableName, cql);
assertColumn(column1, cql);
}
}
}

View File

@@ -0,0 +1,70 @@
package org.springframework.cassandra.test.unit.core.cql.generator;
import static org.junit.Assert.assertTrue;
import static org.springframework.cassandra.core.keyspace.IndexOperations.dropIndex;
import org.junit.Test;
import org.springframework.cassandra.core.cql.generator.DropIndexCqlGenerator;
import org.springframework.cassandra.core.keyspace.DropIndexSpecification;
public class DropIndexCqlGeneratorTests {
/**
* Asserts that the preamble is first & correctly formatted in the given CQL string.
*/
public static void assertStatement(String indexName, boolean ifExists, String cql) {
assertTrue(cql.equals("DROP INDEX " + (ifExists ? "IF EXISTS " : "") + indexName + ";"));
}
/**
* Convenient base class that other test classes can use so as not to repeat the generics declarations.
*/
public static abstract class DropIndexTest extends
IndexOperationCqlGeneratorTest<DropIndexSpecification, DropIndexCqlGenerator> {
}
public static class BasicTest extends DropIndexTest {
public String name = "myindex";
public DropIndexSpecification specification() {
return dropIndex().name(name);
}
public DropIndexCqlGenerator generator() {
return new DropIndexCqlGenerator(specification);
}
@Test
public void test() {
prepare();
assertStatement(name, false, cql);
}
}
public static class IfExistsTest extends DropIndexTest {
public String name = "myindex";
public DropIndexSpecification specification() {
return dropIndex().name(name)
// .ifExists()
;
}
public DropIndexCqlGenerator generator() {
return new DropIndexCqlGenerator(specification);
}
@Test
public void test() {
prepare();
// assertStatement(name, true, cql);
assertStatement(name, false, cql);
}
}
}

View File

@@ -0,0 +1,38 @@
package org.springframework.cassandra.test.unit.core.cql.generator;
import org.junit.Test;
import org.springframework.cassandra.core.cql.generator.IndexNameCqlGenerator;
import org.springframework.cassandra.core.keyspace.IndexNameSpecification;
/**
* Useful test class that specifies just about as much as you can for a CQL generation test. Intended to be extended by
* classes that contain methods annotated with {@link Test}. Everything is public because this is a test class with no
* need for encapsulation, and it makes for easier reuse in other tests like integration tests (hint hint).
*
* @author Matthew T. Adams
* @author David Webb
*
* @param <S> The type of the {@link IndexNameSpecification}
* @param <G> The type of the {@link IndexNameCqlGenerator}
*/
public abstract class IndexOperationCqlGeneratorTest<S extends IndexNameSpecification<?>, G extends IndexNameCqlGenerator<?>> {
public abstract S specification();
public abstract G generator();
public String indexName;
public S specification;
public G generator;
public String cql;
public void prepare() {
this.specification = specification();
this.generator = generator();
this.cql = generateCql();
}
public String generateCql() {
return generator.toCql();
}
}

View File

@@ -0,0 +1 @@
create table mytable (id uuid primary key, column1 text);