From 9c16c6f7365fd50caa1ba359974dde69d5c986dd Mon Sep 17 00:00:00 2001 From: Olivia Ytterbrink Date: Thu, 1 Mar 2018 15:28:38 +0100 Subject: [PATCH] Add support for local datetime to temporal index --- .../index/schema/LocalDateTimeLayout.java | 167 ++++++++++++++++++ .../index/schema/LocalDateTimeSchemaKey.java | 151 ++++++++++++++++ .../index/schema/TemporalIndexAccessor.java | 2 +- .../impl/index/schema/TemporalIndexFiles.java | 12 +- .../TemporalIndexPopulatingUpdater.java | 2 +- .../index/schema/TemporalIndexPopulator.java | 2 +- .../index/schema/TemporalIndexReader.java | 2 +- .../index/schema/TemporalIndexUpdater.java | 2 +- .../IndexProviderCompatibilityTestSuite.java | 10 +- 9 files changed, 339 insertions(+), 11 deletions(-) create mode 100644 community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/LocalDateTimeLayout.java create mode 100644 community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/LocalDateTimeSchemaKey.java diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/LocalDateTimeLayout.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/LocalDateTimeLayout.java new file mode 100644 index 0000000000000..240142d045c0e --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/LocalDateTimeLayout.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.index.schema; + +import org.neo4j.index.internal.gbptree.Layout; +import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.kernel.api.schema.index.IndexDescriptor; + +/** + * {@link Layout} for local date times. + */ +abstract class LocalDateTimeLayout extends Layout.Adapter +{ + public static Layout of( IndexDescriptor descriptor ) + { + return descriptor.type() == IndexDescriptor.Type.UNIQUE ? LocalDateTimeLayout.UNIQUE : LocalDateTimeLayout.NON_UNIQUE; + } + + private static final long UNIQUE_LAYOUT_IDENTIFIER = Layout.namedIdentifier( "UTld", NativeSchemaValue.SIZE ); + public static LocalDateTimeLayout UNIQUE = new LocalDateTimeLayout() + { + @Override + public long identifier() + { + return UNIQUE_LAYOUT_IDENTIFIER; + } + + @Override + public int majorVersion() + { + return 0; + } + + @Override + public int minorVersion() + { + return 1; + } + + @Override + public int compare( LocalDateTimeSchemaKey o1, LocalDateTimeSchemaKey o2 ) + { + int comparison = o1.compareValueTo( o2 ); + if ( comparison == 0 ) + { + // This is a special case where we need also compare entityId to support inclusive/exclusive + if ( o1.getCompareId() || o2.getCompareId() ) + { + return Long.compare( o1.getEntityId(), o2.getEntityId() ); + } + } + return comparison; + } + }; + + private static final long NON_UNIQUE_LAYOUT_IDENTIFIER = Layout.namedIdentifier( "NTld", NativeSchemaValue.SIZE ); + public static LocalDateTimeLayout NON_UNIQUE = new LocalDateTimeLayout() + { + @Override + public long identifier() + { + return NON_UNIQUE_LAYOUT_IDENTIFIER; + } + + @Override + public int majorVersion() + { + return 0; + } + + @Override + public int minorVersion() + { + return 1; + } + + @Override + public int compare( LocalDateTimeSchemaKey o1, LocalDateTimeSchemaKey o2 ) + { + int comparison = o1.compareValueTo( o2 ); + return comparison != 0 ? comparison : Long.compare( o1.getEntityId(), o2.getEntityId() ); + } + }; + + @Override + public LocalDateTimeSchemaKey newKey() + { + return new LocalDateTimeSchemaKey(); + } + + @Override + public LocalDateTimeSchemaKey copyKey( LocalDateTimeSchemaKey key, LocalDateTimeSchemaKey into ) + { + into.epochSecond = key.epochSecond; + into.nanoOfSecond = key.nanoOfSecond; + into.setEntityId( key.getEntityId() ); + into.setCompareId( key.getCompareId() ); + return into; + } + + @Override + public NativeSchemaValue newValue() + { + return NativeSchemaValue.INSTANCE; + } + + @Override + public int keySize( LocalDateTimeSchemaKey key ) + { + return LocalDateTimeSchemaKey.SIZE; + } + + @Override + public int valueSize( NativeSchemaValue value ) + { + return NativeSchemaValue.SIZE; + } + + @Override + public void writeKey( PageCursor cursor, LocalDateTimeSchemaKey key ) + { + cursor.putLong( key.epochSecond ); + cursor.putInt( key.nanoOfSecond ); + cursor.putLong( key.getEntityId() ); + } + + @Override + public void writeValue( PageCursor cursor, NativeSchemaValue value ) + { + } + + @Override + public void readKey( PageCursor cursor, LocalDateTimeSchemaKey into, int keySize ) + { + into.epochSecond = cursor.getLong(); + into.nanoOfSecond = cursor.getInt(); + into.setEntityId( cursor.getLong() ); + } + + @Override + public void readValue( PageCursor cursor, NativeSchemaValue into, int valueSize ) + { + } + + @Override + public boolean fixedSize() + { + return true; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/LocalDateTimeSchemaKey.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/LocalDateTimeSchemaKey.java new file mode 100644 index 0000000000000..bea9129468272 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/LocalDateTimeSchemaKey.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.index.schema; + +import org.neo4j.values.storable.LocalDateTimeValue; +import org.neo4j.values.storable.Value; +import org.neo4j.values.storable.ValueWriter; + +import static java.lang.String.format; + +/** + * Includes value and entity id (to be able to handle non-unique values). A value can be any {@link LocalDateTimeValue}. + */ +class LocalDateTimeSchemaKey extends ValueWriter.Adapter implements NativeSchemaKey +{ + static final int SIZE = + Long.BYTES + /* epochSecond */ + Integer.BYTES + /* nanoOfSecond */ + Long.BYTES; /* entityId */ + + private long entityId; + private boolean compareId; + + int nanoOfSecond; + long epochSecond; + + public void setCompareId( boolean compareId ) + { + this.compareId = compareId; + } + + public boolean getCompareId() + { + return compareId; + } + + @Override + public long getEntityId() + { + return entityId; + } + + @Override + public void setEntityId( long entityId ) + { + this.entityId = entityId; + } + + @Override + public void from( long entityId, Value... values ) + { + this.entityId = entityId; + compareId = false; + assertValidValue( values ).writeTo( this ); + } + + private LocalDateTimeValue assertValidValue( Value... values ) + { + if ( values.length > 1 ) + { + throw new IllegalArgumentException( "Tried to create composite key with non-composite schema key layout" ); + } + if ( values.length < 1 ) + { + throw new IllegalArgumentException( "Tried to create key without value" ); + } + if ( !(values[0] instanceof LocalDateTimeValue) ) + { + throw new IllegalArgumentException( + "Key layout does only support LocalDateTimeValue, tried to create key from " + values[0] ); + } + return (LocalDateTimeValue) values[0]; + } + + @Override + public String propertiesAsString() + { + return asValue().toString(); + } + + @Override + public Value asValue() + { + return LocalDateTimeValue.localDateTime( epochSecond, nanoOfSecond ); + } + + @Override + public void initAsLowest() + { + epochSecond = Long.MIN_VALUE; + nanoOfSecond = Integer.MIN_VALUE; + entityId = Long.MIN_VALUE; + compareId = true; + } + + @Override + public void initAsHighest() + { + epochSecond = Long.MAX_VALUE; + nanoOfSecond = Integer.MAX_VALUE; + entityId = Long.MAX_VALUE; + compareId = true; + } + + /** + * Compares the value of this key to that of another key. + * This method is expected to be called in scenarios where inconsistent reads may happen (and later retried). + * + * @param other the {@link LocalDateTimeSchemaKey} to compare to. + * @return comparison against the {@code other} {@link LocalDateTimeSchemaKey}. + */ + int compareValueTo( LocalDateTimeSchemaKey other ) + { + int compare = Long.compare( epochSecond, other.epochSecond ); + if ( compare == 0 ) + { + compare = Integer.compare( nanoOfSecond, other.nanoOfSecond ); + } + return compare; + } + + @Override + public String toString() + { + return format( "value=%s,entityId=%d,nanoOfDay=%s", asValue(), entityId, nanoOfSecond ); + } + + @Override + public void writeLocalDateTime( long epochSecond, int nano ) + { + this.nanoOfSecond = nano; + this.epochSecond = epochSecond; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexAccessor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexAccessor.java index 211762aa7e2f3..1184ae9d3e598 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexAccessor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexAccessor.java @@ -236,7 +236,7 @@ public PartAccessor newDate() throws IOException @Override public PartAccessor newDateTime() { - throw new UnsupportedOperationException( "no comprende" ); + return createPartAccessor( temporalIndexFiles.dateTime() ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexFiles.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexFiles.java index 40bb26e6dd1e8..75010080d3e05 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexFiles.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexFiles.java @@ -33,9 +33,9 @@ class TemporalIndexFiles { private final FileSystemAbstraction fs; private FileLayout dateFileLayout; - private FileLayout dateTimeFileLayout; + private FileLayout dateTimeFileLayout; private FileLayout dateTimeZonedFileLayout; - private FileLayout timeFileLayout; + private FileLayout timeFileLayout; private FileLayout timeZonedFileLayout; private FileLayout durationFileLayout; @@ -45,6 +45,7 @@ class TemporalIndexFiles File indexDirectory = directoryStructure.directoryForIndex( indexId ); dateFileLayout = new FileLayout<>( new File( indexDirectory, "date" ), DateLayout.of( descriptor ), ValueGroup.DATE ); timeFileLayout = new FileLayout<>( new File( indexDirectory, "time" ), LocalTimeLayout.of( descriptor ), ValueGroup.LOCAL_TIME ); + dateTimeFileLayout = new FileLayout<>( new File( indexDirectory, "datetime" ), LocalDateTimeLayout.of( descriptor ), ValueGroup.LOCAL_DATE_TIME ); } Iterable existing() @@ -85,11 +86,16 @@ public FileLayout date() return dateFileLayout; } - public FileLayout time() + public FileLayout time() { return timeFileLayout; } + public FileLayout dateTime() + { + return dateTimeFileLayout; + } + // .... we will add more explicit accessor methods later static class FileLayout diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulatingUpdater.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulatingUpdater.java index 6d4298570c76b..d63e101d50c87 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulatingUpdater.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulatingUpdater.java @@ -98,7 +98,7 @@ public IndexUpdater newDate() throws IOException @Override public IndexUpdater newDateTime() throws IOException { - throw new UnsupportedOperationException( "too tired to write" ); + return populator.dateTime().newPopulatingUpdater( propertyAccessor ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulator.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulator.java index bc6758da52fe6..d3eef528e38d4 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulator.java @@ -250,7 +250,7 @@ public PartPopulator newDate() throws IOException @Override public PartPopulator newDateTime() throws IOException { - throw new UnsupportedOperationException( "not implementedur still" ); + return create( temporalIndexFiles.dateTime() ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexReader.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexReader.java index 7e0cb7cdd16ce..b3b9d64ca92e3 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexReader.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexReader.java @@ -145,7 +145,7 @@ public TemporalIndexPartReader newDate() throws IOException @Override public TemporalIndexPartReader newDateTime() { - throw new UnsupportedOperationException( "Illiterate" ); + return accessor.selectOrElse( ValueGroup.LOCAL_DATE_TIME, TemporalIndexAccessor.PartAccessor::newReader, null ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexUpdater.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexUpdater.java index 49d79b9f34562..c4db02458bf60 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexUpdater.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexUpdater.java @@ -91,7 +91,7 @@ public NativeSchemaIndexUpdater newDate() throws IOExcepti @Override public NativeSchemaIndexUpdater newDateTime() throws IOException { - throw new UnsupportedOperationException( "ma-a-da dayo" ); + return accessor.dateTime().newUpdater( mode ); } @Override diff --git a/community/kernel/src/test/java/org/neo4j/kernel/api/index/IndexProviderCompatibilityTestSuite.java b/community/kernel/src/test/java/org/neo4j/kernel/api/index/IndexProviderCompatibilityTestSuite.java index 46a54b1f48897..615ae1091e31b 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/api/index/IndexProviderCompatibilityTestSuite.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/api/index/IndexProviderCompatibilityTestSuite.java @@ -38,6 +38,7 @@ import org.neo4j.test.runner.ParameterizedSuiteRunner; import org.neo4j.values.storable.CoordinateReferenceSystem; import org.neo4j.values.storable.DateValue; +import org.neo4j.values.storable.LocalDateTimeValue; import org.neo4j.values.storable.LocalTimeValue; import org.neo4j.values.storable.Value; import org.neo4j.values.storable.Values; @@ -92,8 +93,10 @@ public Compatibility( IndexProviderCompatibilityTestSuite testSuite, IndexDescri testSuite.supportsSpatial(), testSuite.supportsTemporal(), Arrays.asList( Values.of( "string1" ), Values.of( 42 ) ), - Arrays.asList( DateValue.epochDate( 2 ), - LocalTimeValue.localTime( 100000 ) ), + Arrays.asList( + DateValue.epochDate( 2 ), + LocalTimeValue.localTime( 100000 ), + LocalDateTimeValue.localDateTime( 2018, 3, 1, 13, 50, 42, 1337 ) ), Arrays.asList( Values.pointValue( CoordinateReferenceSystem.Cartesian, 0, 0 ), Values.pointValue( CoordinateReferenceSystem.WGS84, 12.78, 56.7 ) ) ); @@ -103,7 +106,8 @@ public Compatibility( IndexProviderCompatibilityTestSuite testSuite, IndexDescri Arrays.asList( Values.of( "string2" ), Values.of( 1337 ) ), Arrays.asList( DateValue.epochDate( 42 ), - LocalTimeValue.localTime( 2000 ) ), + LocalTimeValue.localTime( 2000 ), + LocalDateTimeValue.localDateTime( 2018, 2, 28, 11, 5, 1, 42 ) ), Arrays.asList( Values.pointValue( CoordinateReferenceSystem.Cartesian, 10, 10 ), Values.pointValue( CoordinateReferenceSystem.WGS84, 87.21, 7.65 ) ) );