Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public record Options(@Nullable @Valid Replication replication) {}
+ "For NetworkTopologyStrategy, use {\"class\": \"NetworkTopologyStrategy\", \"datacenter_name\": N, ...}.")
public record Replication(
@NotNull
@Pattern(regexp = "SimpleStrategy|NetworkTopologyStrategy")
@Pattern(regexp = "(SimpleStrategy|NetworkTopologyStrategy)")
@JsonProperty("class")
@Schema(
description =
Expand All @@ -53,7 +53,7 @@ public record Replication(
+ "For NetworkTopologyStrategy, use datacenter names as keys with replication factor as values "
+ "(e.g. 'dc1': 3, 'dc2': 2).",
type = SchemaType.OBJECT)
Map<String, Integer> strategyOptions) {}
Map<String, @NotNull Integer> strategyOptions) {}
Comment thread
erichare marked this conversation as resolved.

/** {@inheritDoc} */
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public record Options(@Nullable @Valid Replication replication) {}
deprecated = true)
public record Replication(
@NotNull()
@Pattern(regexp = "SimpleStrategy|NetworkTopologyStrategy")
@Pattern(regexp = "(SimpleStrategy|NetworkTopologyStrategy)")
@JsonProperty("class")
@Schema(
description =
Expand All @@ -67,7 +67,7 @@ public record Replication(
+ "For NetworkTopologyStrategy, use datacenter names as keys with replication factor as values "
+ "(e.g. 'dc1': 3, 'dc2': 2).",
type = SchemaType.OBJECT)
Map<String, Integer> strategyOptions) {}
Map<String, @NotNull Integer> strategyOptions) {}

