diff --git a/CHANGES.txt b/CHANGES.txt index 0b8be6e1ae..209ff70c6f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -58,6 +58,9 @@ Merged from 2.0: * Fix writetime/ttl functions for static columns (CASSANDRA-7081) * Suggest CTRL-C or semicolon after three blank lines in cqlsh (CASSANDRA-7142) * Fix 2ndary index queries with DESC clustering order (CASSANDRA-6950) + * Invalid key cache entries on DROP (CASSANDRA-6525) + * Fix flapping RecoveryManagerTest (CASSANDRA-7084) + * Add missing iso8601 patterns for date strings (6973) Merged from 1.2: * Add Cloudstack snitch (CASSANDRA-7147) * Update system.peers correctly when relocating tokens (CASSANDRA-7126) diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index 3786ef59a5..4d334e7a17 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -373,6 +373,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean indexManager.invalidate(); CacheService.instance.invalidateRowCacheForCf(metadata.cfId); + CacheService.instance.invalidateKeyCacheForCf(metadata.cfId); } /** diff --git a/src/java/org/apache/cassandra/db/marshal/DateType.java b/src/java/org/apache/cassandra/db/marshal/DateType.java index f63b87800b..7003097dcb 100644 --- a/src/java/org/apache/cassandra/db/marshal/DateType.java +++ b/src/java/org/apache/cassandra/db/marshal/DateType.java @@ -53,43 +53,7 @@ public class DateType extends AbstractType if (source.isEmpty()) return ByteBufferUtil.EMPTY_BYTE_BUFFER; - return ByteBufferUtil.bytes(dateStringToTimestamp(source)); - } - - public static long dateStringToTimestamp(String source) throws MarshalException - { - long millis; - - if (source.toLowerCase().equals("now")) - { - millis = System.currentTimeMillis(); - } - // Milliseconds since epoch? - else if (source.matches("^-?\\d+$")) - { - try - { - millis = Long.parseLong(source); - } - catch (NumberFormatException e) - { - throw new MarshalException(String.format("unable to make long (for date) from: '%s'", source), e); - } - } - // Last chance, attempt to parse as date-time string - else - { - try - { - millis = DateUtils.parseDateStrictly(source, TimestampSerializer.iso8601Patterns).getTime(); - } - catch (ParseException e1) - { - throw new MarshalException(String.format("unable to coerce '%s' to a formatted date (long)", source), e1); - } - } - - return millis; + return ByteBufferUtil.bytes(TimestampSerializer.dateStringToTimestamp(source)); } @Override diff --git a/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java b/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java index 82f5ce9135..88dc211d57 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java @@ -25,6 +25,7 @@ import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.TimeUUIDSerializer; +import org.apache.cassandra.serializers.TimestampSerializer; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.UUIDGen; @@ -105,9 +106,10 @@ public class TimeUUIDType extends AbstractType if (uuid.version() != 1) throw new MarshalException("TimeUUID supports only version 1 UUIDs"); - } else + } + else { - idBytes = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(DateType.dateStringToTimestamp(source))); + idBytes = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(TimestampSerializer.dateStringToTimestamp(source))); } return idBytes; diff --git a/src/java/org/apache/cassandra/db/marshal/TimestampType.java b/src/java/org/apache/cassandra/db/marshal/TimestampType.java index cf1ea41714..69ead8e93b 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimestampType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimestampType.java @@ -18,9 +18,7 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; -import java.text.ParseException; import java.util.Date; -import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +27,6 @@ import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.TimestampSerializer; import org.apache.cassandra.utils.ByteBufferUtil; -import org.apache.commons.lang3.time.DateUtils; /** * Type for date-time values. @@ -44,8 +41,6 @@ public class TimestampType extends AbstractType public static final TimestampType instance = new TimestampType(); - private static final Pattern timestampPattern = Pattern.compile("^-?\\d+$"); - private TimestampType() {} // singleton public int compare(ByteBuffer o1, ByteBuffer o2) @@ -59,43 +54,7 @@ public class TimestampType extends AbstractType if (source.isEmpty()) return ByteBufferUtil.EMPTY_BYTE_BUFFER; - return ByteBufferUtil.bytes(dateStringToTimestamp(source)); - } - - public static long dateStringToTimestamp(String source) throws MarshalException - { - long millis; - - if (source.toLowerCase().equals("now")) - { - millis = System.currentTimeMillis(); - } - // Milliseconds since epoch? - else if (timestampPattern.matcher(source).matches()) - { - try - { - millis = Long.parseLong(source); - } - catch (NumberFormatException e) - { - throw new MarshalException(String.format("unable to make long (for date) from: '%s'", source), e); - } - } - // Last chance, attempt to parse as date-time string - else - { - try - { - millis = DateUtils.parseDateStrictly(source, TimestampSerializer.iso8601Patterns).getTime(); - } - catch (ParseException e1) - { - throw new MarshalException(String.format("unable to coerce '%s' to a formatted date (long)", source), e1); - } - } - - return millis; + return ByteBufferUtil.bytes(TimestampSerializer.dateStringToTimestamp(source)); } @Override diff --git a/src/java/org/apache/cassandra/db/marshal/UUIDType.java b/src/java/org/apache/cassandra/db/marshal/UUIDType.java index a083736053..216b0bc057 100644 --- a/src/java/org/apache/cassandra/db/marshal/UUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/UUIDType.java @@ -26,12 +26,11 @@ import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.UUIDSerializer; +import org.apache.cassandra.serializers.TimestampSerializer; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.UUIDGen; import org.apache.commons.lang3.time.DateUtils; -import static org.apache.cassandra.serializers.TimestampSerializer.iso8601Patterns; - /** * Compares UUIDs using the following criteria:
* - if count of supplied bytes is less than 16, compare counts
@@ -165,8 +164,6 @@ public class UUIDType extends AbstractType if (source.isEmpty()) return ByteBufferUtil.EMPTY_BYTE_BUFFER; - ByteBuffer idBytes = null; - // ffffffff-ffff-ffff-ffff-ffffffffff if (TimeUUIDType.regexPattern.matcher(source).matches()) { @@ -174,43 +171,22 @@ public class UUIDType extends AbstractType try { uuid = UUID.fromString(source); - idBytes = ByteBuffer.wrap(UUIDGen.decompose(uuid)); + return ByteBuffer.wrap(UUIDGen.decompose(uuid)); } catch (IllegalArgumentException e) { throw new MarshalException(String.format("unable to make UUID from '%s'", source), e); } - } else if (source.toLowerCase().equals("now")) - { - idBytes = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes()); - } - // Milliseconds since epoch? - else if (source.matches("^\\d+$")) - { - try - { - idBytes = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(Long.parseLong(source))); - } - catch (NumberFormatException e) - { - throw new MarshalException(String.format("unable to make version 1 UUID from '%s'", source), e); - } - } - // Last chance, attempt to parse as date-time string - else - { - try - { - long timestamp = DateUtils.parseDate(source, iso8601Patterns).getTime(); - idBytes = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(timestamp)); - } - catch (ParseException e1) - { - throw new MarshalException(String.format("unable to coerce '%s' to version 1 UUID", source), e1); - } } - return idBytes; + try + { + return ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(TimestampSerializer.dateStringToTimestamp(source))); + } + catch (MarshalException e) + { + throw new MarshalException(String.format("unable to make version 1 UUID from '%s'", source), e); + } } public CQL3Type asCQL3Type() diff --git a/src/java/org/apache/cassandra/serializers/TimestampSerializer.java b/src/java/org/apache/cassandra/serializers/TimestampSerializer.java index f2a40f1316..5cb958600b 100644 --- a/src/java/org/apache/cassandra/serializers/TimestampSerializer.java +++ b/src/java/org/apache/cassandra/serializers/TimestampSerializer.java @@ -21,30 +21,49 @@ import org.apache.cassandra.utils.ByteBufferUtil; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; +import java.text.ParseException; import java.util.Date; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.time.DateUtils; public class TimestampSerializer implements TypeSerializer { - public static final String[] iso8601Patterns = new String[] { + private static final String[] dateStringPatterns = new String[] { "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mmZ", - "yyyy-MM-dd HH:mm:ssZ", + "yyyy-MM-dd HH:mmX", + "yyyy-MM-dd HH:mmXX", + "yyyy-MM-dd HH:mmXXX", + "yyyy-MM-dd HH:mm:ssX", + "yyyy-MM-dd HH:mm:ssXX", + "yyyy-MM-dd HH:mm:ssXXX", "yyyy-MM-dd HH:mm:ss.SSS", - "yyyy-MM-dd HH:mm:ss.SSSZ", + "yyyy-MM-dd HH:mm:ss.SSSX", + "yyyy-MM-dd HH:mm:ss.SSSXX", + "yyyy-MM-dd HH:mm:ss.SSSXXX", "yyyy-MM-dd'T'HH:mm", - "yyyy-MM-dd'T'HH:mmZ", + "yyyy-MM-dd'T'HH:mmX", + "yyyy-MM-dd'T'HH:mmXX", + "yyyy-MM-dd'T'HH:mmXXX", "yyyy-MM-dd'T'HH:mm:ss", - "yyyy-MM-dd'T'HH:mm:ssZ", + "yyyy-MM-dd'T'HH:mm:ssX", + "yyyy-MM-dd'T'HH:mm:ssXX", + "yyyy-MM-dd'T'HH:mm:ssXXX", "yyyy-MM-dd'T'HH:mm:ss.SSS", - "yyyy-MM-dd'T'HH:mm:ss.SSSZ", + "yyyy-MM-dd'T'HH:mm:ss.SSSX", + "yyyy-MM-dd'T'HH:mm:ss.SSSXX", + "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", "yyyy-MM-dd", - "yyyy-MM-ddZ" + "yyyy-MM-ddX", + "yyyy-MM-ddXX", + "yyyy-MM-ddXXX" }; - static final String DEFAULT_FORMAT = iso8601Patterns[3]; + private static final String DEFAULT_FORMAT = dateStringPatterns[3]; + private static final Pattern timestampPattern = Pattern.compile("^-?\\d+$"); - static final ThreadLocal FORMATTER = new ThreadLocal() + private static final ThreadLocal FORMATTER = new ThreadLocal() { protected SimpleDateFormat initialValue() { @@ -64,6 +83,35 @@ public class TimestampSerializer implements TypeSerializer return value == null ? ByteBufferUtil.EMPTY_BYTE_BUFFER : ByteBufferUtil.bytes(value.getTime()); } + public static long dateStringToTimestamp(String source) throws MarshalException + { + if (source.equalsIgnoreCase("now")) + return System.currentTimeMillis(); + + // Milliseconds since epoch? + if (timestampPattern.matcher(source).matches()) + { + try + { + return Long.parseLong(source); + } + catch (NumberFormatException e) + { + throw new MarshalException(String.format("unable to make long (for date) from: '%s'", source), e); + } + } + + // Last chance, attempt to parse as date-time string + try + { + return DateUtils.parseDateStrictly(source, dateStringPatterns).getTime(); + } + catch (ParseException e1) + { + throw new MarshalException(String.format("unable to coerce '%s' to a formatted date (long)", source), e1); + } + } + public void validate(ByteBuffer bytes) throws MarshalException { if (bytes.remaining() != 8 && bytes.remaining() != 0) diff --git a/src/java/org/apache/cassandra/service/CacheService.java b/src/java/org/apache/cassandra/service/CacheService.java index 78f6d66ccb..f51a166555 100644 --- a/src/java/org/apache/cassandra/service/CacheService.java +++ b/src/java/org/apache/cassandra/service/CacheService.java @@ -283,6 +283,17 @@ public class CacheService implements CacheServiceMBean keyCache.clear(); } + public void invalidateKeyCacheForCf(UUID cfId) + { + Iterator keyCacheIterator = keyCache.getKeySet().iterator(); + while (keyCacheIterator.hasNext()) + { + KeyCacheKey key = keyCacheIterator.next(); + if (key.cfId.equals(cfId)) + keyCacheIterator.remove(); + } + } + public void invalidateRowCache() { rowCache.clear(); diff --git a/test/unit/org/apache/cassandra/db/RecoveryManagerTest.java b/test/unit/org/apache/cassandra/db/RecoveryManagerTest.java index e812dfb6e3..687df5a881 100644 --- a/test/unit/org/apache/cassandra/db/RecoveryManagerTest.java +++ b/test/unit/org/apache/cassandra/db/RecoveryManagerTest.java @@ -22,9 +22,11 @@ import java.io.IOException; import java.util.Date; import java.util.concurrent.TimeUnit; +import org.apache.cassandra.OrderedJUnit4ClassRunner; import org.apache.cassandra.Util; import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.db.commitlog.CommitLog; @@ -34,6 +36,7 @@ import static org.apache.cassandra.Util.column; import static org.apache.cassandra.db.KeyspaceTest.assertColumns; import static org.apache.cassandra.Util.cellname; +@RunWith(OrderedJUnit4ClassRunner.class) public class RecoveryManagerTest extends SchemaLoader { @Test diff --git a/test/unit/org/apache/cassandra/serializers/TimestampSerializerTest.java b/test/unit/org/apache/cassandra/serializers/TimestampSerializerTest.java new file mode 100644 index 0000000000..d991845da6 --- /dev/null +++ b/test/unit/org/apache/cassandra/serializers/TimestampSerializerTest.java @@ -0,0 +1,77 @@ +/** + * 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.serializers; + +import java.util.List; +import java.util.ArrayList; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; + +import org.apache.cassandra.serializers.MarshalException; +import org.apache.cassandra.serializers.TimestampSerializer; + +public class TimestampSerializerTest +{ + private String dates[] = new String[] + { + "2014-04-01", + "2014-04-01+0000", + "2014-04-01 20:30", + "2014-04-01 20:30:35", + "2014-04-01 20:30:35Z", + "2014-04-01 20:30+07", + "2014-04-01 20:30+0700", + "2014-04-01 20:30+07:00", + "2014-04-01 20:30:35+07", + "2014-04-01 20:30:35+0700", + "2014-04-01 20:30:35+07:00", + "2014-04-01 20:30:35.898", + "2014-04-01 20:30:35.898Z", + "2014-04-01 20:30:35.898+07", + "2014-04-01 20:30:35.898+0700", + "2014-04-01 20:30:35.898+07:00", + "2014-04-01T20:30", + "2014-04-01T20:30:25", + "2014-04-01T20:30:35Z", + "2014-04-01T20:30:35+00:00", + "2014-04-01T20:30:35+0700", + "2014-04-01T20:30:35+07:00", + "2014-04-01T20:30:35.898", + "2014-04-01T20:30:35.898+00:00" + }; + + @Test + public void testDateStringToTimestamp() + { + List unparsedDates = new ArrayList<>(); + for (String date: dates) + { + try + { + long millis = TimestampSerializer.dateStringToTimestamp(date); + } + catch (MarshalException e) + { + unparsedDates.add(date); + } + } + assertTrue("Unable to parse: " + unparsedDates, unparsedDates.isEmpty()); + } +}