Skip to content

Commit

Permalink
closes #105: delete collection command implementation (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Senic authored Feb 16, 2023
1 parent ff24e79 commit 60d5d07
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 19 deletions.
11 changes: 11 additions & 0 deletions src/main/java/io/stargate/sgv2/jsonapi/StargateJsonApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,17 @@
}
}
"""),
@ExampleObject(
name = "deleteCollection",
summary = "`DeleteCollection` command",
value =
"""
{
"deleteCollection": {
"name": "events"
}
}
"""),
@ExampleObject(
name = "resultCount",
summary = "countDocuments command result",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.stargate.sgv2.jsonapi.api.model.command.impl.CountDocumentsCommands;
import io.stargate.sgv2.jsonapi.api.model.command.impl.CreateCollectionCommand;
import io.stargate.sgv2.jsonapi.api.model.command.impl.CreateNamespaceCommand;
import io.stargate.sgv2.jsonapi.api.model.command.impl.DeleteCollectionCommand;
import io.stargate.sgv2.jsonapi.api.model.command.impl.DeleteOneCommand;
import io.stargate.sgv2.jsonapi.api.model.command.impl.FindCommand;
import io.stargate.sgv2.jsonapi.api.model.command.impl.FindOneAndUpdateCommand;
Expand Down Expand Up @@ -38,6 +39,7 @@
@JsonSubTypes.Type(value = CountDocumentsCommands.class),
@JsonSubTypes.Type(value = CreateNamespaceCommand.class),
@JsonSubTypes.Type(value = CreateCollectionCommand.class),
@JsonSubTypes.Type(value = DeleteCollectionCommand.class),
@JsonSubTypes.Type(value = DeleteOneCommand.class),
@JsonSubTypes.Type(value = FindCommand.class),
@JsonSubTypes.Type(value = FindOneCommand.class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.stargate.sgv2.jsonapi.api.model.command.NamespaceCommand;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotBlank;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Schema(description = "Command that creates a collection.")
@JsonTypeName("createCollection")
public record CreateCollectionCommand(
@NotNull
@NotBlank
@Schema(
description = "Name of the collection",
implementation = Object.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.stargate.sgv2.jsonapi.api.model.command.impl;

import com.fasterxml.jackson.annotation.JsonTypeName;
import io.stargate.sgv2.jsonapi.api.model.command.NamespaceCommand;
import javax.validation.constraints.NotBlank;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

/**
* Command for deleting a collection.
*
* @param name Name of the collection
*/
@Schema(description = "Command that deletes a collection if one exists.")
@JsonTypeName("deleteCollection")
public record DeleteCollectionCommand(
@NotBlank
@Schema(
description = "Name of the collection",
implementation = Object.class,
type = SchemaType.OBJECT)
String name)
implements NamespaceCommand {}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public NamespaceResource(CommandProcessor commandProcessor) {
schema = @Schema(anyOf = {CreateCollectionCommand.class}),
examples = {
@ExampleObject(ref = "createCollection"),
@ExampleObject(ref = "deleteCollection"),
}))
@APIResponses(
@APIResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public record CreateNamespaceOperation(String name, String replicationMap) imple

// simple pattern for the cql
private static final String CREATE_KEYSPACE_CQL =
"CREATE KEYSPACE IF NOT EXISTS \"%s\" WITH REPLICATION = %s;\n";
"CREATE KEYSPACE IF NOT EXISTS \"%s\" WITH REPLICATION = %s;";

/** {@inheritDoc} */
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.stargate.sgv2.jsonapi.service.operation.model.impl;

import io.smallrye.mutiny.Uni;
import io.stargate.bridge.proto.QueryOuterClass;
import io.stargate.sgv2.jsonapi.api.model.command.CommandContext;
import io.stargate.sgv2.jsonapi.api.model.command.CommandResult;
import io.stargate.sgv2.jsonapi.service.bridge.executor.QueryExecutor;
import io.stargate.sgv2.jsonapi.service.operation.model.Operation;
import java.util.function.Supplier;

/**
* Implementation of the delete collection.
*
* @param context Command context, carries namespace of the collection.
* @param name Collection name.
*/
public record DeleteCollectionOperation(CommandContext context, String name) implements Operation {

private static final String DROP_TABLE_CQL = "DROP TABLE IF EXISTS %s.%s;";

@Override
public Uni<Supplier<CommandResult>> execute(QueryExecutor queryExecutor) {
String cql = DROP_TABLE_CQL.formatted(context.namespace(), name);
QueryOuterClass.Query query = QueryOuterClass.Query.newBuilder().setCql(cql).build();

// execute
return queryExecutor
.executeSchemaChange(query)

// if we have a result always respond positively
.map(any -> new SchemaChangeResult(true));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.stargate.sgv2.jsonapi.service.resolver.model.impl;

import io.stargate.sgv2.jsonapi.api.model.command.CommandContext;
import io.stargate.sgv2.jsonapi.api.model.command.impl.DeleteCollectionCommand;
import io.stargate.sgv2.jsonapi.service.operation.model.Operation;
import io.stargate.sgv2.jsonapi.service.operation.model.impl.DeleteCollectionOperation;
import io.stargate.sgv2.jsonapi.service.resolver.model.CommandResolver;
import javax.enterprise.context.ApplicationScoped;

/** Resolver for the {@link DeleteCollectionCommand}. */
@ApplicationScoped
public class DeleteCollectionResolver implements CommandResolver<DeleteCollectionCommand> {
@Override
public Class<DeleteCollectionCommand> getCommandClass() {
return DeleteCollectionCommand.class;
}

@Override
public Operation resolveCommand(CommandContext ctx, DeleteCollectionCommand command) {
return new DeleteCollectionOperation(ctx, command.name());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.stargate.sgv2.jsonapi.api.model.command.impl;

import static org.assertj.core.api.Assertions.assertThat;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import io.stargate.sgv2.common.testprofiles.NoGlobalResourcesTestProfile;
import java.util.Set;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

@QuarkusTest
@TestProfile(NoGlobalResourcesTestProfile.Impl.class)
class DeleteCollectionCommandTest {

@Inject ObjectMapper objectMapper;

@Inject Validator validator;

@Nested
class Validation {

@Test
public void noName() throws Exception {
String json =
"""
{
"deleteCollection": {
}
}
""";

DeleteCollectionCommand command = objectMapper.readValue(json, DeleteCollectionCommand.class);
Set<ConstraintViolation<DeleteCollectionCommand>> result = validator.validate(command);

assertThat(result)
.isNotEmpty()
.extracting(ConstraintViolation::getMessage)
.contains("must not be blank");
}

@Test
public void nameBlank() throws Exception {
String json =
"""
{
"deleteCollection": {
"name": " "
}
}
""";

DeleteCollectionCommand command = objectMapper.readValue(json, DeleteCollectionCommand.class);
Set<ConstraintViolation<DeleteCollectionCommand>> result = validator.validate(command);

assertThat(result)
.isNotEmpty()
.extracting(ConstraintViolation::getMessage)
.contains("must not be blank");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,128 @@ public static void enableLog() {
}

@Nested
class PostCommand {
class CreateCollection {

@Test
public void happyPath() {
String json =
String.format(
"""
{
"createCollection": {
"name": "%s"
}
}
""",
"col" + RandomStringUtils.randomNumeric(16));
"""
{
"createCollection": {
"name": "%s"
}
}
"""
.formatted("col" + RandomStringUtils.randomNumeric(16));

given()
.header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken())
.contentType(ContentType.JSON)
.body(json)
.when()
.post(NamespaceResource.BASE_PATH, keyspaceId.asInternal())
.then()
.statusCode(200)
.body("status.ok", is(1));
}
}

@Nested
class DeleteCollection {

@Test
public void happyPath() {
String collection = RandomStringUtils.randomAlphabetic(16);

// first create
String createJson =
"""
{
"createCollection": {
"name": "%s"
}
}
"""
.formatted(collection);

given()
.header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken())
.contentType(ContentType.JSON)
.body(createJson)
.when()
.post(NamespaceResource.BASE_PATH, keyspaceId.asInternal())
.then()
.statusCode(200)
.body("status.ok", is(1));

// then delete
String json =
"""
{
"deleteCollection": {
"name": "%s"
}
}
"""
.formatted(collection);

given()
.header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken())
.contentType(ContentType.JSON)
.body(json)
.when()
.post(NamespaceResource.BASE_PATH, keyspaceId.asInternal())
.then()
.statusCode(200);
.statusCode(200)
.body("status.ok", is(1));
}

@Test
public void notExisting() {
String collection = RandomStringUtils.randomAlphabetic(16);

// delete not existing
String json =
"""
{
"deleteCollection": {
"name": "%s"
}
}
"""
.formatted(collection);

given()
.header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken())
.contentType(ContentType.JSON)
.body(json)
.when()
.post(NamespaceResource.BASE_PATH, keyspaceId.asInternal())
.then()
.statusCode(200)
.body("status.ok", is(1));
}

@Test
public void invalidCommand() {
String json =
"""
{
"deleteCollection": {
}
}
""";

given()
.header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken())
.contentType(ContentType.JSON)
.body(json)
.when()
.post(NamespaceResource.BASE_PATH, keyspaceId.asInternal())
.then()
.statusCode(200)
.body("errors[0].message", is(not(blankString())))
.body("errors[0].exceptionClass", is("ConstraintViolationException"));
}
}

Expand Down Expand Up @@ -92,11 +192,11 @@ public void malformedBody() {
public void unknownCommand() {
String json =
"""
{
"unknownCommand": {
}
}
""";
{
"unknownCommand": {
}
}
""";

given()
.header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken())
Expand Down
Loading

0 comments on commit 60d5d07

Please sign in to comment.