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 <smiklosovic@apache.org>
This commit is contained in:
Marko Tsymbaluk 2025-08-14 22:58:50 -04:00 committed by Stefan Miklosovic
parent 7f92e1ac2c
commit b26e33d506
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
4 changed files with 80 additions and 7 deletions

View File

@ -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:

View File

@ -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

View File

@ -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<String, Property> 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<String, Object> map = new HashMap<>();
for (Map.Entry<String, Object> entry : ((Map<String, Object>) 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<String, Property> getProperties()
{
Loader loader = Properties.defaultLoader();

View File

@ -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<String, String> 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<String, String> 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}");
}
}