/*
* Copyright (C) 2012-2015 DataStax Inc.
*
* 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;
import com.datastax.driver.core.utils.CassandraVersion;
import org.testng.SkipException;
import org.testng.annotations.Test;
import static com.datastax.driver.core.Assertions.assertThat;
import static com.datastax.driver.core.DataType.cint;
import static com.datastax.driver.core.DataType.text;
import static com.datastax.driver.core.TestUtils.serializeForDynamicCompositeType;
@CassandraVersion("2.2.0")
@CCMConfig(config = "enable_user_defined_functions:true")
public class AggregateMetadataTest extends CCMTestsSupport {
@Test(groups = "short")
public void should_parse_and_format_aggregate_with_initcond_and_no_finalfunc() {
// given
String cqlFunction = String.format("CREATE FUNCTION %s.cat(s text,v int) RETURNS NULL ON NULL INPUT RETURNS text LANGUAGE java AS 'return s+v;';", keyspace);
String cqlAggregate = String.format("CREATE AGGREGATE %s.cat_tos(int) SFUNC cat STYPE text INITCOND '0';", keyspace);
// when
session().execute(cqlFunction);
session().execute(cqlAggregate);
// then
KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
FunctionMetadata stateFunc = keyspace.getFunction("cat", text(), cint());
AggregateMetadata aggregate = keyspace.getAggregate("cat_tos", cint());
assertThat(aggregate).isNotNull();
assertThat(aggregate.getSignature()).isEqualTo("cat_tos(int)");
assertThat(aggregate.getSimpleName()).isEqualTo("cat_tos");
assertThat(aggregate.getArgumentTypes()).containsExactly(cint());
assertThat(aggregate.getFinalFunc()).isNull();
assertThat(aggregate.getInitCond()).isEqualTo("0");
assertThat(aggregate.getReturnType()).isEqualTo(text());
assertThat(aggregate.getStateFunc()).isEqualTo(stateFunc);
assertThat(aggregate.getStateType()).isEqualTo(text());
assertThat(aggregate.toString()).isEqualTo(cqlAggregate);
assertThat(aggregate.exportAsString()).isEqualTo(String.format("CREATE AGGREGATE %s.cat_tos(int)\n"
+ "SFUNC cat STYPE text\n"
+ "INITCOND '0';", this.keyspace));
}
@Test(groups = "short")
public void should_parse_and_format_aggregate_with_no_arguments() {
// given
String cqlFunction = String.format("CREATE FUNCTION %s.inc(i int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java AS 'return i+1;';", keyspace);
String cqlAggregate = String.format("CREATE AGGREGATE %s.mycount() SFUNC inc STYPE int INITCOND 0;", keyspace);
// when
session().execute(cqlFunction);
session().execute(cqlAggregate);
// then
KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
FunctionMetadata stateFunc = keyspace.getFunction("inc", cint());
AggregateMetadata aggregate = keyspace.getAggregate("mycount");
assertThat(aggregate).isNotNull();
assertThat(aggregate.getSignature()).isEqualTo("mycount()");
assertThat(aggregate.getSimpleName()).isEqualTo("mycount");
assertThat(aggregate.getArgumentTypes()).isEmpty();
assertThat(aggregate.getFinalFunc()).isNull();
assertThat(aggregate.getInitCond()).isEqualTo(0);
assertThat(aggregate.getReturnType()).isEqualTo(cint());
assertThat(aggregate.getStateFunc()).isEqualTo(stateFunc);
assertThat(aggregate.getStateType()).isEqualTo(cint());
assertThat(aggregate.toString()).isEqualTo(cqlAggregate);
assertThat(aggregate.exportAsString()).isEqualTo(String.format("CREATE AGGREGATE %s.mycount()\n"
+ "SFUNC inc STYPE int\n"
+ "INITCOND 0;", this.keyspace));
}
@Test(groups = "short")
public void should_parse_and_format_aggregate_with_final_function() {
// given
String cqlFunction1 = String.format("CREATE FUNCTION %s.plus(i int, j int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java AS 'return i+j;';", keyspace);
String cqlFunction2 = String.format("CREATE FUNCTION %s.announce(i int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java AS 'return i;';", keyspace);
String cqlAggregate = String.format("CREATE AGGREGATE %s.prettysum(int) SFUNC plus STYPE int FINALFUNC announce INITCOND 0;", keyspace);
// when
session().execute(cqlFunction1);
session().execute(cqlFunction2);
session().execute(cqlAggregate);
// then
KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
FunctionMetadata stateFunc = keyspace.getFunction("plus", cint(), cint());
FunctionMetadata finalFunc = keyspace.getFunction("announce", cint());
AggregateMetadata aggregate = keyspace.getAggregate("prettysum", cint());
assertThat(aggregate).isNotNull();
assertThat(aggregate.getSignature()).isEqualTo("prettysum(int)");
assertThat(aggregate.getSimpleName()).isEqualTo("prettysum");
assertThat(aggregate.getArgumentTypes()).containsExactly(cint());
assertThat(aggregate.getFinalFunc()).isEqualTo(finalFunc);
assertThat(aggregate.getInitCond()).isEqualTo(0);
assertThat(aggregate.getReturnType()).isEqualTo(cint());
assertThat(aggregate.getStateFunc()).isEqualTo(stateFunc);
assertThat(aggregate.getStateType()).isEqualTo(cint());
assertThat(aggregate.toString()).isEqualTo(cqlAggregate);
assertThat(aggregate.exportAsString()).isEqualTo(String.format("CREATE AGGREGATE %s.prettysum(int)\n"
+ "SFUNC plus STYPE int\n"
+ "FINALFUNC announce\n"
+ "INITCOND 0;", this.keyspace));
}
@Test(groups = "short")
public void should_parse_and_format_aggregate_with_no_initcond() {
// given
String cqlFunction = String.format("CREATE FUNCTION %s.plus2(i int, j int) CALLED ON NULL INPUT RETURNS int LANGUAGE java AS 'return i+j;';", keyspace);
String cqlAggregate = String.format("CREATE AGGREGATE %s.sum(int) SFUNC plus2 STYPE int;", keyspace);
// when
session().execute(cqlFunction);
session().execute(cqlAggregate);
// then
KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
FunctionMetadata stateFunc = keyspace.getFunction("plus2", cint(), cint());
AggregateMetadata aggregate = keyspace.getAggregate("sum", cint());
assertThat(aggregate).isNotNull();
assertThat(aggregate.getSignature()).isEqualTo("sum(int)");
assertThat(aggregate.getSimpleName()).isEqualTo("sum");
assertThat(aggregate.getArgumentTypes()).containsExactly(cint());
assertThat(aggregate.getFinalFunc()).isNull();
assertThat(aggregate.getInitCond()).isNull();
assertThat(aggregate.getReturnType()).isEqualTo(cint());
assertThat(aggregate.getStateFunc()).isEqualTo(stateFunc);
assertThat(aggregate.getStateType()).isEqualTo(cint());
assertThat(aggregate.toString()).isEqualTo(cqlAggregate);
assertThat(aggregate.exportAsString()).isEqualTo(String.format("CREATE AGGREGATE %s.sum(int)\n"
+ "SFUNC plus2 STYPE int;", this.keyspace));
}
@Test(groups = "short")
public void should_parse_and_format_aggregate_with_udts() {
// given
String cqlFunction = String.format(
"CREATE FUNCTION %s.\"MY_FUNC\"(address1 \"Address\", address2 \"Address\") "
+ "CALLED ON NULL INPUT "
+ "RETURNS \"Address\" "
+ "LANGUAGE java "
+ "AS 'return address1;'", keyspace);
String cqlAggregate = String.format(
"CREATE AGGREGATE %s.\"MY_AGGREGATE\"(\"Address\") "
+ "SFUNC \"MY_FUNC\" "
+ "STYPE \"Address\";",
keyspace);
// when
session().execute(cqlFunction);
session().execute(cqlAggregate);
// then
KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
UserType addressType = keyspace.getUserType("\"Address\"");
FunctionMetadata stateFunc = keyspace.getFunction("\"MY_FUNC\"", addressType, addressType);
AggregateMetadata aggregate = keyspace.getAggregate("\"MY_AGGREGATE\"", addressType);
assertThat(aggregate).isNotNull();
assertThat(aggregate.getSignature()).isEqualTo("\"MY_AGGREGATE\"(\"Address\")");
assertThat(aggregate.getSimpleName()).isEqualTo("MY_AGGREGATE");
assertThat(aggregate.getArgumentTypes()).containsExactly(addressType);
assertThat(aggregate.getFinalFunc()).isNull();
assertThat(aggregate.getInitCond()).isNull();
assertThat(aggregate.getReturnType()).isEqualTo(addressType);
assertThat(aggregate.getStateFunc()).isEqualTo(stateFunc);
assertThat(aggregate.getStateType()).isEqualTo(addressType);
assertThat(aggregate.toString()).isEqualTo(cqlAggregate);
}
/**
* Validates aggregates with DynamicCompositeType state types and an initcond value that is a literal, i.e.:
* 's@foo:i@32' can be appropriately parsed and generate a CQL string with the init cond as a hex string, i.e.:
* 0x80730003666f6f00806900040000002000.
*
* @jira_ticket JAVA-1046
* @test_category metadata
* @since 3.0.1
*/
@Test(groups = "short")
@CassandraVersion("2.2.0")
public void should_parse_and_format_aggregate_with_composite_type_literal_initcond() {
VersionNumber ver = ccm().getCassandraVersion();
if (ver.getMajor() == 3) {
if ((ver.getMinor() >= 1 && ver.getMinor() < 4) || (ver.getMinor() == 0 && ver.getPatch() < 4)) {
throw new SkipException("Requires C* 2.2.X, 3.0.4+ or 3.4.X+");
}
}
parse_and_format_aggregate_with_composite_type("ag0", "'s@foo:i@32'");
}
/**
* Validates aggregates with DynamicCompositeType state types and an initcond value that is a hex string
* representing the bytes for the type, i.e.: 0x80730003666f6f00806900040000002000' can be appropriately parsed
* and generates an equivalent CQL string.
*
* @jira_ticket JAVA-1046
* @test_category metadata
* @since 3.0.1
*/
@Test(groups = "short")
@CassandraVersion("3.4")
public void should_parse_and_format_aggregate_with_composite_type_hex_initcond() {
VersionNumber ver = ccm().getCassandraVersion();
if ((ver.getMinor() >= 1 && ver.getMinor() < 4)) {
throw new SkipException("Requires 3.0.4+ or 3.4.X+");
}
parse_and_format_aggregate_with_composite_type("ag1", "0x80730003666f6f00806900040000002000");
}
public void parse_and_format_aggregate_with_composite_type(String aggregateName, String initCond) {
// given
DataType custom = DataType.custom(
"org.apache.cassandra.db.marshal.DynamicCompositeType(" +
"s=>org.apache.cassandra.db.marshal.UTF8Type," +
"i=>org.apache.cassandra.db.marshal.Int32Type)");
String cqlFunction = String.format(
"CREATE FUNCTION %s.%s_id(i %s) " +
"RETURNS NULL ON NULL INPUT " +
"RETURNS %s " +
"LANGUAGE java " +
"AS 'return i;';", keyspace, aggregateName, custom, custom);
session().execute(cqlFunction);
String cqlAggregate = String.format(
"CREATE AGGREGATE %s.%s() " +
"SFUNC %s_id " +
"STYPE %s " +
"INITCOND %s;", keyspace, aggregateName, aggregateName, custom, initCond);
String expectedAggregate = String.format(
"CREATE AGGREGATE %s.%s() " +
"SFUNC %s_id " +
"STYPE %s " +
"INITCOND 0x80730003666f6f00806900040000002000;", keyspace, aggregateName, aggregateName, custom);
int agCount = 0;
// when
session().execute(cqlAggregate);
// then
KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
FunctionMetadata stateFunc = keyspace.getFunction(aggregateName + "_id", custom);
AggregateMetadata aggregate = keyspace.getAggregate(aggregateName);
assertThat(aggregate).isNotNull();
assertThat(aggregate.getSignature()).isEqualTo(aggregateName + "()");
assertThat(aggregate.getSimpleName()).isEqualTo(aggregateName);
assertThat(aggregate.getArgumentTypes()).isEmpty();
assertThat(aggregate.getFinalFunc()).isNull();
assertThat(aggregate.getInitCond()).isEqualTo(serializeForDynamicCompositeType("foo", 32));
assertThat(aggregate.getReturnType()).isEqualTo(custom);
assertThat(aggregate.getStateFunc()).isEqualTo(stateFunc);
assertThat(aggregate.getStateType()).isEqualTo(custom);
assertThat(aggregate.toString()).isEqualTo(expectedAggregate);
}
@Override
public void onTestContextInitialized() {
execute(
String.format("CREATE TYPE IF NOT EXISTS %s.phone (number text)", keyspace),
String.format("CREATE TYPE IF NOT EXISTS %s.\"Address\" ("
+ " street text,"
+ " city text,"
+ " zip int,"
+ " phones frozen<set<frozen<phone>>>,"
+ " location frozen<tuple<float, float>>"
+ ")", keyspace)
);
}
}