yifan-c commented on code in PR #4458:
URL: https://github.com/apache/cassandra/pull/4458#discussion_r2487296008
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionary.java:
##########
@@ -49,6 +49,20 @@ public interface CompressionDictionary extends AutoCloseable
*/
byte[] rawDictionary();
+ /**
+ * Get checkum of this dictionary.
Review Comment:
typo:
```suggestion
* Get checksum of this dictionary.
```
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionary.java:
##########
@@ -281,4 +315,60 @@ public String toString()
'}';
}
}
+
+ /**
+ * The purpose of lightweight dictionary is to not carry the actual
dictionary bytes for performance reasons.
+ * Handy for situations when retrieval from the database does not need to
contain dictionary
+ * or the instatiation of a proper dictionary object is not desirable or
unnecessary for other,
+ * mostly performance-related, reasons.
+ */
+ class LightweightCompressionDictionary implements CompressionDictionary
+ {
+ private final DictId dictId;
+ private final int checksum;
+ private final int size;
+
+ public LightweightCompressionDictionary(DictId dictId, int checksum,
int size)
+ {
+ this.dictId = dictId;
+ this.checksum = checksum;
+ this.size = size;
+ }
+
+ @Override
+ public DictId dictId()
+ {
+ return dictId;
+ }
+
+ @Override
+ public byte[] rawDictionary()
+ {
+ return null;
Review Comment:
In my opinion, returning null loosen the API contract by permitting `null`
return value. Caller has to consider the null case. How about throwing
`UnsupportedOperationException` instead? So the API remains return non-null
values only.
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryDetailsTabularData.java:
##########
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.compression;
+
+import java.util.Arrays;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import static java.lang.String.format;
+
+public class CompressionDictionaryDetailsTabularData
+{
+ public static final String KEYSPACE_NAME = "Keyspace";
+ public static final String TABLE_NAME = "Table";
+ public static final String DICT_ID_NAME = "DictId";
+ public static final String DICT_NAME = "Dict";
+ public static final String KIND_NAME = "Kind";
+ public static final String CHECKSUM_NAME = "Checksum";
+ public static final String SIZE_NAME = "Size";
+
+
+ private static final String[] ITEM_NAMES = new String[]{ KEYSPACE_NAME,
+ TABLE_NAME,
+ DICT_ID_NAME,
+ DICT_NAME,
+ KIND_NAME,
+ CHECKSUM_NAME,
+ SIZE_NAME };
+
+ private static final String[] ITEM_DESCS = new String[]{ "keyspace",
+ "table",
+ "dictionary_id",
+
"dictionary_bytes",
+ "kind",
+ "checksum",
+ "size" };
+
+ private static final String TYPE_NAME = "DictionaryDetails";
+ private static final String ROW_DESC = "DictionaryDetails";
+ private static final OpenType<?>[] ITEM_TYPES;
+ private static final CompositeType COMPOSITE_TYPE;
+ public static final TabularType TABULAR_TYPE;
+
+ static
+ {
+ try
+ {
+ ITEM_TYPES = new OpenType[]{ SimpleType.STRING, // keyspace
+ SimpleType.STRING, // table
+ SimpleType.LONG, // dict id
+ new
ArrayType<String[]>(SimpleType.BYTE, true), // dict bytes
+ SimpleType.STRING, // kind
+ SimpleType.INTEGER, // checksum
+ SimpleType.INTEGER }; // size of dict
bytes
+
+ COMPOSITE_TYPE = new CompositeType(TYPE_NAME, ROW_DESC,
ITEM_NAMES, ITEM_DESCS, ITEM_TYPES);
+ TABULAR_TYPE = new TabularType(TYPE_NAME, ROW_DESC,
COMPOSITE_TYPE, ITEM_NAMES);
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void from(String keyspace,
+ String table,
+ CompressionDictionary dictionary,
+ TabularDataSupport result)
+ {
+ result.put(from(keyspace, table, dictionary));
+ }
+
+ public static CompositeData from(String keyspace, String table,
CompressionDictionary dictionary)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ keyspace,
+ table,
+ dictionary.dictId().id,
+ dictionary.rawDictionary(),
+ dictionary.kind().name(),
+ dictionary.checksum(),
+ dictionary.size(),
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CompositeData from(CompressionDictionaryPojo pojo)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ pojo.keyspace,
+ pojo.table,
+ pojo.dictId,
+ pojo.dict,
+ pojo.kind,
+ pojo.dictChecksum,
+ pojo.dictLength
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Deserializes data to convenience object to work further with.
+ *
+ * @param compositeData data to create pojo from
+ * @return deserialized composite data to convenience object
+ * @throws IllegalArgumentException if values in deserialized object are
invalid.
+ * @see CompressionDictionaryPojo#validate()
+ */
+ public static CompressionDictionaryPojo from(CompositeData compositeData)
+ {
+ String keyspace = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KEYSPACE_NAME);
+ String table = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.TABLE_NAME);
+ long dictId = (Long)
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_ID_NAME);
+ byte[] dictionaryBytes = (byte[])
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_NAME);
+ String kind = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KIND_NAME);
+ int checksum = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.CHECKSUM_NAME);
+ int size = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.SIZE_NAME);
+
+ CompressionDictionaryPojo pojo = new CompressionDictionaryPojo();
+ pojo.keyspace = keyspace;
+ pojo.table = table;
+ pojo.dictId = dictId;
+ pojo.dict = dictionaryBytes;
+ pojo.kind = kind;
+ pojo.dictChecksum = checksum;
+ pojo.dictLength = size;
+
+ pojo.validate();
+
+ return pojo;
+ }
+
+ public static class CompressionDictionaryPojo
+ {
+ public String keyspace;
+ public String table;
+ public long dictId;
+ public byte[] dict;
+ public String kind;
+ public int dictChecksum;
+ public int dictLength;
+
+ /**
+ * Dictionary is valid if, keyspace and table are specified,
dictionary id is strictly positive integer,
+ * dictionary byte array is not nor not empty,
Review Comment:
not sure what does "dictionary byte array is not nor not empty" mean.
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryManager.java:
##########
@@ -244,6 +251,68 @@ public CompositeData getTrainingState()
return dictionaryTrainer.getTrainingState().toCompositeData();
}
+ @Override
+ public TabularData listDictionaries(String keyspace, String table)
+ {
+ List<LightweightCompressionDictionary> dictionaries =
SystemDistributedKeyspace.retrieveLightweightCompressionDictionaries(keyspace,
table);
+ TabularDataSupport tableData = new
TabularDataSupport(CompressionDictionaryDetailsTabularData.TABULAR_TYPE);
+
+ if (dictionaries == null)
+ {
+ return tableData;
+ }
+
+ for (LightweightCompressionDictionary dictionary : dictionaries)
+ {
+ CompressionDictionaryDetailsTabularData.from(keyspace, table,
dictionary, tableData);
+ }
+
+ return tableData;
+ }
+
+ @Override
+ public CompositeData getDictionary(String keyspace, String table)
+ {
+ CompressionDictionary compressionDictionary =
SystemDistributedKeyspace.retrieveLatestCompressionDictionary(keyspaceName,
tableName);
+ if (compressionDictionary == null)
+ return null;
+
+ return CompressionDictionaryDetailsTabularData.from(keyspace, table,
compressionDictionary);
+ }
+
+ @Override
+ public CompositeData getDictionary(String keyspace, String table, long
dictId)
+ {
+ CompressionDictionary compressionDictionary =
SystemDistributedKeyspace.retrieveCompressionDictionary(keyspaceName,
tableName, dictId);
+ if (compressionDictionary == null)
+ return null;
+
+ return CompressionDictionaryDetailsTabularData.from(keyspace, table,
compressionDictionary);
+ }
+
+ @Override
+ public void importDictionary(CompositeData compositeData)
Review Comment:
Should this method be `synchornized`? We would like to avoid importing
concurrently.
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryDetailsTabularData.java:
##########
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.compression;
+
+import java.util.Arrays;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import static java.lang.String.format;
+
+public class CompressionDictionaryDetailsTabularData
+{
+ public static final String KEYSPACE_NAME = "Keyspace";
+ public static final String TABLE_NAME = "Table";
+ public static final String DICT_ID_NAME = "DictId";
+ public static final String DICT_NAME = "Dict";
+ public static final String KIND_NAME = "Kind";
+ public static final String CHECKSUM_NAME = "Checksum";
+ public static final String SIZE_NAME = "Size";
+
+
+ private static final String[] ITEM_NAMES = new String[]{ KEYSPACE_NAME,
+ TABLE_NAME,
+ DICT_ID_NAME,
+ DICT_NAME,
+ KIND_NAME,
+ CHECKSUM_NAME,
+ SIZE_NAME };
+
+ private static final String[] ITEM_DESCS = new String[]{ "keyspace",
+ "table",
+ "dictionary_id",
+
"dictionary_bytes",
+ "kind",
+ "checksum",
+ "size" };
+
+ private static final String TYPE_NAME = "DictionaryDetails";
+ private static final String ROW_DESC = "DictionaryDetails";
+ private static final OpenType<?>[] ITEM_TYPES;
+ private static final CompositeType COMPOSITE_TYPE;
+ public static final TabularType TABULAR_TYPE;
+
+ static
+ {
+ try
+ {
+ ITEM_TYPES = new OpenType[]{ SimpleType.STRING, // keyspace
+ SimpleType.STRING, // table
+ SimpleType.LONG, // dict id
+ new
ArrayType<String[]>(SimpleType.BYTE, true), // dict bytes
+ SimpleType.STRING, // kind
+ SimpleType.INTEGER, // checksum
+ SimpleType.INTEGER }; // size of dict
bytes
+
+ COMPOSITE_TYPE = new CompositeType(TYPE_NAME, ROW_DESC,
ITEM_NAMES, ITEM_DESCS, ITEM_TYPES);
+ TABULAR_TYPE = new TabularType(TYPE_NAME, ROW_DESC,
COMPOSITE_TYPE, ITEM_NAMES);
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void from(String keyspace,
+ String table,
+ CompressionDictionary dictionary,
+ TabularDataSupport result)
+ {
+ result.put(from(keyspace, table, dictionary));
+ }
+
+ public static CompositeData from(String keyspace, String table,
CompressionDictionary dictionary)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ keyspace,
+ table,
+ dictionary.dictId().id,
+ dictionary.rawDictionary(),
+ dictionary.kind().name(),
+ dictionary.checksum(),
+ dictionary.size(),
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CompositeData from(CompressionDictionaryPojo pojo)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ pojo.keyspace,
+ pojo.table,
+ pojo.dictId,
+ pojo.dict,
+ pojo.kind,
+ pojo.dictChecksum,
+ pojo.dictLength
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Deserializes data to convenience object to work further with.
+ *
+ * @param compositeData data to create pojo from
+ * @return deserialized composite data to convenience object
+ * @throws IllegalArgumentException if values in deserialized object are
invalid.
+ * @see CompressionDictionaryPojo#validate()
+ */
+ public static CompressionDictionaryPojo from(CompositeData compositeData)
+ {
+ String keyspace = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KEYSPACE_NAME);
+ String table = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.TABLE_NAME);
+ long dictId = (Long)
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_ID_NAME);
+ byte[] dictionaryBytes = (byte[])
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_NAME);
+ String kind = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KIND_NAME);
+ int checksum = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.CHECKSUM_NAME);
+ int size = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.SIZE_NAME);
+
+ CompressionDictionaryPojo pojo = new CompressionDictionaryPojo();
+ pojo.keyspace = keyspace;
+ pojo.table = table;
+ pojo.dictId = dictId;
+ pojo.dict = dictionaryBytes;
+ pojo.kind = kind;
+ pojo.dictChecksum = checksum;
+ pojo.dictLength = size;
+
+ pojo.validate();
+
+ return pojo;
+ }
+
+ public static class CompressionDictionaryPojo
+ {
+ public String keyspace;
+ public String table;
+ public long dictId;
+ public byte[] dict;
+ public String kind;
+ public int dictChecksum;
+ public int dictLength;
+
+ /**
+ * Dictionary is valid if, keyspace and table are specified,
dictionary id is strictly positive integer,
+ * dictionary byte array is not nor not empty,
+ * kind corresponds to {@code Kind}, checksum and size are bigger than
0.
+ */
+ public void validate()
+ {
+ if (keyspace == null)
+ throw new IllegalArgumentException("keyspace not specified");
+ if (table == null)
+ throw new IllegalArgumentException("table not specified");
+ if (dictId <= 0)
+ throw new IllegalArgumentException("Provided dictionary id is
lower than 0, it is '" + dictId + "'.'");
+ if (dict == null || dict.length == 0)
+ throw new IllegalArgumentException("Provided dictionary byte
array is null or empty.");
+ if (kind == null)
+ throw new IllegalArgumentException("Provided kind is null.");
+
+ CompressionDictionary.Kind dictionarykind;
Review Comment:
nit: snake-case
```suggestion
CompressionDictionary.Kind dictionaryKind;
```
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryDetailsTabularData.java:
##########
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.compression;
+
+import java.util.Arrays;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import static java.lang.String.format;
+
+public class CompressionDictionaryDetailsTabularData
+{
+ public static final String KEYSPACE_NAME = "Keyspace";
+ public static final String TABLE_NAME = "Table";
+ public static final String DICT_ID_NAME = "DictId";
+ public static final String DICT_NAME = "Dict";
+ public static final String KIND_NAME = "Kind";
+ public static final String CHECKSUM_NAME = "Checksum";
+ public static final String SIZE_NAME = "Size";
+
+
+ private static final String[] ITEM_NAMES = new String[]{ KEYSPACE_NAME,
+ TABLE_NAME,
+ DICT_ID_NAME,
+ DICT_NAME,
+ KIND_NAME,
+ CHECKSUM_NAME,
+ SIZE_NAME };
+
+ private static final String[] ITEM_DESCS = new String[]{ "keyspace",
+ "table",
+ "dictionary_id",
+
"dictionary_bytes",
+ "kind",
+ "checksum",
+ "size" };
+
+ private static final String TYPE_NAME = "DictionaryDetails";
+ private static final String ROW_DESC = "DictionaryDetails";
+ private static final OpenType<?>[] ITEM_TYPES;
+ private static final CompositeType COMPOSITE_TYPE;
+ public static final TabularType TABULAR_TYPE;
+
+ static
+ {
+ try
+ {
+ ITEM_TYPES = new OpenType[]{ SimpleType.STRING, // keyspace
+ SimpleType.STRING, // table
+ SimpleType.LONG, // dict id
+ new
ArrayType<String[]>(SimpleType.BYTE, true), // dict bytes
+ SimpleType.STRING, // kind
+ SimpleType.INTEGER, // checksum
+ SimpleType.INTEGER }; // size of dict
bytes
+
+ COMPOSITE_TYPE = new CompositeType(TYPE_NAME, ROW_DESC,
ITEM_NAMES, ITEM_DESCS, ITEM_TYPES);
+ TABULAR_TYPE = new TabularType(TYPE_NAME, ROW_DESC,
COMPOSITE_TYPE, ITEM_NAMES);
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void from(String keyspace,
+ String table,
+ CompressionDictionary dictionary,
+ TabularDataSupport result)
+ {
+ result.put(from(keyspace, table, dictionary));
+ }
+
+ public static CompositeData from(String keyspace, String table,
CompressionDictionary dictionary)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ keyspace,
+ table,
+ dictionary.dictId().id,
+ dictionary.rawDictionary(),
+ dictionary.kind().name(),
+ dictionary.checksum(),
+ dictionary.size(),
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CompositeData from(CompressionDictionaryPojo pojo)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ pojo.keyspace,
+ pojo.table,
+ pojo.dictId,
+ pojo.dict,
+ pojo.kind,
+ pojo.dictChecksum,
+ pojo.dictLength
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Deserializes data to convenience object to work further with.
+ *
+ * @param compositeData data to create pojo from
+ * @return deserialized composite data to convenience object
+ * @throws IllegalArgumentException if values in deserialized object are
invalid.
+ * @see CompressionDictionaryPojo#validate()
+ */
+ public static CompressionDictionaryPojo from(CompositeData compositeData)
+ {
+ String keyspace = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KEYSPACE_NAME);
+ String table = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.TABLE_NAME);
+ long dictId = (Long)
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_ID_NAME);
+ byte[] dictionaryBytes = (byte[])
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_NAME);
+ String kind = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KIND_NAME);
+ int checksum = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.CHECKSUM_NAME);
+ int size = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.SIZE_NAME);
+
+ CompressionDictionaryPojo pojo = new CompressionDictionaryPojo();
+ pojo.keyspace = keyspace;
+ pojo.table = table;
+ pojo.dictId = dictId;
+ pojo.dict = dictionaryBytes;
+ pojo.kind = kind;
+ pojo.dictChecksum = checksum;
+ pojo.dictLength = size;
+
+ pojo.validate();
+
+ return pojo;
+ }
+
+ public static class CompressionDictionaryPojo
+ {
+ public String keyspace;
+ public String table;
+ public long dictId;
+ public byte[] dict;
+ public String kind;
+ public int dictChecksum;
+ public int dictLength;
+
+ /**
+ * Dictionary is valid if, keyspace and table are specified,
dictionary id is strictly positive integer,
+ * dictionary byte array is not nor not empty,
+ * kind corresponds to {@code Kind}, checksum and size are bigger than
0.
+ */
+ public void validate()
+ {
+ if (keyspace == null)
+ throw new IllegalArgumentException("keyspace not specified");
+ if (table == null)
+ throw new IllegalArgumentException("table not specified");
+ if (dictId <= 0)
+ throw new IllegalArgumentException("Provided dictionary id is
lower than 0, it is '" + dictId + "'.'");
+ if (dict == null || dict.length == 0)
+ throw new IllegalArgumentException("Provided dictionary byte
array is null or empty.");
+ if (kind == null)
+ throw new IllegalArgumentException("Provided kind is null.");
+
+ CompressionDictionary.Kind dictionarykind;
+
+ try
+ {
+ dictionarykind = CompressionDictionary.Kind.valueOf(kind);
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IllegalArgumentException("There is no such
dictionary kind like '" + kind + "'. Available kinds: " +
Arrays.asList(CompressionDictionary.Kind.values()));
+ }
+
+ if (dictChecksum <= 0)
+ throw new IllegalArgumentException("Checksum has to be
strictly positive number, it is '" + dictChecksum + "'.");
Review Comment:
I think the checksum could be negative. The checksum is from crc32, which
produce 32 bits. In signed integer, half of the values are negative.
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryManagerMBean.java:
##########
@@ -44,4 +46,42 @@ public interface CompressionDictionaryManagerMBean
* @return CompositeData representing {@link TrainingState}
*/
CompositeData getTrainingState();
+
+ /**
+ * Lists dictionaries for given keyspace and table. Returned dictionaries
+ * do not contain raw dictionary bytes.
+ *
+ * @param keyspace keyspace to get dictionaries from
+ * @param table table to get dictionaries from
+ * @return dictionaries for given keyspace and table
+ */
+ TabularData listDictionaries(String keyspace, String table);
+
+ /**
+ * Get latest compression dictionary.
+ *
+ * @param keyspace keyspace to get dictionary for
+ * @param table table to get dictionary for
+ * @return CompositeData representing dictionary or null of not found
Review Comment:
typo:
```suggestion
* @return CompositeData representing dictionary or null if not found
```
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionary.java:
##########
@@ -49,6 +49,20 @@ public interface CompressionDictionary extends AutoCloseable
*/
byte[] rawDictionary();
+ /**
+ * Get checkum of this dictionary.
+ *
+ * @return checksum of this dictionary
+ */
+ int checksum();
+
+ /**
+ * Get size of raw dictionary.
+ *
+ * @return size of raw dictionary, in bytes
+ */
+ int size();
Review Comment:
Why is this method needed in addition to `byte[] rawDictionary();`?
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionary.java:
##########
@@ -281,4 +315,60 @@ public String toString()
'}';
}
}
+
+ /**
+ * The purpose of lightweight dictionary is to not carry the actual
dictionary bytes for performance reasons.
+ * Handy for situations when retrieval from the database does not need to
contain dictionary
+ * or the instatiation of a proper dictionary object is not desirable or
unnecessary for other,
+ * mostly performance-related, reasons.
+ */
+ class LightweightCompressionDictionary implements CompressionDictionary
Review Comment:
Giving it another thought, should it implement `CompressionDictionary`? What
about just have a data class that contains 3 fields? I can see that
`LightweightCompressionDictionary` type is used specifically, e.g.
`SystemDistributedKeyspace#retrieveLightweightLatestCompressionDictionary`
returns `LightweightCompressionDictionary`, not `CompressionDictionary`.
By having a specific data type would give better type safety and caller may
not need to worry about the unnecessary methods (that are marked unsupported or
returning null, etc.)
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionary.java:
##########
@@ -281,4 +315,60 @@ public String toString()
'}';
}
}
+
+ /**
+ * The purpose of lightweight dictionary is to not carry the actual
dictionary bytes for performance reasons.
+ * Handy for situations when retrieval from the database does not need to
contain dictionary
+ * or the instatiation of a proper dictionary object is not desirable or
unnecessary for other,
Review Comment:
typo:
```suggestion
* or the instantiation of a proper dictionary object is not desirable
or unnecessary for other,
```
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryManagerMBean.java:
##########
@@ -44,4 +46,42 @@ public interface CompressionDictionaryManagerMBean
* @return CompositeData representing {@link TrainingState}
*/
CompositeData getTrainingState();
+
+ /**
+ * Lists dictionaries for given keyspace and table. Returned dictionaries
+ * do not contain raw dictionary bytes.
+ *
+ * @param keyspace keyspace to get dictionaries from
+ * @param table table to get dictionaries from
+ * @return dictionaries for given keyspace and table
+ */
+ TabularData listDictionaries(String keyspace, String table);
Review Comment:
1. Please name the method (this and the other new ones) as
`listCompressionDictionaries` or simply `list` if you consider dictionaries is
implied already implied by the mbean name. I intentionally stayed about from
using "dictionary" alone in the initial patch, because the word "dictionary" is
too general.
2. I do not think you need to supply `String keyspace, String table`. The
MBean, like CFS MBean, is per table. The keyspace and table names are already
known.
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryDetailsTabularData.java:
##########
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.compression;
+
+import java.util.Arrays;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import static java.lang.String.format;
+
+public class CompressionDictionaryDetailsTabularData
+{
+ public static final String KEYSPACE_NAME = "Keyspace";
+ public static final String TABLE_NAME = "Table";
+ public static final String DICT_ID_NAME = "DictId";
+ public static final String DICT_NAME = "Dict";
+ public static final String KIND_NAME = "Kind";
+ public static final String CHECKSUM_NAME = "Checksum";
+ public static final String SIZE_NAME = "Size";
+
+
+ private static final String[] ITEM_NAMES = new String[]{ KEYSPACE_NAME,
+ TABLE_NAME,
+ DICT_ID_NAME,
+ DICT_NAME,
+ KIND_NAME,
+ CHECKSUM_NAME,
+ SIZE_NAME };
+
+ private static final String[] ITEM_DESCS = new String[]{ "keyspace",
+ "table",
+ "dictionary_id",
+
"dictionary_bytes",
+ "kind",
+ "checksum",
+ "size" };
+
+ private static final String TYPE_NAME = "DictionaryDetails";
+ private static final String ROW_DESC = "DictionaryDetails";
+ private static final OpenType<?>[] ITEM_TYPES;
+ private static final CompositeType COMPOSITE_TYPE;
+ public static final TabularType TABULAR_TYPE;
+
+ static
+ {
+ try
+ {
+ ITEM_TYPES = new OpenType[]{ SimpleType.STRING, // keyspace
+ SimpleType.STRING, // table
+ SimpleType.LONG, // dict id
+ new
ArrayType<String[]>(SimpleType.BYTE, true), // dict bytes
+ SimpleType.STRING, // kind
+ SimpleType.INTEGER, // checksum
+ SimpleType.INTEGER }; // size of dict
bytes
+
+ COMPOSITE_TYPE = new CompositeType(TYPE_NAME, ROW_DESC,
ITEM_NAMES, ITEM_DESCS, ITEM_TYPES);
+ TABULAR_TYPE = new TabularType(TYPE_NAME, ROW_DESC,
COMPOSITE_TYPE, ITEM_NAMES);
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void from(String keyspace,
+ String table,
+ CompressionDictionary dictionary,
+ TabularDataSupport result)
+ {
+ result.put(from(keyspace, table, dictionary));
+ }
+
+ public static CompositeData from(String keyspace, String table,
CompressionDictionary dictionary)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ keyspace,
+ table,
+ dictionary.dictId().id,
+ dictionary.rawDictionary(),
+ dictionary.kind().name(),
+ dictionary.checksum(),
+ dictionary.size(),
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CompositeData from(CompressionDictionaryPojo pojo)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ pojo.keyspace,
+ pojo.table,
+ pojo.dictId,
+ pojo.dict,
+ pojo.kind,
+ pojo.dictChecksum,
+ pojo.dictLength
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Deserializes data to convenience object to work further with.
+ *
+ * @param compositeData data to create pojo from
+ * @return deserialized composite data to convenience object
+ * @throws IllegalArgumentException if values in deserialized object are
invalid.
+ * @see CompressionDictionaryPojo#validate()
+ */
+ public static CompressionDictionaryPojo from(CompositeData compositeData)
+ {
+ String keyspace = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KEYSPACE_NAME);
+ String table = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.TABLE_NAME);
+ long dictId = (Long)
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_ID_NAME);
+ byte[] dictionaryBytes = (byte[])
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_NAME);
+ String kind = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KIND_NAME);
+ int checksum = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.CHECKSUM_NAME);
+ int size = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.SIZE_NAME);
+
+ CompressionDictionaryPojo pojo = new CompressionDictionaryPojo();
+ pojo.keyspace = keyspace;
+ pojo.table = table;
+ pojo.dictId = dictId;
+ pojo.dict = dictionaryBytes;
+ pojo.kind = kind;
+ pojo.dictChecksum = checksum;
+ pojo.dictLength = size;
+
+ pojo.validate();
+
+ return pojo;
+ }
+
+ public static class CompressionDictionaryPojo
+ {
+ public String keyspace;
+ public String table;
+ public long dictId;
+ public byte[] dict;
+ public String kind;
+ public int dictChecksum;
+ public int dictLength;
+
+ /**
+ * Dictionary is valid if, keyspace and table are specified,
dictionary id is strictly positive integer,
+ * dictionary byte array is not nor not empty,
+ * kind corresponds to {@code Kind}, checksum and size are bigger than
0.
+ */
+ public void validate()
+ {
+ if (keyspace == null)
+ throw new IllegalArgumentException("keyspace not specified");
+ if (table == null)
+ throw new IllegalArgumentException("table not specified");
+ if (dictId <= 0)
+ throw new IllegalArgumentException("Provided dictionary id is
lower than 0, it is '" + dictId + "'.'");
+ if (dict == null || dict.length == 0)
+ throw new IllegalArgumentException("Provided dictionary byte
array is null or empty.");
+ if (kind == null)
+ throw new IllegalArgumentException("Provided kind is null.");
+
+ CompressionDictionary.Kind dictionarykind;
+
+ try
+ {
+ dictionarykind = CompressionDictionary.Kind.valueOf(kind);
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IllegalArgumentException("There is no such
dictionary kind like '" + kind + "'. Available kinds: " +
Arrays.asList(CompressionDictionary.Kind.values()));
+ }
+
+ if (dictChecksum <= 0)
+ throw new IllegalArgumentException("Checksum has to be
strictly positive number, it is '" + dictChecksum + "'.");
+ if (dictLength <= 0)
+ throw new IllegalArgumentException("Size has to be strictly
positive number, it is '" + dictLength + "'.");
+ if (dict.length != dictLength)
+ throw new IllegalArgumentException("The length of the provided
dictionary array (" + dict.length + ") does not equal to provided lenght value
(" + dictLength + ").");
Review Comment:
typo:
```suggestion
throw new IllegalArgumentException("The length of the
provided dictionary array (" + dict.length + ") does not equal to provided
length value (" + dictLength + ").");
```
##########
src/java/org/apache/cassandra/schema/SystemDistributedKeyspace.java:
##########
@@ -458,18 +459,45 @@ public static CompressionDictionary
retrieveLatestCompressionDictionary(String k
}
/**
- * Retrieves a specific compression dictionary for a given keyspace and
table.
+ * Retrieves the latest compression dictionary for a given keyspace and
table
+ * backed by {@link LightweightCompressionDictionary} object.
+ *
+ * @param keyspaceName the keyspace name to retrieve the dictionary for
+ * @param tableName the table name to retrieve the dictionary for
+ * @return the latest compression dictionary for the specified keyspace
and table,
+ * or null if no dictionary exists or if an error occurs during
retrieval
+ */
+ @Nullable
Review Comment:
👍 on adding the annotation.
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryManager.java:
##########
@@ -244,6 +251,68 @@ public CompositeData getTrainingState()
return dictionaryTrainer.getTrainingState().toCompositeData();
}
+ @Override
+ public TabularData listDictionaries(String keyspace, String table)
+ {
+ List<LightweightCompressionDictionary> dictionaries =
SystemDistributedKeyspace.retrieveLightweightCompressionDictionaries(keyspace,
table);
Review Comment:
We should use the field variables of the enclosing
`CompressionDictionaryManager` instance.
```
private final String keyspaceName;
private final String tableName;
```
##########
src/java/org/apache/cassandra/tools/nodetool/CompressionDictionary.java:
##########
@@ -119,4 +136,168 @@ private static void displayProgress(TrainingState
trainingState, long startTime,
status, sampleCount, sampleSizeMB, elapsedSeconds);
}
}
+
+ @Command(name = "list", description = "List available dictionaries of
specific keyspace and table.")
+ public static class List extends AbstractCommand
Review Comment:
nit: Could we rename the command, so that the full qualified name
`java.util.List` can be avoided?
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryManagerMBean.java:
##########
@@ -44,4 +46,42 @@ public interface CompressionDictionaryManagerMBean
* @return CompositeData representing {@link TrainingState}
*/
CompositeData getTrainingState();
+
+ /**
+ * Lists dictionaries for given keyspace and table. Returned dictionaries
+ * do not contain raw dictionary bytes.
+ *
+ * @param keyspace keyspace to get dictionaries from
+ * @param table table to get dictionaries from
+ * @return dictionaries for given keyspace and table
+ */
+ TabularData listDictionaries(String keyspace, String table);
+
+ /**
+ * Get latest compression dictionary.
+ *
+ * @param keyspace keyspace to get dictionary for
+ * @param table table to get dictionary for
+ * @return CompositeData representing dictionary or null of not found
+ */
+ @Nullable
+ CompositeData getDictionary(String keyspace, String table);
+
+ /**
+ * Get compression dictionary.
+ *
+ * @param keyspace keyspace to get dictionary for
+ * @param table table to get dictionary for
+ * @param dictId id of dictionary to get
+ * @return CompositeData representing dictionary or null of not found
+ */
+ @Nullable
+ CompositeData getDictionary(String keyspace, String table, long dictId);
Review Comment:
`String keyspace, String table` are redundant.
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryManagerMBean.java:
##########
@@ -44,4 +46,42 @@ public interface CompressionDictionaryManagerMBean
* @return CompositeData representing {@link TrainingState}
*/
CompositeData getTrainingState();
+
+ /**
+ * Lists dictionaries for given keyspace and table. Returned dictionaries
+ * do not contain raw dictionary bytes.
+ *
+ * @param keyspace keyspace to get dictionaries from
+ * @param table table to get dictionaries from
+ * @return dictionaries for given keyspace and table
+ */
+ TabularData listDictionaries(String keyspace, String table);
+
+ /**
+ * Get latest compression dictionary.
+ *
+ * @param keyspace keyspace to get dictionary for
+ * @param table table to get dictionary for
+ * @return CompositeData representing dictionary or null of not found
+ */
+ @Nullable
+ CompositeData getDictionary(String keyspace, String table);
Review Comment:
`String keyspace, String table` are redundant.
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryDetailsTabularData.java:
##########
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.compression;
+
+import java.util.Arrays;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import static java.lang.String.format;
+
+public class CompressionDictionaryDetailsTabularData
+{
+ public static final String KEYSPACE_NAME = "Keyspace";
+ public static final String TABLE_NAME = "Table";
+ public static final String DICT_ID_NAME = "DictId";
+ public static final String DICT_NAME = "Dict";
+ public static final String KIND_NAME = "Kind";
+ public static final String CHECKSUM_NAME = "Checksum";
+ public static final String SIZE_NAME = "Size";
+
+
+ private static final String[] ITEM_NAMES = new String[]{ KEYSPACE_NAME,
+ TABLE_NAME,
+ DICT_ID_NAME,
+ DICT_NAME,
+ KIND_NAME,
+ CHECKSUM_NAME,
+ SIZE_NAME };
+
+ private static final String[] ITEM_DESCS = new String[]{ "keyspace",
+ "table",
+ "dictionary_id",
+
"dictionary_bytes",
+ "kind",
+ "checksum",
+ "size" };
+
+ private static final String TYPE_NAME = "DictionaryDetails";
+ private static final String ROW_DESC = "DictionaryDetails";
+ private static final OpenType<?>[] ITEM_TYPES;
+ private static final CompositeType COMPOSITE_TYPE;
+ public static final TabularType TABULAR_TYPE;
+
+ static
+ {
+ try
+ {
+ ITEM_TYPES = new OpenType[]{ SimpleType.STRING, // keyspace
+ SimpleType.STRING, // table
+ SimpleType.LONG, // dict id
+ new
ArrayType<String[]>(SimpleType.BYTE, true), // dict bytes
+ SimpleType.STRING, // kind
+ SimpleType.INTEGER, // checksum
+ SimpleType.INTEGER }; // size of dict
bytes
+
+ COMPOSITE_TYPE = new CompositeType(TYPE_NAME, ROW_DESC,
ITEM_NAMES, ITEM_DESCS, ITEM_TYPES);
+ TABULAR_TYPE = new TabularType(TYPE_NAME, ROW_DESC,
COMPOSITE_TYPE, ITEM_NAMES);
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void from(String keyspace,
+ String table,
+ CompressionDictionary dictionary,
+ TabularDataSupport result)
+ {
+ result.put(from(keyspace, table, dictionary));
+ }
+
+ public static CompositeData from(String keyspace, String table,
CompressionDictionary dictionary)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ keyspace,
+ table,
+ dictionary.dictId().id,
+ dictionary.rawDictionary(),
+ dictionary.kind().name(),
+ dictionary.checksum(),
+ dictionary.size(),
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CompositeData from(CompressionDictionaryPojo pojo)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ pojo.keyspace,
+ pojo.table,
+ pojo.dictId,
+ pojo.dict,
+ pojo.kind,
+ pojo.dictChecksum,
+ pojo.dictLength
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Deserializes data to convenience object to work further with.
+ *
+ * @param compositeData data to create pojo from
+ * @return deserialized composite data to convenience object
+ * @throws IllegalArgumentException if values in deserialized object are
invalid.
+ * @see CompressionDictionaryPojo#validate()
+ */
+ public static CompressionDictionaryPojo from(CompositeData compositeData)
+ {
+ String keyspace = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KEYSPACE_NAME);
+ String table = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.TABLE_NAME);
+ long dictId = (Long)
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_ID_NAME);
+ byte[] dictionaryBytes = (byte[])
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_NAME);
+ String kind = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KIND_NAME);
+ int checksum = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.CHECKSUM_NAME);
+ int size = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.SIZE_NAME);
+
+ CompressionDictionaryPojo pojo = new CompressionDictionaryPojo();
+ pojo.keyspace = keyspace;
+ pojo.table = table;
+ pojo.dictId = dictId;
+ pojo.dict = dictionaryBytes;
+ pojo.kind = kind;
+ pojo.dictChecksum = checksum;
+ pojo.dictLength = size;
+
+ pojo.validate();
+
+ return pojo;
+ }
+
+ public static class CompressionDictionaryPojo
+ {
+ public String keyspace;
+ public String table;
+ public long dictId;
+ public byte[] dict;
+ public String kind;
+ public int dictChecksum;
+ public int dictLength;
+
+ /**
+ * Dictionary is valid if, keyspace and table are specified,
dictionary id is strictly positive integer,
+ * dictionary byte array is not nor not empty,
+ * kind corresponds to {@code Kind}, checksum and size are bigger than
0.
+ */
+ public void validate()
+ {
+ if (keyspace == null)
+ throw new IllegalArgumentException("keyspace not specified");
+ if (table == null)
+ throw new IllegalArgumentException("table not specified");
+ if (dictId <= 0)
+ throw new IllegalArgumentException("Provided dictionary id is
lower than 0, it is '" + dictId + "'.'");
+ if (dict == null || dict.length == 0)
+ throw new IllegalArgumentException("Provided dictionary byte
array is null or empty.");
Review Comment:
Can we add a new check to ensure the importing dictionary size does not
exceed certain amount? We will not want to import dictionary that is larger
than 1MiB, most likely the dictionary should be less than 100KiB.
##########
src/java/org/apache/cassandra/db/compression/ZstdCompressionDictionary.java:
##########
@@ -38,16 +39,28 @@ public class ZstdCompressionDictionary implements
CompressionDictionary, SelfRef
private final DictId dictId;
private final byte[] rawDictionary;
+ private volatile int checksum;
Review Comment:
why is it `volatile`? the value is never updated.
##########
src/java/org/apache/cassandra/schema/SystemDistributedKeyspace.java:
##########
@@ -481,6 +509,38 @@ public static CompressionDictionary
retrieveCompressionDictionary(String keyspac
}
}
+ /**
+ * Retrieves all dictionaries for a given keyspace and table.
+ *
+ * @param keyspaceName the keyspace name to retrieve the dictionary for
+ * @param tableName the table name to retrieve the dictionary for
+ * @return the compression dictionaries identified by the specified
keyspace and table,
+ * or null if no dictionary exists or if an error occurs during
retrieval
+ */
+ @Nullable
Review Comment:
Should it return an empty list? It is cheap and simplifies the call-site
handling.
##########
src/java/org/apache/cassandra/tools/nodetool/CompressionDictionary.java:
##########
@@ -18,25 +18,42 @@
package org.apache.cassandra.tools.nodetool;
import java.io.PrintStream;
+import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.TabularData;
import com.google.common.util.concurrent.Uninterruptibles;
+import
org.apache.cassandra.db.compression.CompressionDictionaryDetailsTabularData;
+import
org.apache.cassandra.db.compression.CompressionDictionaryDetailsTabularData.CompressionDictionaryPojo;
import
org.apache.cassandra.db.compression.ICompressionDictionaryTrainer.TrainingStatus;
import org.apache.cassandra.db.compression.TrainingState;
+import org.apache.cassandra.io.util.File;
+import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import org.apache.cassandra.utils.Clock;
+import org.apache.cassandra.utils.JsonUtils;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
@Command(name = "compressiondictionary",
- description = "Manage compression dictionaries",
- subcommands = { CompressionDictionary.Train.class })
+description = "Manage compression dictionaries",
+subcommands = { CompressionDictionary.Train.class,
+ CompressionDictionary.List.class,
+ CompressionDictionary.Export.class,
+ CompressionDictionary.Import.class })
public class CompressionDictionary
{
@Command(name = "train",
- description = "Manually trigger compression dictionary training
for a table. If no SSTables are available, the memtable will be flushed first.")
+ description = "Manually trigger compression dictionary training for a
table. If no SSTables are available, the memtable will be flushed first.")
Review Comment:
nit: keep the alignment of the annotation elements?
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryDetailsTabularData.java:
##########
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.compression;
+
+import java.util.Arrays;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import static java.lang.String.format;
+
+public class CompressionDictionaryDetailsTabularData
+{
+ public static final String KEYSPACE_NAME = "Keyspace";
+ public static final String TABLE_NAME = "Table";
+ public static final String DICT_ID_NAME = "DictId";
+ public static final String DICT_NAME = "Dict";
+ public static final String KIND_NAME = "Kind";
+ public static final String CHECKSUM_NAME = "Checksum";
+ public static final String SIZE_NAME = "Size";
+
+
+ private static final String[] ITEM_NAMES = new String[]{ KEYSPACE_NAME,
+ TABLE_NAME,
+ DICT_ID_NAME,
+ DICT_NAME,
+ KIND_NAME,
+ CHECKSUM_NAME,
+ SIZE_NAME };
+
+ private static final String[] ITEM_DESCS = new String[]{ "keyspace",
+ "table",
+ "dictionary_id",
+
"dictionary_bytes",
+ "kind",
+ "checksum",
+ "size" };
+
+ private static final String TYPE_NAME = "DictionaryDetails";
+ private static final String ROW_DESC = "DictionaryDetails";
+ private static final OpenType<?>[] ITEM_TYPES;
+ private static final CompositeType COMPOSITE_TYPE;
+ public static final TabularType TABULAR_TYPE;
+
+ static
+ {
+ try
+ {
+ ITEM_TYPES = new OpenType[]{ SimpleType.STRING, // keyspace
+ SimpleType.STRING, // table
+ SimpleType.LONG, // dict id
+ new
ArrayType<String[]>(SimpleType.BYTE, true), // dict bytes
+ SimpleType.STRING, // kind
+ SimpleType.INTEGER, // checksum
+ SimpleType.INTEGER }; // size of dict
bytes
+
+ COMPOSITE_TYPE = new CompositeType(TYPE_NAME, ROW_DESC,
ITEM_NAMES, ITEM_DESCS, ITEM_TYPES);
+ TABULAR_TYPE = new TabularType(TYPE_NAME, ROW_DESC,
COMPOSITE_TYPE, ITEM_NAMES);
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void from(String keyspace,
+ String table,
+ CompressionDictionary dictionary,
+ TabularDataSupport result)
+ {
+ result.put(from(keyspace, table, dictionary));
+ }
+
+ public static CompositeData from(String keyspace, String table,
CompressionDictionary dictionary)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ keyspace,
+ table,
+ dictionary.dictId().id,
+ dictionary.rawDictionary(),
+ dictionary.kind().name(),
+ dictionary.checksum(),
+ dictionary.size(),
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CompositeData from(CompressionDictionaryPojo pojo)
+ {
+ try
+ {
+ return new CompositeDataSupport(COMPOSITE_TYPE,
+ ITEM_NAMES,
+ new Object[]
+ {
+ pojo.keyspace,
+ pojo.table,
+ pojo.dictId,
+ pojo.dict,
+ pojo.kind,
+ pojo.dictChecksum,
+ pojo.dictLength
+ });
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Deserializes data to convenience object to work further with.
+ *
+ * @param compositeData data to create pojo from
+ * @return deserialized composite data to convenience object
+ * @throws IllegalArgumentException if values in deserialized object are
invalid.
+ * @see CompressionDictionaryPojo#validate()
+ */
+ public static CompressionDictionaryPojo from(CompositeData compositeData)
+ {
+ String keyspace = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KEYSPACE_NAME);
+ String table = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.TABLE_NAME);
+ long dictId = (Long)
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_ID_NAME);
+ byte[] dictionaryBytes = (byte[])
compositeData.get(CompressionDictionaryDetailsTabularData.DICT_NAME);
+ String kind = (String)
compositeData.get(CompressionDictionaryDetailsTabularData.KIND_NAME);
+ int checksum = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.CHECKSUM_NAME);
+ int size = (Integer)
compositeData.get(CompressionDictionaryDetailsTabularData.SIZE_NAME);
+
+ CompressionDictionaryPojo pojo = new CompressionDictionaryPojo();
+ pojo.keyspace = keyspace;
+ pojo.table = table;
+ pojo.dictId = dictId;
+ pojo.dict = dictionaryBytes;
+ pojo.kind = kind;
+ pojo.dictChecksum = checksum;
+ pojo.dictLength = size;
+
+ pojo.validate();
+
+ return pojo;
+ }
+
+ public static class CompressionDictionaryPojo
+ {
+ public String keyspace;
+ public String table;
+ public long dictId;
+ public byte[] dict;
+ public String kind;
+ public int dictChecksum;
+ public int dictLength;
+
+ /**
+ * Dictionary is valid if, keyspace and table are specified,
dictionary id is strictly positive integer,
+ * dictionary byte array is not nor not empty,
+ * kind corresponds to {@code Kind}, checksum and size are bigger than
0.
+ */
+ public void validate()
+ {
+ if (keyspace == null)
+ throw new IllegalArgumentException("keyspace not specified");
+ if (table == null)
+ throw new IllegalArgumentException("table not specified");
+ if (dictId <= 0)
+ throw new IllegalArgumentException("Provided dictionary id is
lower than 0, it is '" + dictId + "'.'");
+ if (dict == null || dict.length == 0)
+ throw new IllegalArgumentException("Provided dictionary byte
array is null or empty.");
+ if (kind == null)
+ throw new IllegalArgumentException("Provided kind is null.");
+
+ CompressionDictionary.Kind dictionarykind;
+
+ try
+ {
+ dictionarykind = CompressionDictionary.Kind.valueOf(kind);
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IllegalArgumentException("There is no such
dictionary kind like '" + kind + "'. Available kinds: " +
Arrays.asList(CompressionDictionary.Kind.values()));
+ }
+
+ if (dictChecksum <= 0)
+ throw new IllegalArgumentException("Checksum has to be
strictly positive number, it is '" + dictChecksum + "'.");
+ if (dictLength <= 0)
+ throw new IllegalArgumentException("Size has to be strictly
positive number, it is '" + dictLength + "'.");
+ if (dict.length != dictLength)
+ throw new IllegalArgumentException("The length of the provided
dictionary array (" + dict.length + ") does not equal to provided lenght value
(" + dictLength + ").");
+
+ int checksumOfDictionaryToImport =
CompressionDictionary.calculateChecksum((byte) dictionarykind.ordinal(),
dictId, dict);
+ if (checksumOfDictionaryToImport != dictChecksum)
+ {
+ throw new IllegalArgumentException(format("Computed checksum
of dictionary to import (%s) is different from checksum specified on input
(%s).",
Review Comment:
nit: error message revision. `Compression dictionary checksum mismatched.
Actual: %s; provided: %s."
##########
src/java/org/apache/cassandra/db/compression/CompressionDictionaryManager.java:
##########
@@ -244,6 +251,68 @@ public CompositeData getTrainingState()
return dictionaryTrainer.getTrainingState().toCompositeData();
}
+ @Override
+ public TabularData listDictionaries(String keyspace, String table)
+ {
+ List<LightweightCompressionDictionary> dictionaries =
SystemDistributedKeyspace.retrieveLightweightCompressionDictionaries(keyspace,
table);
+ TabularDataSupport tableData = new
TabularDataSupport(CompressionDictionaryDetailsTabularData.TABULAR_TYPE);
+
+ if (dictionaries == null)
+ {
+ return tableData;
+ }
+
+ for (LightweightCompressionDictionary dictionary : dictionaries)
+ {
+ CompressionDictionaryDetailsTabularData.from(keyspace, table,
dictionary, tableData);
Review Comment:
It looks a bit odd that no return value is there in the `from` method and
not used.
Could you use this instead and delete the unused `from` method?
```java
for (LightweightCompressionDictionary dictionary : dictionaries)
{
tableData.put(CompressionDictionaryDetailsTabularData.from(keyspace, table,
dictionary));
}
```
It leads to the question: should the
`CompressionDictionaryDetailsTabularData` be called `TabularData`? It is to
return the individual compression dictionary details. Would it make better
sense to rename to `CompressionDictionaryDetails`?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]