From b26e33d50698f080f801983e34c883a82082b26d Mon Sep 17 00:00:00 2001 From: Marko Tsymbaluk Date: Thu, 14 Aug 2025 22:58:50 -0400 Subject: [PATCH] Represent complex settings as JSON on system_views.settings table The patch for 5.0 is preserving old behavior, it is possible to turn on JSON representation by a system property. The patch for trunk is by default transforming collections to JSON string but there is the property (same as in 5.0) which has default to be set to true. patch by Marko Tsymbaluk; reviewed by Paulo Motta, Stefan Miklosovic for CASSANDRA-20827 Co-authored-by: Stefan Miklosovic --- CHANGES.txt | 1 + .../config/CassandraRelevantProperties.java | 2 + .../cassandra/db/virtual/SettingsTable.java | 33 ++++++++++-- .../db/virtual/SettingsTableTest.java | 51 +++++++++++++++++-- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 93cc0ead34..273ccebbae 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.0.6 + * Represent complex settings as JSON on system_views.settings table (CASSANDRA-20827) * Expose StorageService.dropPreparedStatements via JMX (CASSANDRA-20870) * Sort SSTable TOC entries for determinism (CASSANDRA-20494) Merged from 4.1: diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java index e1e080a0a4..f8b983ba74 100644 --- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java +++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java @@ -607,6 +607,8 @@ public enum CassandraRelevantProperties USE_NIX_RECURSIVE_DELETE("cassandra.use_nix_recursive_delete"), /** Gossiper compute expiration timeout. Default value 3 days. */ VERY_LONG_TIME_MS("cassandra.very_long_time_ms", "259200000"), + /** Controls output format for Collection-type settings in system_views.settings table */ + VIRTUAL_TABLE_COMPLEX_SETTINGS_FORMAT_JSON("cassandra.virtual_table_complex_settings_format_json", "false"), WAIT_FOR_TRACING_EVENTS_TIMEOUT_SECS("cassandra.wait_for_tracing_events_timeout_secs", "0"); static diff --git a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java index c4e7d95698..379b6793d8 100644 --- a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java +++ b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java @@ -18,6 +18,7 @@ package org.apache.cassandra.db.virtual; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -25,6 +26,7 @@ import java.util.Objects; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.Redacted; import org.apache.cassandra.config.DatabaseDescriptor; @@ -36,6 +38,7 @@ import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.dht.LocalPartitioner; import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.utils.JsonUtils; import org.apache.cassandra.service.ClientWarn; import org.yaml.snakeyaml.introspector.Property; @@ -48,6 +51,7 @@ final class SettingsTable extends AbstractVirtualTable protected static final Map PROPERTIES = ImmutableMap.copyOf(getProperties()); private final Config config; + private final boolean useJsonFormat; SettingsTable(String keyspace) { @@ -64,6 +68,7 @@ final class SettingsTable extends AbstractVirtualTable .addRegularColumn(VALUE, UTF8Type.instance) .build()); this.config = config; + this.useJsonFormat = CassandraRelevantProperties.VIRTUAL_TABLE_COMPLEX_SETTINGS_FORMAT_JSON.getBoolean(); } @Override @@ -99,9 +104,10 @@ final class SettingsTable extends AbstractVirtualTable return null; if (value.getClass().isArray()) - return Arrays.asList((Object[]) value).toString(); - - if (value instanceof Map) + return tryConstructJson(Arrays.asList((Object[]) value)); + else if (value instanceof Collection) + return tryConstructJson(value); + else if (value instanceof Map) { Map map = new HashMap<>(); for (Map.Entry entry : ((Map) value).entrySet()) @@ -115,12 +121,31 @@ final class SettingsTable extends AbstractVirtualTable map.put(entry.getKey(), entry.getValue()); } - return map.toString(); + return tryConstructJson(map); } return value.toString(); } + private String tryConstructJson(Object o) + { + if (useJsonFormat) + { + try + { + return JsonUtils.JSON_OBJECT_MAPPER.writeValueAsString(o); + } + catch (Exception e) + { + return o.toString(); + } + } + else + { + return o.toString(); + } + } + private static Map getProperties() { Loader loader = Properties.defaultLoader(); diff --git a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java index 1470fac238..82d4133422 100644 --- a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java +++ b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java @@ -18,6 +18,7 @@ package org.apache.cassandra.db.virtual; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -33,6 +34,7 @@ import org.junit.Test; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.Redacted; import org.apache.cassandra.config.DefaultLoader; @@ -41,13 +43,17 @@ import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.Int import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.config.TransparentDataEncryptionOptions; import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.distributed.shared.WithProperties; import org.apache.cassandra.security.SSLFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.introspector.Property; import static java.util.stream.Collectors.toMap; public class SettingsTableTest extends CQLTester { + private static final Logger logger = LoggerFactory.getLogger(SettingsTableTest.class); private static final String KS_NAME = "vts"; private Config config; @@ -172,12 +178,12 @@ public class SettingsTableTest extends CQLTester assertRowsNet(executeNet(q), new Object[] {"credentials_update_interval_in_ms", "-1"}); } - private void check(String setting, String expected) throws Throwable + private void check(String keyspaceTable, String setting, String expected) { - String q = "SELECT * FROM vts.settings WHERE name = '"+setting+'\''; + String q = "SELECT * FROM " + keyspaceTable + " WHERE name = '" + setting + '\''; try { - assertRowsNet(executeNet(q), new Object[] {setting, expected}); + assertRowsNet(executeNet(q), new Object[]{ setting, expected }); } catch (AssertionError e) { @@ -185,6 +191,11 @@ public class SettingsTableTest extends CQLTester } } + private void check(String setting, String expected) + { + check("vts.settings", setting, expected); + } + @Test public void testEncryptionOverride() throws Throwable { @@ -353,4 +364,38 @@ public class SettingsTableTest extends CQLTester Assert.assertEquals(settingName, name); Assert.assertEquals(expectedValue, value); } + + @Test + public void testComplexSettingsFormatProperty() + { + Map parameters = new HashMap<>(); + parameters.put("seeds", "127.0.0.1:7000"); + config.seed_provider = new ParameterizedClass("org.apache.cassandra.locator.SimpleSeedProvider", parameters); + + // Test set property to true (collection as json) + try (WithProperties properties = new WithProperties().set(CassandraRelevantProperties.VIRTUAL_TABLE_COMPLEX_SETTINGS_FORMAT_JSON, "true")) + { + table = new SettingsTable("json_true", config); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace("json_true", ImmutableList.of(table))); + + check("json_true.settings", "data_file_directories", "[\"/my/data/directory\",\"/another/data/directory\"]"); + check("json_true.settings", "seed_provider.parameters", "{\"seeds\":\"127.0.0.1:7000\"}"); + } + } + + @Test + public void testOldBehaviourForComplexSettingsFormatProperty() + { + Map parameters = new HashMap<>(); + parameters.put("seeds", "127.0.0.1:7000"); + config.seed_provider = new ParameterizedClass("org.apache.cassandra.locator.SimpleSeedProvider", parameters); + + // we are not setting property here to false, we expect it to be false by default + + table = new SettingsTable("json_false", config); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace("json_false", ImmutableList.of(table))); + + check("json_false.settings", "data_file_directories", "[/my/data/directory, /another/data/directory]"); + check("json_false.settings", "seed_provider.parameters", "{seeds=127.0.0.1:7000}"); + } }