Skip to content

Commit 06440e9

Browse files
committed
Merge branch 'cassandra-5.0' into trunk
* cassandra-5.0: Prevent too long table names not fitting file names
2 parents 55db318 + 7f92e1a commit 06440e9

File tree

13 files changed

+223
-105
lines changed

13 files changed

+223
-105
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ Merged from 4.1:
308308
* Enforce CQL message size limit on multiframe messages (CASSANDRA-20052)
309309
* Fix race condition in DecayingEstimatedHistogramReservoir during rescale (CASSANDRA-19365)
310310
Merged from 4.0:
311+
* Prevent too long table names not fitting file names (CASSANDRA-20389)
311312
* Update Jackson to 2.19.2 (CASSANDRA-20848)
312313
* Update commons-lang3 to 3.18.0 (CASSANDRA-20849)
313314
* Add NativeTransportMaxConcurrentConnectionsPerIp to StorageProxyMBean (CASSANDRA-20642)

src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
import org.apache.cassandra.transport.Event.SchemaChange;
4747
import org.apache.cassandra.transport.messages.ResultMessage;
4848

49+
import static org.apache.cassandra.schema.KeyspaceMetadata.validateKeyspaceName;
50+
4951
abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspaceCqlStatement, SchemaTransformation
5052
{
5153
private static final Logger logger = LoggerFactory.getLogger(AlterSchemaStatement.class);
@@ -168,7 +170,8 @@ public ResultMessage execute(QueryState state)
168170
if (null != keyspace && keyspace.isVirtual())
169171
throw ire("Virtual keyspace '%s' is not user-modifiable", keyspaceName);
170172

171-
validateKeyspaceName();
173+
validateKeyspaceName(keyspaceName, AlterSchemaStatement::ire);
174+
172175
setExecutionTimestamp(state.getTimestamp());
173176
// Perform a 'dry-run' attempt to apply the transformation locally before submitting to the CMS. This can save a
174177
// round trip to the CMS for things syntax errors, but also fail fast for things like configuration errors.
@@ -211,16 +214,6 @@ protected ClusterMetadata commit(ClusterMetadata metadata)
211214
return Schema.instance.submit(this);
212215
}
213216

