commandContext =
- new CommandContext<>(new KeyspaceSchemaObject(namespace), null, "", null);
+ new CommandContext<>(new KeyspaceSchemaObject(namespace), null, "", null, apiFeatures);
+
+ // Need context first to check if feature is enabled
+ if (command instanceof TableOnlyCommand && !apiFeatures.isFeatureEnabled(ApiFeature.TABLES)) {
+ return Uni.createFrom()
+ .item(
+ new ThrowableCommandResultSupplier(
+ ErrorCodeV1.TABLE_FEATURE_NOT_ENABLED.toApiException()))
+ .map(commandResult -> commandResult.toRestResponse());
+ }
- // call processor
+ // call processor
return meteredCommandProcessor
.processCommand(dataApiRequestInfo, commandContext, command)
// map to 2xx unless overridden by error
diff --git a/src/main/java/io/stargate/sgv2/jsonapi/config/ApiTablesConfig.java b/src/main/java/io/stargate/sgv2/jsonapi/config/ApiTablesConfig.java
deleted file mode 100644
index d4a423540e..0000000000
--- a/src/main/java/io/stargate/sgv2/jsonapi/config/ApiTablesConfig.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package io.stargate.sgv2.jsonapi.config;
-
-import io.smallrye.config.ConfigMapping;
-import io.smallrye.config.WithDefault;
-
-/** Configuration mapping for API Tables feature. */
-@ConfigMapping(prefix = "stargate.tables")
-public interface ApiTablesConfig {
- /** Setting that determines if the API Tables feature is enabled. */
- @WithDefault("false")
- boolean enabled();
-}
diff --git a/src/main/java/io/stargate/sgv2/jsonapi/config/feature/ApiFeature.java b/src/main/java/io/stargate/sgv2/jsonapi/config/feature/ApiFeature.java
new file mode 100644
index 0000000000..8d98f05091
--- /dev/null
+++ b/src/main/java/io/stargate/sgv2/jsonapi/config/feature/ApiFeature.java
@@ -0,0 +1,62 @@
+package io.stargate.sgv2.jsonapi.config.feature;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1;
+
+/**
+ * Set of "Feature Flags" that can be used to enable/disable certain features in the Data API.
+ * Enumeration defines the key used to introspect state of feature.
+ *
+ * NOTE: although flag names are in upper case (like {@code TABLES}), the actual configuration
+ * uses lower-case names (like {@code tables}) (with proper prefix).
+ *
+ *
Usage: Features may be enabled via configuration: see {@link FeaturesConfig}; if defined at
+ * that level, they are either enabled or disabled for all requests. If not defined (left as empty
+ * or {@code null}), HTTP Request headers can be used to enable/disable features on per-request
+ * basis. Finally, if neither configuration nor request headers are used, feature is disabled.
+ */
+public enum ApiFeature {
+ /**
+ * API Tables feature flag: if enabled, the API will expose table-specific Namespace resource
+ * commands, and support commands on Tables. If disabled, those operations will fail with {@link
+ * ErrorCodeV1#TABLE_FEATURE_NOT_ENABLED}.
+ */
+ TABLES("tables");
+
+ /**
+ * Prefix for HTTP headers used to override feature flags for specific requests: prepended before
+ * {@link #featureName()}, so f.ex for {@link #TABLES} flag, the header name would be {@code
+ * Feature-Flag-tables}.
+ */
+ public static final String HTTP_HEADER_PREFIX = "Feature-Flag-";
+
+ /**
+ * Feature flag name, in lower-case with hyphens (aka "kebab case"), used for as serialization
+ * (JSON and YAML config files) as well as for constructing HTTP header names.
+ */
+ private final String featureName;
+
+ /**
+ * HTTP header name to be used to override the feature flag for a specific request: lower-case,
+ * prefixed with "x-stargate-feature-"; lookup case-insensitive.
+ */
+ private final String featureNameAsHeader;
+
+ ApiFeature(String featureName) {
+ if (!featureName.equals(featureName.toLowerCase())) {
+ throw new IllegalStateException(
+ "Internal error: 'featureName' must be lower-case, was: \"" + featureName + "\"");
+ }
+ this.featureName = featureName;
+ featureNameAsHeader = HTTP_HEADER_PREFIX + featureName;
+ }
+
+ @JsonValue // for Jackson to serialize as lower-case
+ public String featureName() {
+ return featureName;
+ }
+
+ public String httpHeaderName() {
+ return featureNameAsHeader;
+ }
+}
diff --git a/src/main/java/io/stargate/sgv2/jsonapi/config/feature/ApiFeatures.java b/src/main/java/io/stargate/sgv2/jsonapi/config/feature/ApiFeatures.java
new file mode 100644
index 0000000000..f31bafdccf
--- /dev/null
+++ b/src/main/java/io/stargate/sgv2/jsonapi/config/feature/ApiFeatures.java
@@ -0,0 +1,49 @@
+package io.stargate.sgv2.jsonapi.config.feature;
+
+import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Accessor for combined state of feature flags; typically based on static configuration (with its
+ * overrides) and possible per-request settings. For code that wants to check whether given feature
+ * is enabled or not, method to use is {@link ApiFeatures#isFeatureEnabled(ApiFeature)}. For details
+ * on how configuration settings and request headers are combined, see {@link ApiFeature} and {@link
+ * FeaturesConfig}
+ */
+public class ApiFeatures {
+ private final Map fromConfig;
+ private final DataApiRequestInfo.HttpHeaderAccess httpHeaders;
+
+ private ApiFeatures(
+ Map fromConfig, DataApiRequestInfo.HttpHeaderAccess httpHeaders) {
+ this.fromConfig = fromConfig;
+ this.httpHeaders = httpHeaders;
+ }
+
+ public static ApiFeatures empty() {
+ return new ApiFeatures(Collections.emptyMap(), null);
+ }
+
+ public static ApiFeatures fromConfigAndRequest(
+ FeaturesConfig config, DataApiRequestInfo.HttpHeaderAccess httpHeaders) {
+ Map fromConfig = config.flags();
+ if (fromConfig == null) {
+ fromConfig = Collections.emptyMap();
+ }
+ return new ApiFeatures(fromConfig, httpHeaders);
+ }
+
+ public boolean isFeatureEnabled(ApiFeature flag) {
+ // First check if there is definition from configuration
+ Boolean b = fromConfig.get(flag);
+
+ if (b == null) {
+ // and only if not, allow per-request specification
+ if (httpHeaders != null) {
+ b = httpHeaders.getHeaderAsBoolean(flag.httpHeaderName());
+ }
+ }
+ return (b != null) && b.booleanValue();
+ }
+}
diff --git a/src/main/java/io/stargate/sgv2/jsonapi/config/feature/FeaturesConfig.java b/src/main/java/io/stargate/sgv2/jsonapi/config/feature/FeaturesConfig.java
new file mode 100644
index 0000000000..765f1d00c0
--- /dev/null
+++ b/src/main/java/io/stargate/sgv2/jsonapi/config/feature/FeaturesConfig.java
@@ -0,0 +1,18 @@
+package io.stargate.sgv2.jsonapi.config.feature;
+
+import io.smallrye.config.ConfigMapping;
+import java.util.Map;
+
+/**
+ * Configuration mapping for Data API Feature flags as read from main application configuration
+ * (with possible property / sysenv overrides).
+ *
+ * NOTE: actual keys in YAML or similar configuration file would be {@code
+ * stargate.feature.}, where {@code } comes from {@link
+ * ApiFeature#featureName()} (which is always lower-case). So, for example, to enable {@link
+ * ApiFeature#TABLES} feature, one would use: {@code stargate.feature.flags.tables} as the key.
+ */
+@ConfigMapping(prefix = "stargate.feature")
+public interface FeaturesConfig {
+ Map flags();
+}
diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCache.java b/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCache.java
index f09241c1fa..6efe201530 100644
--- a/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCache.java
+++ b/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCache.java
@@ -22,8 +22,6 @@ public class NamespaceCache {
private final ObjectMapper objectMapper;
- private final boolean apiTablesEnabled;
-
// TODO: move the settings to config
// TODO: set the cache loader when creating the cache
private static final long CACHE_TTL_SECONDS = 300;
@@ -34,15 +32,10 @@ public class NamespaceCache {
.maximumSize(CACHE_MAX_SIZE)
.build();
- public NamespaceCache(
- String namespace,
- boolean apiTablesEnabled,
- QueryExecutor queryExecutor,
- ObjectMapper objectMapper) {
+ public NamespaceCache(String namespace, QueryExecutor queryExecutor, ObjectMapper objectMapper) {
this.namespace = namespace;
this.queryExecutor = queryExecutor;
this.objectMapper = objectMapper;
- this.apiTablesEnabled = apiTablesEnabled;
}
protected Uni getSchemaObject(
@@ -109,13 +102,8 @@ private Uni loadSchemaObject(
optionalTable.get(), objectMapper);
}
- if (apiTablesEnabled) {
- return new TableSchemaObject(table);
- }
-
- // Target is not a collection and we are not supporting tables
- throw ErrorCodeV1.INVALID_JSONAPI_COLLECTION_SCHEMA.toApiException(
- "%s", collectionName);
+ // 04-Sep-2024, tatu: Used to check that API Tables enabled; no longer checked here
+ return new TableSchemaObject(table);
});
}
diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/SchemaCache.java b/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/SchemaCache.java
index b4f602833d..864087a9a8 100644
--- a/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/SchemaCache.java
+++ b/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/SchemaCache.java
@@ -9,7 +9,6 @@
import com.github.benmanes.caffeine.cache.Caffeine;
import io.smallrye.mutiny.Uni;
import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo;
-import io.stargate.sgv2.jsonapi.config.ApiTablesConfig;
import io.stargate.sgv2.jsonapi.config.OperationsConfig;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@@ -26,8 +25,6 @@ public class SchemaCache {
@Inject OperationsConfig operationsConfig;
- @Inject ApiTablesConfig apiTablesConfig;
-
// TODO: The size of the cache should be in configuration.
// TODO: set the cache loader when creating the cache
private final Cache schemaCache =
@@ -68,8 +65,7 @@ public void evictCollectionSettingCacheEntry(
}
private NamespaceCache addNamespaceCache(CacheKey cacheKey) {
- return new NamespaceCache(
- cacheKey.namespace(), apiTablesConfig.enabled(), queryExecutor, objectMapper);
+ return new NamespaceCache(cacheKey.namespace(), queryExecutor, objectMapper);
}
/**
diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/tables/CreateTableOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/tables/CreateTableOperation.java
index 2136d6ed9c..3d7af004fc 100644
--- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/tables/CreateTableOperation.java
+++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/tables/CreateTableOperation.java
@@ -22,6 +22,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import org.slf4j.Logger;
@@ -47,7 +48,7 @@ public CreateTableOperation(
String comment) {
this.commandContext = commandContext;
this.tableName = tableName;
- this.columnTypes = columnTypes;
+ this.columnTypes = Objects.requireNonNull(columnTypes, "columnTypes must not be null");
this.partitionKeys = partitionKeys;
this.clusteringKeys = clusteringKeys;
this.comment = comment;
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 11f37783b7..c67e7196cc 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -25,6 +25,10 @@ stargate:
exception-mappers:
enabled: false
+ feature: # See io.stargate.sgv2.jsonapi.config.feature.FeaturesConfig
+ flags: # Ok to leave out features that have no default value (enabled or disabled)
+ tables:
+
# custom grpc settings
grpc:
@@ -48,10 +52,6 @@ stargate:
multi-tenancy:
enabled: false
- # Is support for API Tables enabled?
- tables:
- enabled: false
-
quarkus:
# general app properties
application:
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/TestConstants.java b/src/test/java/io/stargate/sgv2/jsonapi/TestConstants.java
index 31a5c4f62e..e50b656de2 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/TestConstants.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/TestConstants.java
@@ -1,6 +1,7 @@
package io.stargate.sgv2.jsonapi;
import io.stargate.sgv2.jsonapi.api.model.command.CommandContext;
+import io.stargate.sgv2.jsonapi.config.feature.ApiFeatures;
import io.stargate.sgv2.jsonapi.service.cqldriver.executor.*;
import org.apache.commons.lang3.RandomStringUtils;
@@ -40,15 +41,21 @@ public final class TestConstants {
// CommandContext for working on the schema objects above
+ public static final ApiFeatures DEFAULT_API_FEATURES_FOR_TESTS = ApiFeatures.empty();
+
public static final CommandContext COLLECTION_CONTEXT =
- new CommandContext<>(COLLECTION_SCHEMA_OBJECT, null, TEST_COMMAND_NAME, null);
+ new CommandContext<>(
+ COLLECTION_SCHEMA_OBJECT, null, TEST_COMMAND_NAME, null, DEFAULT_API_FEATURES_FOR_TESTS);
public static final CommandContext VECTOR_COLLECTION_CONTEXT =
- new CommandContext<>(VECTOR_COLLECTION_SCHEMA_OBJECT, null, null, null);
+ new CommandContext<>(
+ VECTOR_COLLECTION_SCHEMA_OBJECT, null, null, null, DEFAULT_API_FEATURES_FOR_TESTS);
public static final CommandContext KEYSPACE_CONTEXT =
- new CommandContext<>(KEYSPACE_SCHEMA_OBJECT, null, TEST_COMMAND_NAME, null);
+ new CommandContext<>(
+ KEYSPACE_SCHEMA_OBJECT, null, TEST_COMMAND_NAME, null, DEFAULT_API_FEATURES_FOR_TESTS);
public static final CommandContext DATABASE_CONTEXT =
- new CommandContext<>(DATABASE_SCHEMA_OBJECT, null, TEST_COMMAND_NAME, null);
+ new CommandContext<>(
+ DATABASE_SCHEMA_OBJECT, null, TEST_COMMAND_NAME, null, DEFAULT_API_FEATURES_FOR_TESTS);
}
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/AbstractTableIntegrationTestBase.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/AbstractTableIntegrationTestBase.java
index 39b6c01b30..f898f41092 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/AbstractTableIntegrationTestBase.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/AbstractTableIntegrationTestBase.java
@@ -10,6 +10,7 @@
import io.stargate.sgv2.jsonapi.api.v1.AbstractNamespaceIntegrationTestBase;
import io.stargate.sgv2.jsonapi.api.v1.CollectionResource;
import io.stargate.sgv2.jsonapi.api.v1.util.DataApiCommandSenders;
+import io.stargate.sgv2.jsonapi.api.v1.util.DataApiResponseValidator;
import java.io.IOException;
import java.util.Map;
@@ -17,9 +18,9 @@
public class AbstractTableIntegrationTestBase extends AbstractNamespaceIntegrationTestBase {
private static final ObjectMapper MAPPER = new ObjectMapper();
- protected void createTableWithColumns(
+ protected DataApiResponseValidator createTableWithColumns(
String tableName, Map columns, Object primaryKeyDef) {
- createTable(
+ return createTable(
"""
{
"name": "%s",
@@ -32,8 +33,8 @@ protected void createTableWithColumns(
.formatted(tableName, asJSON(columns), asJSON(primaryKeyDef)));
}
- protected void createTable(String tableDefAsJSON) {
- DataApiCommandSenders.assertNamespaceCommand(namespaceName)
+ protected DataApiResponseValidator createTable(String tableDefAsJSON) {
+ return DataApiCommandSenders.assertNamespaceCommand(namespaceName)
.postCreateTable(tableDefAsJSON)
.hasNoErrors()
.body("status.ok", is(1));
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/CreateTableIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/CreateTableIntegrationTest.java
index 9b882e815e..c978f7847d 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/CreateTableIntegrationTest.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/CreateTableIntegrationTest.java
@@ -9,17 +9,16 @@
import io.stargate.sgv2.jsonapi.api.v1.AbstractNamespaceIntegrationTestBase;
import io.stargate.sgv2.jsonapi.api.v1.NamespaceResource;
import io.stargate.sgv2.jsonapi.testresource.DseTestResource;
-import org.junit.Test;
import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
@QuarkusIntegrationTest
@WithTestResource(value = DseTestResource.class, restrictToAnnotatedClass = false)
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class CreateTableIntegrationTest extends AbstractNamespaceIntegrationTestBase {
-
@Nested
@Order(1)
class CreateTable {
@@ -111,7 +110,7 @@ private void deleteTable(String tableName) {
.body(
"""
{
- "deleteTable": {
+ "deleteCollection": {
"name": "%s"
}
}
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/TableFeatureDisabledIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/TableFeatureDisabledIntegrationTest.java
new file mode 100644
index 0000000000..4337c832a3
--- /dev/null
+++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/TableFeatureDisabledIntegrationTest.java
@@ -0,0 +1,95 @@
+package io.stargate.sgv2.jsonapi.api.v1.tables;
+
+import static org.hamcrest.Matchers.is;
+
+import io.quarkus.test.common.WithTestResource;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.stargate.sgv2.jsonapi.api.v1.util.DataApiCommandSenders;
+import io.stargate.sgv2.jsonapi.config.feature.ApiFeature;
+import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1;
+import io.stargate.sgv2.jsonapi.testresource.DseTestResource;
+import org.junit.jupiter.api.ClassOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestClassOrder;
+
+/**
+ * Basic testing to see what happens when "Tables" feature is disabled (other tests will have
+ * feature enabled)
+ */
+@QuarkusIntegrationTest
+@WithTestResource(
+ value = TableFeatureDisabledIntegrationTest.TestResource.class,
+ restrictToAnnotatedClass = true)
+@TestClassOrder(ClassOrderer.OrderAnnotation.class)
+public class TableFeatureDisabledIntegrationTest extends AbstractTableIntegrationTestBase {
+ // Need to be able to enable/disable the TABLES feature
+ public static class TestResource extends DseTestResource {
+ public TestResource() {}
+
+ @Override
+ public String getFeatureFlagTables() {
+ // return empty to leave feature "undefined" (disabled unless per-request header override)
+ // ("false" would be "disabled" for all tests, regardless of headers)
+ return "";
+ }
+ }
+
+ private static final String TABLE_TO_CREATE = "table_with_table_feature";
+
+ // By default, table creation should fail
+ @Order(1)
+ @Test
+ public void failCreateWithoutFeatureEnabled() {
+ DataApiCommandSenders.assertNamespaceCommand(namespaceName)
+ .postCreateTable(simpleTableDef(TABLE_TO_CREATE))
+ .hasSingleApiError(ErrorCodeV1.TABLE_FEATURE_NOT_ENABLED);
+ }
+
+ // But with header override, should succeed
+ @Order(2)
+ @Test
+ public void okCreateWithFeatureEnabledViaHeader() {
+ DataApiCommandSenders.assertNamespaceCommand(namespaceName)
+ .header(ApiFeature.TABLES.httpHeaderName(), "true")
+ .postCreateTable(simpleTableDef(TABLE_TO_CREATE))
+ .hasNoErrors()
+ .body("status.ok", is(1));
+ }
+
+ // But even with table, find() should fail without Feature enabled
+ @Order(3)
+ @Test
+ public void failFindWithoutFeature() {
+ DataApiCommandSenders.assertTableCommand(namespaceName, TABLE_TO_CREATE)
+ .postFindOne("{}")
+ .hasSingleApiError(ErrorCodeV1.TABLE_FEATURE_NOT_ENABLED);
+ }
+
+ // And finally, with header override, should succeed in findOne()
+ @Order(4)
+ @Test
+ public void okFindWithFeatureEnabledViaHeader() {
+ DataApiCommandSenders.assertTableCommand(namespaceName, TABLE_TO_CREATE)
+ .header(ApiFeature.TABLES.httpHeaderName(), "true")
+ .postFindOne("{}")
+ .hasNoErrors();
+ }
+
+ private static String simpleTableDef(String tableName) {
+ return
+ """
+ {
+ "name": "%s",
+ "definition": {
+ "columns": {
+ "id": { "type": "text" },
+ "name": { "type": "text" }
+ },
+ "primaryKey": "id"
+ }
+ }
+ """
+ .formatted(tableName);
+ }
+}
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/util/DataApiCommandSenderBase.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/util/DataApiCommandSenderBase.java
index 5450a93d5a..5bbab6a6b0 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/util/DataApiCommandSenderBase.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/util/DataApiCommandSenderBase.java
@@ -12,6 +12,7 @@
import io.stargate.sgv2.jsonapi.service.embedding.operation.test.CustomITEmbeddingProvider;
import jakarta.ws.rs.core.Response;
import java.util.Base64;
+import java.util.LinkedHashMap;
import java.util.Map;
import org.eclipse.microprofile.config.ConfigProvider;
@@ -24,8 +25,11 @@ public abstract class DataApiCommandSenderBase headers;
+
protected DataApiCommandSenderBase(String namespace) {
this.namespace = namespace;
+ headers = DefaultHeaders.mutableCopy();
}
/**
@@ -39,6 +43,22 @@ public T expectHttpStatus(Response.Status expectedHttpStatus) {
return _this();
}
+ /**
+ * Fluent method for adding/overriding/removing a header in the request.
+ *
+ * @param name Name of header to set
+ * @param value Value of header to set; if null, header is removed, otherwise added or overridden
+ * @return Type-safe "this" sender for call chaining
+ */
+ public T header(String name, String value) {
+ if (value == null) {
+ headers.remove(name);
+ } else {
+ headers.put(name, value);
+ }
+ return _this();
+ }
+
protected T _this() {
return (T) this;
}
@@ -50,11 +70,11 @@ protected T _this() {
* @param jsonBody JSON body to POST
* @return Response validator for further assertions
*/
- public final DataApiResponseValidator postRaw(String jsonBody) {
+ public final DataApiResponseValidator postJSON(String jsonBody) {
RequestSpecification request =
given()
.port(getTestPort())
- .headers(getHeaders())
+ .headers(headers)
.contentType(ContentType.JSON)
.body(jsonBody)
.when();
@@ -64,41 +84,46 @@ public final DataApiResponseValidator postRaw(String jsonBody) {
}
public DataApiResponseValidator postCommand(String command, String commandClause) {
- return postRaw("{ \"%s\": %s }".formatted(command, commandClause));
+ return postJSON("{ \"%s\": %s }".formatted(command, commandClause));
}
protected abstract io.restassured.response.Response postInternal(RequestSpecification request);
- protected Map getHeaders() {
- if (useCoordinator()) {
- return Map.of(
- HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME,
- getAuthToken(),
- HttpConstants.EMBEDDING_AUTHENTICATION_TOKEN_HEADER_NAME,
- CustomITEmbeddingProvider.TEST_API_KEY);
- } else {
- String credential =
- "Cassandra:"
- + Base64.getEncoder().encodeToString(getCassandraUsername().getBytes())
- + ":"
- + Base64.getEncoder().encodeToString(getCassandraPassword().getBytes());
- return Map.of(
- HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME,
- credential,
- HttpConstants.EMBEDDING_AUTHENTICATION_TOKEN_HEADER_NAME,
- CustomITEmbeddingProvider.TEST_API_KEY);
- }
- }
-
- private boolean useCoordinator() {
- return Boolean.getBoolean("testing.containers.use-coordinator");
- }
-
- protected int getTestPort() {
+ protected static int getTestPort() {
try {
return ConfigProvider.getConfig().getValue("quarkus.http.test-port", Integer.class);
} catch (Exception e) {
return Integer.parseInt(System.getProperty("quarkus.http.test-port"));
}
}
+
+ private static class DefaultHeaders {
+ private static final Map HEADERS = collectDefaultHeaders();
+
+ public static Map mutableCopy() {
+ return new LinkedHashMap<>(HEADERS);
+ }
+
+ private static Map collectDefaultHeaders() {
+ final boolean useCoordinator = Boolean.getBoolean("testing.containers.use-coordinator");
+ if (useCoordinator) {
+ return Map.of(
+ HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME,
+ getAuthToken(),
+ HttpConstants.EMBEDDING_AUTHENTICATION_TOKEN_HEADER_NAME,
+ CustomITEmbeddingProvider.TEST_API_KEY);
+ } else {
+ String credential =
+ "Cassandra:"
+ + Base64.getEncoder().encodeToString(getCassandraUsername().getBytes())
+ + ":"
+ + Base64.getEncoder().encodeToString(getCassandraPassword().getBytes());
+ return Map.of(
+ HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME,
+ credential,
+ HttpConstants.EMBEDDING_AUTHENTICATION_TOKEN_HEADER_NAME,
+ CustomITEmbeddingProvider.TEST_API_KEY);
+ }
+ }
+ }
}
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCacheTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCacheTest.java
index f3c488fe07..d7891cb379 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCacheTest.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCacheTest.java
@@ -18,8 +18,6 @@
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.helpers.test.UniAssertSubscriber;
import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo;
-import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1;
-import io.stargate.sgv2.jsonapi.exception.JsonApiException;
import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile;
import jakarta.inject.Inject;
import java.util.HashMap;
@@ -292,7 +290,7 @@ public void checkValidJsonApiTableWithIndexing() {
.awaitItem()
.getItem();
- assertThat(schemaObject instanceof CollectionSchemaObject);
+ assertThat(schemaObject).isInstanceOf(CollectionSchemaObject.class);
var collectionSchemaObject = (CollectionSchemaObject) schemaObject;
assertThat(collectionSchemaObject)
.satisfies(
@@ -304,7 +302,7 @@ public void checkValidJsonApiTableWithIndexing() {
}
@Test
- public void checkInvalidJsonApiTable() {
+ public void checkNonCollectionJsonApiTable() {
QueryExecutor queryExecutor = mock(QueryExecutor.class);
when(queryExecutor.getSchema(any(), any(), any()))
.then(
@@ -350,28 +348,19 @@ public void checkInvalidJsonApiTable() {
new HashMap<>())));
});
NamespaceCache namespaceCache = createNamespaceCache(queryExecutor);
- Throwable error =
+ var schemaObject =
namespaceCache
.getSchemaObject(dataApiRequestInfo, "table")
.subscribe()
.withSubscriber(UniAssertSubscriber.create())
- .awaitFailure()
- .getFailure();
+ .awaitItem()
+ .getItem();
- assertThat(error)
- .isInstanceOfSatisfying(
- JsonApiException.class,
- s -> {
- assertThat(s.getErrorCode())
- .isEqualTo(ErrorCodeV1.INVALID_JSONAPI_COLLECTION_SCHEMA);
- assertThat(s.getMessage())
- .isEqualTo(
- ErrorCodeV1.INVALID_JSONAPI_COLLECTION_SCHEMA.getMessage() + ": table");
- });
+ assertThat(schemaObject).isInstanceOf(TableSchemaObject.class);
}
}
private NamespaceCache createNamespaceCache(QueryExecutor qe) {
- return new NamespaceCache("ks", false, qe, objectMapper);
+ return new NamespaceCache("ks", qe, objectMapper);
}
}
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/embedding/operation/TestEmbeddingProvider.java b/src/test/java/io/stargate/sgv2/jsonapi/service/embedding/operation/TestEmbeddingProvider.java
index bfa8077236..eb7b1f78a7 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/service/embedding/operation/TestEmbeddingProvider.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/service/embedding/operation/TestEmbeddingProvider.java
@@ -24,7 +24,8 @@ public class TestEmbeddingProvider extends EmbeddingProvider {
null),
new TestEmbeddingProvider(),
"testCommand",
- null);
+ null,
+ TestConstants.DEFAULT_API_FEATURES_FOR_TESTS);
@Override
public Uni vectorize(
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/CreateCollectionOperationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/CreateCollectionOperationTest.java
index 1934e33b2c..c3d58a3b9b 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/CreateCollectionOperationTest.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/CreateCollectionOperationTest.java
@@ -53,7 +53,12 @@
public class CreateCollectionOperationTest extends OperationTestBase {
private CommandContext COMMAND_CONTEXT =
- new CommandContext<>(COLLECTION_SCHEMA_OBJECT, null, "CreateCollectionCommand", null);
+ new CommandContext<>(
+ COLLECTION_SCHEMA_OBJECT,
+ null,
+ "CreateCollectionCommand",
+ null,
+ DEFAULT_API_FEATURES_FOR_TESTS);
@Inject DatabaseLimitsConfig databaseLimitsConfig;
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/FindCollectionOperationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/FindCollectionOperationTest.java
index f25dc792ae..9d78f32f69 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/FindCollectionOperationTest.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/FindCollectionOperationTest.java
@@ -85,7 +85,8 @@ public void init() {
null),
null,
"testCommand",
- jsonProcessingMetricsReporter);
+ jsonProcessingMetricsReporter,
+ DEFAULT_API_FEATURES_FOR_TESTS);
}
@Nested
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/InsertCollectionOperationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/InsertCollectionOperationTest.java
index 2f4e569266..04b6c93f51 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/InsertCollectionOperationTest.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/InsertCollectionOperationTest.java
@@ -86,7 +86,8 @@ public void init() {
null),
null,
"testCommand",
- jsonProcessingMetricsReporter);
+ jsonProcessingMetricsReporter,
+ DEFAULT_API_FEATURES_FOR_TESTS);
}
@Nested
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/OperationTestBase.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/OperationTestBase.java
index 3564a1cef0..30736c8d74 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/OperationTestBase.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/OperationTestBase.java
@@ -19,6 +19,7 @@
import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo;
import io.stargate.sgv2.jsonapi.api.v1.metrics.JsonProcessingMetricsReporter;
import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants;
+import io.stargate.sgv2.jsonapi.config.feature.ApiFeatures;
import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject;
import io.stargate.sgv2.jsonapi.service.cqldriver.executor.KeyspaceSchemaObject;
import io.stargate.sgv2.jsonapi.service.cqldriver.executor.SchemaObjectName;
@@ -37,6 +38,7 @@ public class OperationTestBase {
protected final String COLLECTION_NAME = RandomStringUtils.randomAlphanumeric(16);
protected final SchemaObjectName SCHEMA_OBJECT_NAME =
new SchemaObjectName(KEYSPACE_NAME, COLLECTION_NAME);
+ protected final ApiFeatures DEFAULT_API_FEATURES_FOR_TESTS = ApiFeatures.empty();
protected final CollectionSchemaObject COLLECTION_SCHEMA_OBJECT =
new CollectionSchemaObject(
@@ -51,10 +53,18 @@ public class OperationTestBase {
protected final CommandContext COLLECTION_CONTEXT =
new CommandContext<>(
- COLLECTION_SCHEMA_OBJECT, null, COMMAND_NAME, jsonProcessingMetricsReporter);
+ COLLECTION_SCHEMA_OBJECT,
+ null,
+ COMMAND_NAME,
+ jsonProcessingMetricsReporter,
+ DEFAULT_API_FEATURES_FOR_TESTS);
protected final CommandContext KEYSPACE_CONTEXT =
new CommandContext<>(
- KEYSPACE_SCHEMA_OBJECT, null, COMMAND_NAME, jsonProcessingMetricsReporter);
+ KEYSPACE_SCHEMA_OBJECT,
+ null,
+ COMMAND_NAME,
+ jsonProcessingMetricsReporter,
+ DEFAULT_API_FEATURES_FOR_TESTS);
@InjectMock protected DataApiRequestInfo dataApiRequestInfo;
@@ -64,7 +74,11 @@ public class OperationTestBase {
protected CommandContext createCommandContextWithCommandName(
String commandName) {
return new CommandContext<>(
- COLLECTION_SCHEMA_OBJECT, null, commandName, jsonProcessingMetricsReporter);
+ COLLECTION_SCHEMA_OBJECT,
+ null,
+ commandName,
+ jsonProcessingMetricsReporter,
+ DEFAULT_API_FEATURES_FOR_TESTS);
}
protected ColumnDefinitions buildColumnDefs(TestColumn... columns) {
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/ReadAndUpdateCollectionOperationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/ReadAndUpdateCollectionOperationTest.java
index 552001f2bf..b10a676b48 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/ReadAndUpdateCollectionOperationTest.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/collections/ReadAndUpdateCollectionOperationTest.java
@@ -120,7 +120,8 @@ public void init() {
null),
null,
"testCommand",
- jsonProcessingMetricsReporter);
+ jsonProcessingMetricsReporter,
+ DEFAULT_API_FEATURES_FOR_TESTS);
}
private MockRow resultRow(ColumnDefinitions columnDefs, int index, Object... values) {
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/CommandResolverWithVectorizerTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/CommandResolverWithVectorizerTest.java
index e57a40ee88..6c10d60f96 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/CommandResolverWithVectorizerTest.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/CommandResolverWithVectorizerTest.java
@@ -19,6 +19,7 @@
import io.stargate.sgv2.jsonapi.api.model.command.impl.UpdateOneCommand;
import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo;
import io.stargate.sgv2.jsonapi.config.OperationsConfig;
+import io.stargate.sgv2.jsonapi.config.feature.ApiFeatures;
import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1;
import io.stargate.sgv2.jsonapi.exception.JsonApiException;
import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject;
@@ -71,7 +72,7 @@ public class CommandResolverWithVectorizerTest {
@Nested
class Resolve {
- // TODO: do these need to be uniqe to this test ? Can we use TestConstants ?
+ // TODO: do these need to be unique to this test ? Can we use TestConstants ?
protected final String KEYSPACE_NAME = RandomStringUtils.randomAlphanumeric(16);
protected final String COLLECTION_NAME = RandomStringUtils.randomAlphanumeric(16);
private final CommandContext VECTOR_COMMAND_CONTEXT =
@@ -83,7 +84,8 @@ class Resolve {
null),
null,
null,
- null);
+ null,
+ ApiFeatures.empty());
@Test
public void find() throws Exception {
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/testresource/DseTestResource.java b/src/test/java/io/stargate/sgv2/jsonapi/testresource/DseTestResource.java
index 9799f2f24b..be691958ad 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/testresource/DseTestResource.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/testresource/DseTestResource.java
@@ -65,13 +65,24 @@ public Long getMaxDocumentSortCount() {
return 100L;
}
+ // By default, we enable the feature flag for tables
+ public String getFeatureFlagTables() {
+ return "true";
+ }
+
@Override
public Map start() {
Map env = super.start();
ImmutableMap.Builder propsBuilder = ImmutableMap.builder();
propsBuilder.putAll(env);
propsBuilder.put("stargate.jsonapi.custom.embedding.enabled", "true");
- propsBuilder.put("stargate.tables.enabled", "true");
+
+ // 04-Sep-2024, tatu: [data-api#1335] Enable Tables using new Feature Flag:
+ String tableFeatureSetting = getFeatureFlagTables();
+ if (tableFeatureSetting != null) {
+ propsBuilder.put("stargate.feature.flags.tables", tableFeatureSetting);
+ }
+
propsBuilder.put(
"stargate.jsonapi.custom.embedding.clazz",
"io.stargate.sgv2.jsonapi.service.embedding.operation.test.CustomITEmbeddingProvider");