/** {@inheritDoc} */
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public enum Code implements ErrorCode<SchemaException> {
INVALID_CREATE_COLLECTION_OPTIONS,
INVALID_FORMAT_FOR_INDEX_CREATION_COLUMN,
INVALID_INDEXING_DEFINITION,
INVALID_REPLICATION_DATA_CENTER_NAME,
INVALID_USAGE_OF_VECTORIZE, // legacy: converted from ErrorCodeV1
INVALID_USER_DEFINED_TYPE_NAME,
LEXICAL_FEATURE_NOT_ENABLED,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,69 @@
package io.stargate.sgv2.jsonapi.service.operation.keyspaces;

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.querybuilder.SchemaBuilder;
import com.datastax.oss.driver.api.querybuilder.schema.CreateKeyspace;
import com.datastax.oss.driver.api.querybuilder.schema.CreateKeyspaceStart;
import io.smallrye.mutiny.Uni;
import io.stargate.sgv2.jsonapi.api.model.command.CommandResult;
import io.stargate.sgv2.jsonapi.api.request.RequestContext;
import io.stargate.sgv2.jsonapi.service.cqldriver.executor.QueryExecutor;
import io.stargate.sgv2.jsonapi.service.operation.Operation;
import io.stargate.sgv2.jsonapi.service.operation.collections.SchemaChangeResult;
import java.util.Map;
import java.util.function.Supplier;

/**
* Operation that creates a new Cassandra keyspace that serves as a namespace for the Data API.
* Operation that creates a Cassandra keyspace for the Data API.
*
* @param name Name of the keyspace to create.
* @param replicationMap A replication json, see
* https://docs.datastax.com/en/cql-oss/3.3/cql/cql_reference/cqlCreateKeyspace.html#Table2.Replicationstrategyclassandfactorsettings.
* <p>The keyspace name is already a CQL identifier when it reaches this operation. Replication map
* keys are still plain strings because that is what the driver accepts, so the resolver validates
* datacenter names before creating this operation.
*/
public record CreateKeyspaceOperation(String name, String replicationMap) implements Operation {
public record CreateKeyspaceOperation(
CqlIdentifier name, String strategy, Map<String, Integer> strategyOptions)
implements Operation {

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

/** {@inheritDoc} */
@Override
public Uni<Supplier<CommandResult>> execute(
RequestContext dataApiRequestInfo, QueryExecutor queryExecutor) {
SimpleStatement createKeyspace =
SimpleStatement.newInstance(String.format(CREATE_KEYSPACE_CQL, name, replicationMap));
// execute
SimpleStatement createKeyspace = buildStatement();
return queryExecutor
.executeCreateSchemaChange(dataApiRequestInfo, createKeyspace)

// if we have a result always respond positively
.map(any -> new SchemaChangeResult(any.wasApplied()));
}

private SimpleStatement buildStatement() {
CreateKeyspaceStart start = SchemaBuilder.createKeyspace(name).ifNotExists();
Map<String, Integer> safeStrategyOptions = strategyOptionsOrEmpty();

CreateKeyspace withReplication;
if (NETWORK_TOPOLOGY_STRATEGY.equals(strategy)) {
withReplication = start.withNetworkTopologyStrategy(safeStrategyOptions);
} else {
int replicationFactor =
safeStrategyOptions.getOrDefault("replication_factor", DEFAULT_REPLICATION_FACTOR);
withReplication = start.withSimpleStrategy(replicationFactor);
}
return withReplication.build();
}

private Map<String, Integer> strategyOptionsOrEmpty() {
if (strategyOptions == null) {
return Map.of();
}
strategyOptions.forEach(
(key, value) -> {
if (value == null) {
throw new IllegalArgumentException(
"Replication strategy option value must not be null for key '%s'".formatted(key));
}
});
return strategyOptions;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.stargate.sgv2.jsonapi.service.operation.keyspaces;

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.querybuilder.SchemaBuilder;
import io.smallrye.mutiny.Uni;
import io.stargate.sgv2.jsonapi.api.model.command.CommandResult;
import io.stargate.sgv2.jsonapi.api.request.RequestContext;
Expand All @@ -9,27 +11,18 @@
import io.stargate.sgv2.jsonapi.service.operation.collections.SchemaChangeResult;
import java.util.function.Supplier;

/**
* Operation that drops a Cassandra keyspace if it exists.
*
* @param name Name of the keyspace to drop.
*/
public record DropKeyspaceOperation(String name) implements Operation {

// simple pattern for the cql
private static final String DROP_KEYSPACE_CQL = "DROP KEYSPACE IF EXISTS \"%s\";";
public record DropKeyspaceOperation(CqlIdentifier name) implements Operation {

/** {@inheritDoc} */
@Override
public Uni<Supplier<CommandResult>> execute(
RequestContext dataApiRequestInfo, QueryExecutor queryExecutor) {
SimpleStatement deleteStatement =
SimpleStatement.newInstance(DROP_KEYSPACE_CQL.formatted(name));
// execute
return queryExecutor
.executeDropSchemaChange(dataApiRequestInfo, deleteStatement)

// if we have a result always respond positively
.executeDropSchemaChange(dataApiRequestInfo, buildStatement())
.map(any -> new SchemaChangeResult(any.wasApplied()));
}

private SimpleStatement buildStatement() {
return SchemaBuilder.dropKeyspace(name).ifExists().build();
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package io.stargate.sgv2.jsonapi.service.resolver;

import static io.stargate.sgv2.jsonapi.util.ApiOptionUtils.getOrDefault;

import io.stargate.sgv2.jsonapi.api.model.command.CommandContext;
import io.stargate.sgv2.jsonapi.api.model.command.impl.CreateKeyspaceCommand;
import io.stargate.sgv2.jsonapi.service.operation.Operation;
import io.stargate.sgv2.jsonapi.service.operation.keyspaces.CreateKeyspaceOperation;
import io.stargate.sgv2.jsonapi.service.schema.DatabaseSchemaObject;
import io.stargate.sgv2.jsonapi.service.schema.naming.NamingRules;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.Map;

/**
* Command resolver for {@link CreateKeyspaceCommand}. Responsible for creating the replication map.
* Resolve a {@link CreateKeyspaceCommand} to a {@link CreateKeyspaceOperation}
*/
/** Command resolver for {@link CreateKeyspaceCommand}. */
@ApplicationScoped
public class CreateKeyspaceCommandResolver
extends CreateNamespaceKeyspaceCommandResolver<CreateKeyspaceCommand> {
extends KeyspaceDDLCommandResolver<CreateKeyspaceCommand> {

@Override
public Class<CreateKeyspaceCommand> getCommandClass() {
Expand All @@ -27,18 +25,16 @@ public Class<CreateKeyspaceCommand> getCommandClass() {
public Operation resolveDatabaseCommand(
CommandContext<DatabaseSchemaObject> ctx, CreateKeyspaceCommand command) {

var keyspaceName = NamingRules.KEYSPACE.checkRule(command.name());
var keyspaceName = keyspaceIdentifierForCreate(command.name());
var replication =
getOrDefault(command.options(), CreateKeyspaceCommand.Options::replication, null);

String strategy =
(command.options() != null && command.options().replication() != null)
? command.options().replication().strategy()
: null;
String strategy = getOrDefault(replication, CreateKeyspaceCommand.Replication::strategy, null);

Map<String, Integer> strategyOptions =
(command.options() != null && command.options().replication() != null)
? command.options().replication().strategyOptions()
: null;
String replicationMap = getReplicationMap(strategy, strategyOptions);
return new CreateKeyspaceOperation(keyspaceName, replicationMap);
getOrDefault(replication, CreateKeyspaceCommand.Replication::strategyOptions, null);

validateStrategyOptions(strategy, strategyOptions);
return new CreateKeyspaceOperation(keyspaceName, strategy, strategyOptions);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package io.stargate.sgv2.jsonapi.service.resolver;

import static io.stargate.sgv2.jsonapi.util.ApiOptionUtils.getOrDefault;

import io.stargate.sgv2.jsonapi.api.model.command.CommandContext;
import io.stargate.sgv2.jsonapi.api.model.command.impl.CreateNamespaceCommand;
import io.stargate.sgv2.jsonapi.service.operation.Operation;
import io.stargate.sgv2.jsonapi.service.operation.keyspaces.CreateKeyspaceOperation;
import io.stargate.sgv2.jsonapi.service.schema.DatabaseSchemaObject;
import io.stargate.sgv2.jsonapi.service.schema.naming.NamingRules;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.Map;

/**
* Command resolver for {@link CreateNamespaceCommand}. Responsible for creating the replication
* map. Resolve a {@link CreateNamespaceCommand} to a {@link CreateKeyspaceOperation}
*/
/** Command resolver for {@link CreateNamespaceCommand}. */
@ApplicationScoped
public class CreateNamespaceCommandResolver
extends CreateNamespaceKeyspaceCommandResolver<CreateNamespaceCommand> {
extends KeyspaceDDLCommandResolver<CreateNamespaceCommand> {

@Override
public Class<CreateNamespaceCommand> getCommandClass() {
Expand All @@ -27,19 +25,16 @@ public Class<CreateNamespaceCommand> getCommandClass() {
public Operation resolveDatabaseCommand(
CommandContext<DatabaseSchemaObject> ctx, CreateNamespaceCommand command) {

var keyspaceName = NamingRules.KEYSPACE.checkRule(command.name());
var keyspaceName = keyspaceIdentifierForCreate(command.name());
var replication =
getOrDefault(command.options(), CreateNamespaceCommand.Options::replication, null);

String strategy =
(command.options() != null && command.options().replication() != null)
? command.options().replication().strategy()
: null;
String strategy = getOrDefault(replication, CreateNamespaceCommand.Replication::strategy, null);

Map<String, Integer> strategyOptions =
(command.options() != null && command.options().replication() != null)
? command.options().replication().strategyOptions()
: null;
getOrDefault(replication, CreateNamespaceCommand.Replication::strategyOptions, null);

String replicationMap = getReplicationMap(strategy, strategyOptions);
return new CreateKeyspaceOperation(keyspaceName, replicationMap);
validateStrategyOptions(strategy, strategyOptions);
return new CreateKeyspaceOperation(keyspaceName, strategy, strategyOptions);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

/** Command resolver for {@link DropKeyspaceCommand}. */
@ApplicationScoped
public class DropKeyspaceCommandResolver implements CommandResolver<DropKeyspaceCommand> {
public class DropKeyspaceCommandResolver extends KeyspaceDDLCommandResolver<DropKeyspaceCommand> {

/** {@inheritDoc} */
@Override
Expand All @@ -21,6 +21,6 @@ public Class<DropKeyspaceCommand> getCommandClass() {
@Override
public Operation resolveDatabaseCommand(
CommandContext<DatabaseSchemaObject> ctx, DropKeyspaceCommand command) {
return new DropKeyspaceOperation(command.name());
return new DropKeyspaceOperation(keyspaceIdentifierForDrop(command.name()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
import io.stargate.sgv2.jsonapi.service.schema.DatabaseSchemaObject;
import jakarta.enterprise.context.ApplicationScoped;

/**
* Command resolver for {@link DropNamespaceCommand}. Resolve a {@link DropNamespaceCommand} to a
* {@link DropKeyspaceOperation}
*/
/** Command resolver for {@link DropNamespaceCommand}. */
@ApplicationScoped
public class DropNamespaceCommandResolver implements CommandResolver<DropNamespaceCommand> {
public class DropNamespaceCommandResolver extends KeyspaceDDLCommandResolver<DropNamespaceCommand> {

/** {@inheritDoc} */
@Override
Expand All @@ -24,6 +21,6 @@ public Class<DropNamespaceCommand> getCommandClass() {
@Override
public Operation resolveDatabaseCommand(
CommandContext<DatabaseSchemaObject> ctx, DropNamespaceCommand command) {
return new DropKeyspaceOperation(command.name());
return new DropKeyspaceOperation(keyspaceIdentifierForDrop(command.name()));
}
}
Loading