214-
private void validateKeyspaceName()
215-
{
216-
if (!SchemaConstants.isValidName(keyspaceName))
217-
{
218-
throw ire("Keyspace name must not be empty, more than %d characters long, " +
219-
"or contain non-alphanumeric-underscore characters (got '%s')",
220-
SchemaConstants.NAME_LENGTH, keyspaceName);
221-
}
222-
}
223-
224217
protected void validateDefaultTimeToLive(TableParams params)
225218
{
226219
if (params.defaultTimeToLive == 0

src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ public final class CreateIndexStatement extends AlterSchemaStatement
6060
public static final String CUSTOM_MULTIPLE_COLUMNS = "Only CUSTOM indexes support multiple columns";
6161
public static final String DUPLICATE_TARGET_COLUMN = "Duplicate column '%s' in index target list";
6262
public static final String COLUMN_DOES_NOT_EXIST = "Column '%s' doesn't exist";
63-
public static final String INVALID_CUSTOM_INDEX_TARGET = "Column '%s' is longer than the permissible name length of %d characters or" +
64-
" contains non-alphanumeric-underscore characters";
63+
public static final String INVALID_CHARS_CUSTOM_INDEX_TARGET = "Column '%s' contains non-alphanumeric-underscore characters";
64+
public static final String TOO_LONG_CUSTOM_INDEX_TARGET = "Column '%s' is longer than the permissible name length of %d characters";
6565
public static final String COLLECTIONS_WITH_DURATIONS_NOT_SUPPORTED = "Secondary indexes are not supported on collections containing durations";
6666
public static final String TUPLES_WITH_DURATIONS_NOT_SUPPORTED = "Secondary indexes are not supported on tuples containing durations";
6767
public static final String DURATIONS_NOT_SUPPORTED = "Secondary indexes are not supported on duration columns";
@@ -220,6 +220,14 @@ Set<String> clientWarnings(KeyspacesDiff diff)
220220
return ImmutableSet.of();
221221
}
222222

223+
private void validateCustomIndexColumnName(String name)
224+
{
225+
if (!SchemaConstants.isValidCharsName(name))
226+
throw ire(INVALID_CHARS_CUSTOM_INDEX_TARGET, name);
227+
if (name.length() > SchemaConstants.NAME_LENGTH)
228+
throw ire(TOO_LONG_CUSTOM_INDEX_TARGET, name, SchemaConstants.NAME_LENGTH);
229+
}
230+
223231
private void validateIndexTarget(TableMetadata table, IndexMetadata.Kind kind, IndexTarget target)
224232
{
225233
ColumnMetadata column = table.getColumn(target.column);
@@ -229,8 +237,9 @@ private void validateIndexTarget(TableMetadata table, IndexMetadata.Kind kind, I
229237

230238
AbstractType<?> baseType = column.type.unwrap();
231239

232-
if ((kind == IndexMetadata.Kind.CUSTOM) && !SchemaConstants.isValidName(target.column.toString()))
233-
throw ire(INVALID_CUSTOM_INDEX_TARGET, target.column, SchemaConstants.NAME_LENGTH);
240+
// TODO: this check needs to be removed with CASSANDRA-20235
241+
if ((kind == IndexMetadata.Kind.CUSTOM))
242+
validateCustomIndexColumnName(target.column.toString());
234243

235244
if (column.type.referencesDuration())
236245
{

src/java/org/apache/cassandra/db/Directories.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public class Directories
117117
public static final String SNAPSHOT_SUBDIR = "snapshots";
118118
public static final String TMP_SUBDIR = "tmp";
119119
public static final String SECONDARY_INDEX_NAME_SEPARATOR = ".";
120+
public static final String TABLE_DIRECTORY_NAME_SEPARATOR = "-";
120121

121122
/**
122123
* The directories used to store keyspaces data.
@@ -224,14 +225,11 @@ public Directories(final TableMetadata metadata, DataDirectory[] paths)
224225
this.metadata = metadata;
225226
this.paths = paths;
226227
ImmutableMap.Builder<Path, DataDirectory> canonicalPathsBuilder = ImmutableMap.builder();
227-
String tableId = metadata.id.toHexString();
228-
int idx = metadata.name.indexOf(SECONDARY_INDEX_NAME_SEPARATOR);
229-
String cfName = idx >= 0 ? metadata.name.substring(0, idx) : metadata.name;
230-
String indexNameWithDot = idx >= 0 ? metadata.name.substring(idx) : null;
228+
String indexNameWithDot = metadata.getIndexNameWithDot();
231229

232230
this.dataPaths = new File[paths.length];
233231
// If upgraded from version less than 2.1, use existing directories
234-
String oldSSTableRelativePath = join(metadata.keyspace, cfName);
232+
String oldSSTableRelativePath = join(metadata.keyspace, metadata.getTableName());
235233
for (int i = 0; i < paths.length; ++i)
236234
{
237235
// check if old SSTable directory exists
@@ -244,7 +242,7 @@ public Directories(final TableMetadata metadata, DataDirectory[] paths)
244242
{
245243
canonicalPathsBuilder = ImmutableMap.builder();
246244
// use 2.1+ style
247-
String newSSTableRelativePath = join(metadata.keyspace, cfName + '-' + tableId);
245+
String newSSTableRelativePath = join(metadata.keyspace, metadata.getTableDirectoryName());
248246
for (int i = 0; i < paths.length; ++i)
249247
{
250248
File dataPath = new File(paths[i].location, newSSTableRelativePath);

src/java/org/apache/cassandra/schema/IndexMetadata.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.util.TreeSet;
2929
import java.util.UUID;
3030
import java.util.concurrent.ConcurrentHashMap;
31-
import java.util.regex.Pattern;
3231
import java.util.stream.Collectors;
3332

3433
import com.google.common.base.Objects;
@@ -56,6 +55,8 @@
5655

5756
import static org.apache.cassandra.db.TypeSizes.sizeof;
5857
import static org.apache.cassandra.utils.LocalizeString.toLowerCaseLocalized;
58+
import static org.apache.cassandra.schema.SchemaConstants.PATTERN_NON_WORD_CHAR;
59+
import static org.apache.cassandra.schema.SchemaConstants.isValidCharsName;
5960

6061
/**
6162
* An immutable representation of secondary index metadata.
@@ -64,10 +65,6 @@ public final class IndexMetadata
6465
{
6566
private static final Logger logger = LoggerFactory.getLogger(IndexMetadata.class);
6667

67-
private static final Pattern PATTERN_NON_WORD_CHAR = Pattern.compile("\\W");
68-
private static final Pattern PATTERN_WORD_CHARS = Pattern.compile("\\w+");
69-
70-
7168
public static final Serializer serializer = new Serializer();
7269
public static final MetadataSerializer metadataSerializer = new MetadataSerializer();
7370

@@ -117,29 +114,25 @@ public static IndexMetadata fromIndexTargets(List<IndexTarget> targets,
117114
{
118115
Map<String, String> newOptions = new HashMap<>(options);
119116
newOptions.put(IndexTarget.TARGET_OPTION_NAME, targets.stream()
120-
.map(target -> target.asCqlString())
117+
.map(IndexTarget::asCqlString)
121118
.collect(Collectors.joining(", ")));
122119
return new IndexMetadata(name, newOptions, kind);
123120
}
124121

125-
public static boolean isNameValid(String name)
126-
{
127-
return name != null && !name.isEmpty() && PATTERN_WORD_CHARS.matcher(name).matches();
128-
}
129-
130122
public static String generateDefaultIndexName(String table, ColumnIdentifier column)
131123
{
132-
return PATTERN_NON_WORD_CHAR.matcher(table + "_" + column.toString() + "_idx").replaceAll("");
124+
return PATTERN_NON_WORD_CHAR.matcher(table + '_' + column.toString() + "_idx").replaceAll("");
133125
}
134126

135127
public static String generateDefaultIndexName(String table)
136128
{
137-
return PATTERN_NON_WORD_CHAR.matcher(table + "_" + "idx").replaceAll("");
129+
return PATTERN_NON_WORD_CHAR.matcher(table + "_idx").replaceAll("");
138130
}
139131

140132
public void validate(TableMetadata table)
141133
{
142-
if (!isNameValid(name))
134+
// TODO: address validating the length by CASSANDRA-20445
135+
if (!isValidCharsName(name))
143136
throw new ConfigurationException("Illegal index name " + name);
144137

145138
if (kind == null)

src/java/org/apache/cassandra/schema/KeyspaceMetadata.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.apache.cassandra.cql3.functions.UDFunction;
4040
import org.apache.cassandra.db.marshal.UserType;
4141
import org.apache.cassandra.exceptions.ConfigurationException;
42+
import org.apache.cassandra.exceptions.RequestValidationException;
4243
import org.apache.cassandra.io.util.DataInputPlus;
4344
import org.apache.cassandra.io.util.DataOutputPlus;
4445
import org.apache.cassandra.locator.AbstractReplicationStrategy;
@@ -53,6 +54,7 @@
5354
import static com.google.common.collect.Iterables.any;
5455
import static java.lang.String.format;
5556
import static org.apache.cassandra.db.TypeSizes.sizeof;
57+
import static org.apache.cassandra.schema.SchemaConstants.TABLE_NAME_LENGTH;
5658
import static org.apache.cassandra.utils.LocalizeString.toLowerCaseLocalized;
5759

5860
/**
@@ -62,6 +64,23 @@ public final class KeyspaceMetadata implements SchemaElement
6264
{
6365
public static final Serializer serializer = new Serializer();
6466

67+
/**
68+
* Validates the keyspace name for valid characters and correct length.
69+
* Throws an exception if it's invalid.
70+
*
71+
* @param keyspaceName The name of the keyspace to validate
72+
* @param exceptionBuilder The exception constructor to throw if validation fails
73+
*/
74+
public static <T extends RequestValidationException> void validateKeyspaceName(String keyspaceName, java.util.function.Function<String, T> exceptionBuilder)
75+
{
76+
if (!SchemaConstants.isValidCharsName(keyspaceName))
77+
throw exceptionBuilder.apply(format("Keyspace name must not be empty and must contain alphanumeric or underscore characters only (got \"%s\")",
78+
keyspaceName));
79+
if (keyspaceName.length() > SchemaConstants.NAME_LENGTH)
80+
throw exceptionBuilder.apply(format("Keyspace name must not be more than %d characters long (got %d characters for \"%s\")",
81+
TABLE_NAME_LENGTH, keyspaceName.length(), keyspaceName));
82+
}
83+
6584
public enum Kind
6685
{
6786
REGULAR, VIRTUAL
@@ -214,7 +233,7 @@ public String findAvailableIndexName(String baseName)
214233
}
215234

216235
/**
217-
* find an avaiable index name based on the indexes in target keyspace and indexes collections
236+
* find an available index name based on the indexes in target keyspace and indexes collections
218237
* @param baseName the base name of index
219238
* @param indexes find out whether there is any conflict with baseName in the indexes
220239
* @param keyspaceMetadata find out whether there is any conflict with baseName in keyspaceMetadata
@@ -381,14 +400,7 @@ public String toCqlString(boolean withWarnings, boolean withInternals, boolean i
381400

382401
public void validate(ClusterMetadata metadata)
383402
{
384-
if (!SchemaConstants.isValidName(name))
385-
{
386-
throw new ConfigurationException(format("Keyspace name must not be empty, more than %s characters long, "
387-
+ "or contain non-alphanumeric-underscore characters (got \"%s\")",
388-
SchemaConstants.NAME_LENGTH,
389-
name));
390-
}
391-
403+
validateKeyspaceName(name, ConfigurationException::new);
392404
params.validate(name, null, metadata);
393405
tablesAndViews().forEach(TableMetadata::validate);
394406

src/java/org/apache/cassandra/schema/SchemaConstants.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.cassandra.service.accord.AccordKeyspace;
3434
import org.apache.cassandra.tracing.TraceKeyspace;
3535

36+
import static org.apache.cassandra.db.Directories.TABLE_DIRECTORY_NAME_SEPARATOR;
3637
import static org.apache.cassandra.utils.LocalizeString.toLowerCaseLocalized;
3738

3839
/**
@@ -41,6 +42,7 @@
4142
public final class SchemaConstants
4243
{
4344
public static final Pattern PATTERN_WORD_CHARS = Pattern.compile("\\w+");
45+
public static final Pattern PATTERN_NON_WORD_CHAR = Pattern.compile("\\W");
4446

4547
public static final String SYSTEM_KEYSPACE_NAME = "system";
4648
public static final String SCHEMA_KEYSPACE_NAME = "system_schema";
@@ -77,14 +79,37 @@ public final class SchemaConstants
7779
*/
7880
public static final int NAME_LENGTH = 48;
7981

82+
/**
83+
* Longest acceptable file name. Longer names lead to too long file name error.
84+
*/
85+
public static final int FILENAME_LENGTH = 255;
86+
87+
/**
88+
* Length of a table uuid as a hex string.
89+
*/
90+
public static final int TABLE_UUID_AS_HEX_LENGTH = 32;
91+
92+
/**
93+
* Longest acceptable table name, so it can be used in a directory
94+
* name constructed with a suffix of a table id and a separator.
95+
*/
96+
public static final int TABLE_NAME_LENGTH = FILENAME_LENGTH - TABLE_UUID_AS_HEX_LENGTH - TABLE_DIRECTORY_NAME_SEPARATOR.length();
97+
8098
// 59adb24e-f3cd-3e02-97f0-5b395827453f
8199
public static final UUID emptyVersion;
82100

83101
public static final List<String> LEGACY_AUTH_TABLES = Arrays.asList("credentials", "users", "permissions");
84102

85-
public static boolean isValidName(String name)
103+
/**
104+
* Validates that a name is not empty and contains only alphanumeric characters or
105+
* underscore, so it can be used in file or directory names.
106+
*
107+
* @param name the name to check
108+
* @return whether the non-empty name contains only valid characters
109+
*/
110+
public static boolean isValidCharsName(String name)
86111
{
87-
return name != null && !name.isEmpty() && name.length() <= NAME_LENGTH && PATTERN_WORD_CHARS.matcher(name).matches();
112+
return name != null && !name.isEmpty() && PATTERN_WORD_CHARS.matcher(name).matches();
88113
}
89114

90115
static

0 commit comments

Comments
 (0)