From 385603087bd9b3ae5c4fe95a4f1acb453323f2e4 Mon Sep 17 00:00:00 2001 From: Anton Persson Date: Tue, 3 Jul 2018 15:15:10 +0200 Subject: [PATCH] GenericKeyState co-locate methods with its helpers Only move methods around --- .../impl/index/schema/GenericKeyState.java | 1824 ++++++++--------- 1 file changed, 904 insertions(+), 920 deletions(-) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericKeyState.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericKeyState.java index 00d4698679ab5..88c9a112d6561 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericKeyState.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericKeyState.java @@ -103,6 +103,7 @@ class GenericKeyState extends TemporalValueWriterAdapter private long[] long3Array; private byte[][] byteArrayArray; + /* */ void clear() { type = null; @@ -116,6 +117,91 @@ void clear() currentArrayOffset = 0; } + void initializeToDummyValue() + { + type = Type.NUMBER; + long0 = 0; + long1 = 0; + inclusion = NEUTRAL; + } + + // todo is this simple lowest approach viable? + void initValueAsLowest( ValueGroup valueGroup ) + { + type = valueGroup == ValueGroup.UNKNOWN ? LOWEST_TYPE_BY_VALUE_GROUP : GenericLayout.TYPE_BY_GROUP[valueGroup.ordinal()]; + long0 = Long.MIN_VALUE; + long1 = Long.MIN_VALUE; + long2 = Long.MIN_VALUE; + long3 = Long.MIN_VALUE; + byteArray = null; + if ( type == Type.TEXT ) + { + long3 = FALSE; + } + inclusion = LOW; + } + + // todo is this simple highest approach viable? + void initValueAsHighest( ValueGroup valueGroup ) + { + type = valueGroup == ValueGroup.UNKNOWN ? HIGHEST_TYPE_BY_VALUE_GROUP : GenericLayout.TYPE_BY_GROUP[valueGroup.ordinal()]; + long0 = Long.MAX_VALUE; + long1 = Long.MAX_VALUE; + long2 = Long.MAX_VALUE; + long3 = Long.MAX_VALUE; + byteArray = null; + if ( type == Type.TEXT ) + { + long3 = TRUE; + } + inclusion = HIGH; + } + + void initAsPrefixLow( String prefix ) + { + clear(); + writeString( prefix ); + long2 = FALSE; + inclusion = LOW; + // Don't set ignoreLength = true here since the "low" a.k.a. left side of the range should care about length. + // This will make the prefix lower than those that matches the prefix (their length is >= that of the prefix) + } + + void initAsPrefixHigh( String prefix ) + { + clear(); + writeString( prefix ); + long2 = TRUE; + inclusion = HIGH; + } + /* */ + + /* */ + void copyFrom( GenericKeyState key ) + { + this.type = key.type; + this.long0 = key.long0; + this.long1 = key.long1; + this.long2 = key.long2; + this.long3 = key.long3; + this.copyByteArrayFromIfExists( key, (int) key.long0 ); + this.inclusion = key.inclusion; + } + + private void copyByteArrayFromIfExists( GenericKeyState key, int targetLength ) + { + if ( key.type == Type.TEXT ) + { + setBytesLength( targetLength ); + System.arraycopy( key.byteArray, 0, byteArray, 0, targetLength ); + } + else + { + byteArray = null; + } + } + /* */ + Value assertCorrectType( Value value ) { if ( Values.isGeometryValue( value ) || Values.isArrayValue( value ) ) @@ -125,6 +211,75 @@ Value assertCorrectType( Value value ) return value; } + void writeValue( Value value, NativeIndexKey.Inclusion inclusion ) + { + value.writeTo( this ); + this.inclusion = inclusion; + } + + int compareValueTo( GenericKeyState other ) + { + int typeComparison = GenericLayout.TYPE_COMPARATOR.compare( type, other.type ); + if ( typeComparison != 0 ) + { + return typeComparison; + } + + int valueComparison = internalCompareValueTo( other ); + if ( valueComparison != 0 ) + { + return valueComparison; + } + + return inclusion.compareTo( other.inclusion ); + } + + /* */ + int size() + { + return valueSize() + TYPE_ID_SIZE; + } + + private int valueSize() + { + // TODO copy-pasted from individual keys + // TODO also put this in Type enum + switch ( type ) + { + case ZONED_DATE_TIME: + return Long.BYTES + /* epochSecond */ + Integer.BYTES + /* nanoOfSecond */ + Integer.BYTES; /* timeZone */ + case LOCAL_DATE_TIME: + return Long.BYTES + /* epochSecond */ + Integer.BYTES; /* nanoOfSecond */ + case DATE: + return Long.BYTES; /* epochDay */ + case ZONED_TIME: + return Long.BYTES + /* nanosOfDayUTC */ + Integer.BYTES; /* zoneOffsetSeconds */ + case LOCAL_TIME: + return Long.BYTES; /* nanoOfDay */ + case DURATION: + return Long.BYTES + /* totalAvgSeconds */ + Integer.BYTES + /* nanosOfSecond */ + Long.BYTES + /* months */ + Long.BYTES; /* days */ + case TEXT: + return Short.SIZE + /* short field with bytesLength value */ + (int) long0; /* bytesLength */ + case BOOLEAN: + return Byte.BYTES; /* byte for this boolean value */ + case NUMBER: + return Byte.BYTES + /* type of value */ + Long.BYTES; /* raw value bits */ + default: + throw new IllegalArgumentException( "Unknown type " + type ); + } + } + /* */ + + /* */ Value asValue() { switch ( type ) @@ -239,56 +394,9 @@ private T[] populateValueArray( T[] array, ArrayElementValueFactory value } return array; } + /* */ - // todo is this simple lowest approach viable? - void initValueAsLowest( ValueGroup valueGroup ) - { - type = valueGroup == ValueGroup.UNKNOWN ? LOWEST_TYPE_BY_VALUE_GROUP : GenericLayout.TYPE_BY_GROUP[valueGroup.ordinal()]; - long0 = Long.MIN_VALUE; - long1 = Long.MIN_VALUE; - long2 = Long.MIN_VALUE; - long3 = Long.MIN_VALUE; - byteArray = null; - if ( type == Type.TEXT ) - { - long3 = FALSE; - } - inclusion = LOW; - } - - // todo is this simple highest approach viable? - void initValueAsHighest( ValueGroup valueGroup ) - { - type = valueGroup == ValueGroup.UNKNOWN ? HIGHEST_TYPE_BY_VALUE_GROUP : GenericLayout.TYPE_BY_GROUP[valueGroup.ordinal()]; - long0 = Long.MAX_VALUE; - long1 = Long.MAX_VALUE; - long2 = Long.MAX_VALUE; - long3 = Long.MAX_VALUE; - byteArray = null; - if ( type == Type.TEXT ) - { - long3 = TRUE; - } - inclusion = HIGH; - } - - int compareValueTo( GenericKeyState other ) - { - int typeComparison = GenericLayout.TYPE_COMPARATOR.compare( type, other.type ); - if ( typeComparison != 0 ) - { - return typeComparison; - } - - int valueComparison = internalCompareValueTo( other ); - if ( valueComparison != 0 ) - { - return valueComparison; - } - - return inclusion.compareTo( other.inclusion ); - } - + /* */ private int internalCompareValueTo( GenericKeyState that ) { switch ( type ) @@ -384,794 +492,789 @@ private int compareArrays( GenericKeyState that, ArrayElementComparator comparat return x == 0 ? this.arrayLength - that.arrayLength : x; } - private void copyByteArrayFromIfExists( GenericKeyState key, int targetLength ) - { - if ( key.type == Type.TEXT ) - { - setBytesLength( targetLength ); - System.arraycopy( key.byteArray, 0, byteArray, 0, targetLength ); - } - else - { - byteArray = null; - } - } - - private void setBytesLength( int length ) - { - if ( booleanOf( long1 ) || byteArray == null || byteArray.length < length ) - { - long1 = FALSE; - - // allocate a bit more than required so that there's a higher chance that this byte[] instance - // can be used for more keys than just this one - byteArray = new byte[length + length / 2]; - } - long0 = length; - } - - void initAsPrefixLow( String prefix ) + private static int compareNumber( + long this_long0, long this_long1, + long that_long0, long that_long1 ) { - clear(); - writeString( prefix ); - long2 = FALSE; - inclusion = LOW; - // Don't set ignoreLength = true here since the "low" a.k.a. left side of the range should care about length. - // This will make the prefix lower than those that matches the prefix (their length is >= that of the prefix) + return RawBits.compare( this_long0, (byte) this_long1, that_long0, (byte) that_long1 ); } - void initAsPrefixHigh( String prefix ) + private static int compareBoolean( + long this_long0, + long that_long0 ) { - clear(); - writeString( prefix ); - long2 = TRUE; - inclusion = HIGH; + return Long.compare( this_long0, that_long0 ); } - @Override - protected void writeDate( long epochDay ) throws RuntimeException + private static int compareText( + byte[] this_byteArray, long this_long0, long this_long2, long this_long3, + byte[] that_byteArray, long that_long0, long that_long2, long that_long3 ) { - if ( !isArray ) + if ( this_byteArray != that_byteArray ) { - type = Type.DATE; - long0 = epochDay; + if ( this_byteArray == null ) + { + return isHighestText( this_long3 ) ? 1 : -1; + } + if ( that_byteArray == null ) + { + return isHighestText( that_long3 ) ? -1 : 1; + } } else { - long0Array[currentArrayOffset] = epochDay; - currentArrayOffset++; + return 0; } + + return unsignedByteArrayCompare( this_byteArray, (int) this_long0, that_byteArray, (int) that_long0, booleanOf( this_long2 ) | booleanOf( that_long2 ) ); } - @Override - protected void writeLocalTime( long nanoOfDay ) throws RuntimeException + private static int compareZonedDateTime( + long this_long0, long this_long1, long this_long2, long this_long3, + long that_long0, long that_long1, long that_long2, long that_long3 ) { - if ( !isArray ) - { - type = Type.LOCAL_TIME; - long0 = nanoOfDay; - } - else + int compare = Long.compare( this_long0, that_long0 ); + if ( compare == 0 ) { - long0Array[currentArrayOffset] = nanoOfDay; - currentArrayOffset++; + compare = Integer.compare( (int) this_long1, (int) that_long1 ); + if ( compare == 0 && + // We need to check validity upfront without throwing exceptions, because the PageCursor might give garbage bytes + TimeZones.validZoneOffset( (int) this_long3 ) && + TimeZones.validZoneOffset( (int) that_long3 ) ) + { + // In the rare case of comparing the same instant in different time zones, we settle for + // mapping to values and comparing using the general values comparator. + compare = Values.COMPARATOR.compare( + zonedDateTimeAsValue( this_long0, this_long1, this_long2, this_long3 ), + zonedDateTimeAsValue( that_long0, that_long1, that_long2, that_long3 ) ); + } } + return compare; } - @Override - protected void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException + private static int compareLocalDateTime( + long this_long0, long this_long1, + long that_long0, long that_long1 ) { - if ( !isArray ) - { - type = Type.ZONED_TIME; - long0 = nanosOfDayUTC; - long1 = offsetSeconds; - } - else + int compare = Long.compare( this_long1, that_long1 ); + if ( compare == 0 ) { - long0Array[currentArrayOffset] = nanosOfDayUTC; - long1Array[currentArrayOffset] = offsetSeconds; - currentArrayOffset++; + compare = Integer.compare( (int) this_long0, (int) that_long0 ); } + return compare; } - @Override - protected void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException + private static int compareDate( + long this_long0, + long that_long0 ) { - if ( !isArray ) - { - type = Type.LOCAL_DATE_TIME; - long0 = nano; - long1 = epochSecond; - } - else - { - long0Array[currentArrayOffset] = nano; - long1Array[currentArrayOffset] = epochSecond; - currentArrayOffset++; - } + return Long.compare( this_long0, that_long0 ); } - @Override - protected void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException + private static int compareZonedTime( + long this_long0, long this_long1, + long that_long0, long that_long1 ) { - if ( !isArray ) - { - type = Type.ZONED_DATE_TIME; - long0 = epochSecondUTC; - long1 = nano; - long2 = -1; - long3 = offsetSeconds; - } - else + int compare = Long.compare( this_long0, that_long0 ); + if ( compare == 0 ) { - long0Array[currentArrayOffset] = epochSecondUTC; - long1Array[currentArrayOffset] = nano; - long2Array[currentArrayOffset] = -1; - long3Array[currentArrayOffset] = offsetSeconds; - currentArrayOffset++; + compare = Integer.compare( (int) this_long1, (int) that_long1 ); } + return compare; } - @Override - protected void writeDateTime( long epochSecondUTC, int nano, String zoneId ) + private static int compareLocalTime( + long this_long0, + long that_long0 ) { - writeDateTime( epochSecondUTC, nano, TimeZones.map( zoneId ) ); + return Long.compare( this_long0, that_long0 ); } - protected void writeDateTime( long epochSecondUTC, int nano, short zoneId ) throws RuntimeException + private static int compareDuration( + long this_long0, long this_long1, long this_long2, long this_long3, + long that_long0, long that_long1, long that_long2, long that_long3 ) { - if ( !isArray ) + int comparison = Long.compare( this_long0, that_long0 ); + if ( comparison == 0 ) { - type = Type.ZONED_DATE_TIME; - long0 = epochSecondUTC; - long1 = nano; - long2 = zoneId; - long3 = 0; + comparison = Integer.compare( (int) this_long1, (int) that_long1 ); + if ( comparison == 0 ) + { + comparison = Long.compare( this_long2, that_long2 ); + if ( comparison == 0 ) + { + comparison = Long.compare( this_long3, that_long3 ); + } + } } - else - { - long0Array[currentArrayOffset] = epochSecondUTC; - long1Array[currentArrayOffset] = nano; - long2Array[currentArrayOffset] = zoneId; - long3Array[currentArrayOffset] = 0; - currentArrayOffset++; - } } + return comparison; + } + /* */ - @Override - public void writeBoolean( boolean value ) throws RuntimeException + /* */ + void put( PageCursor cursor ) { - if ( !isArray ) - { - type = Type.BOOLEAN; - long0 = value ? TRUE : FALSE; - } - else + cursor.putByte( type.typeId ); + switch ( type ) { - long0Array[currentArrayOffset] = value ? TRUE : FALSE; - currentArrayOffset++; + case ZONED_DATE_TIME: + putZonedDateTime( cursor, long0, long1, long2, long3 ); + break; + case LOCAL_DATE_TIME: + putLocalDateTime( cursor, long0, long1 ); + break; + case DATE: + putDate( cursor, long0 ); + break; + case ZONED_TIME: + putZonedTime( cursor, long0, long1 ); + break; + case LOCAL_TIME: + putLocalTime( cursor, long0 ); + break; + case DURATION: + putDuration( cursor, long0, long1, long2, long3 ); + break; + case TEXT: + putText( cursor, byteArray, long0 ); + break; + case BOOLEAN: + putBoolean( cursor, long0 ); + break; + case NUMBER: + putNumber( cursor, long0, long1 ); + break; + case ZONED_DATE_TIME_ARRAY: + putArray( cursor, ( c, i ) -> putZonedDateTime( c, long0Array[i], long1Array[i], long2Array[i], long3Array[i] ) ); + break; + case LOCAL_DATE_TIME_ARRAY: + putArray( cursor, ( c, i ) -> putLocalDateTime( c, long0Array[i], long1Array[i] ) ); + break; + case DATE_ARRAY: + putArray( cursor, ( c, i ) -> putDate( c, long0Array[i] ) ); + break; + case ZONED_TIME_ARRAY: + putArray( cursor, ( c, i ) -> putZonedTime( c, long0Array[i], long1Array[i] ) ); + break; + case LOCAL_TIME_ARRAY: + putArray( cursor, ( c, i ) -> putLocalTime( c, long0Array[i] ) ); + break; + case DURATION_ARRAY: + putArray( cursor, ( c, i ) -> putDuration( c, long0Array[i], long1Array[i], long2Array[i], long3Array[i] ) ); + break; + case TEXT_ARRAY: + putArray( cursor, ( c, i ) -> putText( c, byteArrayArray[i], long0Array[i] ) ); + break; + case BOOLEAN_ARRAY: + putArray( cursor, ( c, i ) -> putBoolean( c, long0Array[i] ) ); + break; + case NUMBER_ARRAY: + putArray( cursor, ( c, i ) -> putNumber( c, long0Array[i], long1Array[i] ) ); + break; + default: + throw new IllegalArgumentException( "Unknown type " + type ); } } - @Override - public void writeInteger( byte value ) + interface ArrayElementWriter { - if ( !isArray ) - { - type = Type.NUMBER; - long0 = value; - long1 = RawBits.BYTE; - } - else - { - long0Array[currentArrayOffset] = value; - currentArrayOffset++; - } + void write( PageCursor cursor, int i ); } - @Override - public void writeInteger( short value ) + private void putArray( PageCursor cursor, ArrayElementWriter writer ) { - if ( !isArray ) - { - type = Type.NUMBER; - long0 = value; - long1 = RawBits.SHORT; - } - else + cursor.putInt( arrayLength ); + for ( int i = 0; i < arrayLength; i++ ) { - long0Array[currentArrayOffset] = value; - currentArrayOffset++; + writer.write( cursor, i ); } } - @Override - public void writeInteger( int value ) + private static void putNumber( PageCursor cursor, long long0, long long1 ) { - if ( !isArray ) - { - type = Type.NUMBER; - long0 = value; - long1 = RawBits.INT; - } - else - { - long0Array[currentArrayOffset] = value; - currentArrayOffset++; - } + cursor.putByte( (byte) long1 ); + cursor.putLong( long0 ); } - @Override - public void writeInteger( long value ) + private static void putBoolean( PageCursor cursor, long long0 ) { - if ( !isArray ) - { - type = Type.NUMBER; - long0 = value; - long1 = RawBits.LONG; - } - else - { - long0Array[currentArrayOffset] = value; - currentArrayOffset++; - } + cursor.putByte( (byte) long0 ); } - @Override - public void writeFloatingPoint( float value ) - { - if ( !isArray ) - { - type = Type.NUMBER; - long0 = Float.floatToIntBits( value ); - long1 = RawBits.FLOAT; - } - else - { - long0Array[currentArrayOffset] = Float.floatToIntBits( value );; - currentArrayOffset++; - } - } - - @Override - public void writeFloatingPoint( double value ) + private static void putText( PageCursor cursor, byte[] byteArray, long long0 ) { - if ( !isArray ) - { - type = Type.NUMBER; - long0 = Double.doubleToLongBits( value ); - long1 = RawBits.DOUBLE; - } - else - { - long0Array[currentArrayOffset] = Double.doubleToLongBits( value ); - currentArrayOffset++; - } + // TODO short/int weird asymmetry ey? + cursor.putShort( (short) long0 ); + cursor.putBytes( byteArray, 0, (int) long0 ); } - @Override - public void writeString( String value ) throws RuntimeException + private static void putDuration( PageCursor cursor, long long0, long long1, long long2, long long3 ) { - byte[] encoded = UTF8.encode( value ); - if ( !isArray ) - { - type = Type.TEXT; - byteArray = encoded; - long0 = byteArray.length; - } - else - { - byteArrayArray[currentArrayOffset] = encoded; - long0Array[currentArrayOffset] = byteArray.length; - currentArrayOffset++; - } - long1 = FALSE; + cursor.putLong( long0 ); + cursor.putInt( (int) long1 ); + cursor.putLong( long2 ); + cursor.putLong( long3 ); } - @Override - public void writeString( char value ) throws RuntimeException + private static void putLocalTime( PageCursor cursor, long long0 ) { - writeString( String.valueOf( value ) ); + cursor.putLong( long0 ); } - @Override - public void writeDuration( long months, long days, long seconds, int nanos ) + private static void putZonedTime( PageCursor cursor, long long0, long long1 ) { - if ( !isArray ) - { - type = Type.DURATION; - long0 = months * AVG_MONTH_SECONDS + days * AVG_DAY_SECONDS + seconds; - long1 = nanos; - long2 = months; - long3 = days; - } - else - { - long0Array[currentArrayOffset] = months * AVG_MONTH_SECONDS + days * AVG_DAY_SECONDS + seconds; - long1Array[currentArrayOffset] = nanos; - long2Array[currentArrayOffset] = months; - long3Array[currentArrayOffset] = days; - currentArrayOffset++; - } + cursor.putLong( long0 ); + cursor.putInt( (int) long1 ); } - private static NumberValue numberAsValue( long long0, long long1 ) + private static void putDate( PageCursor cursor, long long0 ) { - // There's a difference between composing a single text value and a array text value - // and there's therefore no common "raw" variant of it - return RawBits.asNumberValue( long0, (byte) long1 ); + cursor.putLong( long0 ); } - private static BooleanValue booleanAsValue( long long0 ) + private static void putLocalDateTime( PageCursor cursor, long long0, long long1 ) { - return Values.booleanValue( booleanAsValueRaw( long0 ) ); + cursor.putLong( long1 ); + cursor.putInt( (int) long0 ); } - private static boolean booleanAsValueRaw( long long0 ) + private static void putZonedDateTime( PageCursor cursor, long long0, long long1, long long2, long long3 ) { - return booleanOf( long0 ); + cursor.putLong( long0 ); + cursor.putInt( (int) long1 ); + if ( long2 >= 0 ) + { + cursor.putInt( (int) long2 | ZONE_ID_FLAG ); + } + else + { + cursor.putInt( (int) long3 & ZONE_ID_MASK ); + } } + /* */ - private static Value textAsValue( byte[] byteArray, long long0 ) + /* */ + void read( PageCursor cursor, int size ) { - // There's a difference between composing a single text value and a array text value - // and there's therefore no common "raw" variant of it - return byteArray == null ? NO_VALUE : Values.utf8Value( byteArray, 0, (int) long0 ); - } + if ( size <= TYPE_ID_SIZE ) + { + initializeToDummyValue(); + return; + } - private String textAsValueRaw( byte[] byteArray, long long0 ) - { - return byteArray == null ? null : UTF8.decode( byteArray, 0, (int) long0 ); - } + byte typeId = cursor.getByte(); + if ( typeId < 0 || typeId >= GenericLayout.TYPES.length ) + { + initializeToDummyValue(); + return; + } - private static DurationValue durationAsValue( long long0, long long1, long long2, long long3 ) - { - // DurationValue has no "raw" variant - long seconds = long0 - long2 * AVG_MONTH_SECONDS - long3 * AVG_DAY_SECONDS; - return DurationValue.duration( long2, long3, seconds, long1 ); + size -= TYPE_ID_SIZE; + type = GenericLayout.TYPE_BY_ID[typeId]; + inclusion = NEUTRAL; + switch ( type ) + { + case ZONED_DATE_TIME: + readZonedDateTime( cursor ); + break; + case LOCAL_DATE_TIME: + readLocalDateTime( cursor ); + break; + case DATE: + readDate( cursor ); + break; + case ZONED_TIME: + readZonedTime( cursor ); + break; + case LOCAL_TIME: + readLocalTime( cursor ); + break; + case DURATION: + readDuration( cursor ); + break; + case TEXT: + readText( cursor, size ); + break; + case BOOLEAN: + readBoolean( cursor ); + break; + case NUMBER: + readNumber( cursor ); + break; + case ZONED_DATE_TIME_ARRAY: + readArray( cursor, ArrayType.ZONED_DATE_TIME, this::readZonedDateTime ); + break; + case LOCAL_DATE_TIME_ARRAY: + readArray( cursor, ArrayType.LOCAL_DATE_TIME, this::readLocalDateTime ); + break; + case DATE_ARRAY: + readArray( cursor, ArrayType.DATE, this::readDate ); + break; + case ZONED_TIME_ARRAY: + readArray( cursor, ArrayType.ZONED_DATE_TIME, this::readZonedDateTime ); + break; + case LOCAL_TIME_ARRAY: + readArray( cursor, ArrayType.LOCAL_TIME, this::readLocalTime ); + break; + case DURATION_ARRAY: + readArray( cursor, ArrayType.DURATION, this::readDuration ); + break; + case TEXT_ARRAY: + readTextArray( cursor, size ); + break; + case BOOLEAN_ARRAY: + readArray( cursor, ArrayType.BOOLEAN, this::readBoolean ); + break; + case NUMBER_ARRAY: + readNumberArray( cursor ); + break; + default: + throw new IllegalArgumentException( "Unknown type " + type ); + } } - private static LocalTimeValue localTimeAsValue( long long0 ) + private void readArray( PageCursor cursor, ArrayType type, ArrayElementReader reader ) { - return LocalTimeValue.localTime( localTimeAsValueRaw( long0 ) ); + if ( setArrayLengthWhenReading( cursor ) ) + { + beginArray( arrayLength, type ); + for ( int i = 0; i < arrayLength; i++ ) + { + reader.readFrom( cursor ); + } + endArray(); + } } - private static LocalTime localTimeAsValueRaw( long long0 ) + private void readNumberArray( PageCursor cursor ) { - return LocalTimeValue.localTimeRaw( long0 ); - } + if ( setArrayLengthWhenReading( cursor ) ) + { + long1 = cursor.getByte(); // number type, like: byte, int, short a.s.o. + initializeNumberArray( arrayLength ); + ArrayType numberType = numberArrayTypeOf( (byte) long1 ); + if ( numberType == null ) + { + initializeToDummyValue(); + return; + } - private static Value zonedTimeAsValue( long long0, long long1 ) - { - OffsetTime time = zonedTimeAsValueRaw( long0, long1 ); - return time != null ? TimeValue.time( time ) : NO_VALUE; + beginArray( arrayLength, numberType ); + for ( int i = 0; i < arrayLength; i++ ) + { + long0Array[i] = cursor.getLong(); + } + endArray(); + } } - private static OffsetTime zonedTimeAsValueRaw( long long0, long long1 ) + private ArrayType numberArrayTypeOf( byte numberType ) { - if ( TimeZones.validZoneOffset( (int) long1 ) ) + switch ( numberType ) { - return TimeValue.timeRaw( long0, ZoneOffset.ofTotalSeconds( (int) long1 ) ); + case RawBits.BYTE: + return ArrayType.BYTE; + case RawBits.SHORT: + return ArrayType.SHORT; + case RawBits.INT: + return ArrayType.INT; + case RawBits.LONG: + return ArrayType.LONG; + case RawBits.FLOAT: + return ArrayType.FLOAT; + case RawBits.DOUBLE: + return ArrayType.DOUBLE; } - // TODO Getting here means that after a proper read this value is plain wrong... shouldn't something be thrown instead? + // bad read, hopefully return null; } - private static DateValue dateAsValue( long long0 ) - { - return DateValue.date( dateAsValueRaw( long0 ) ); - } - - private static LocalDate dateAsValueRaw( long long0 ) + private void readTextArray( PageCursor cursor, int maxSize ) { - return DateValue.epochDateRaw( long0 ); - } - - private static LocalDateTimeValue localDateTimeAsValue( long long0, long long1 ) + if ( setArrayLengthWhenReading( cursor ) ) + { + initializeTextArray( arrayLength ); + beginArray( arrayLength, ArrayType.STRING ); + for ( int i = 0; i < arrayLength; i++ ) + { + short bytesLength = cursor.getShort(); + if ( bytesLength <= 0 || bytesLength > maxSize ) + { + initializeToDummyValue(); + return; + } + // long1 == text byte[] have been handed out to a UTF8StringValue and so must not be overwritten with next value + if ( booleanOf( long1 ) || byteArrayArray[i] == null || byteArrayArray[i].length < bytesLength ) + { + long1 = FALSE; + + // allocate a bit more than required so that there's a higher chance that this byte[] instance + // can be used for more keys than just this one + byteArrayArray[i] = new byte[bytesLength + bytesLength / 2]; + } + long0Array[i] = bytesLength; + cursor.getBytes( byteArrayArray[i], 0, bytesLength ); + } + endArray(); + } + } + + private boolean setArrayLengthWhenReading( PageCursor cursor ) { - return LocalDateTimeValue.localDateTime( localDateTimeAsValueRaw( long0, long1 ) ); + arrayLength = cursor.getInt(); + if ( arrayLength < 0 || arrayLength >= BIGGEST_REASONABLE_ARRAY_LENGTH ) + { + initializeToDummyValue(); + return false; + } + + isArray = true; + return true; } - private static LocalDateTime localDateTimeAsValueRaw( long long0, long long1 ) + private void readNumber( PageCursor cursor ) { - return LocalDateTimeValue.localDateTimeRaw( long1, long0 ); + long1 = cursor.getByte(); + long0 = cursor.getLong(); } - private static DateTimeValue zonedDateTimeAsValue( long long0, long long1, long long2, long long3 ) + private void readBoolean( PageCursor cursor ) { - return DateTimeValue.datetime( zonedDateTimeAsValueRaw( long0, long1, long2, long3 ) ); + writeBoolean( cursor.getByte() == TRUE ); } - private static ZonedDateTime zonedDateTimeAsValueRaw( long long0, long long1, long long2, long long3 ) + private void readText( PageCursor cursor, int maxSize ) { - return TimeZones.validZoneId( (short) long2 ) ? - DateTimeValue.datetimeRaw( long0, long1, ZoneId.of( TimeZones.map( (short) long2 ) ) ) : - DateTimeValue.datetimeRaw( long0, long1, ZoneOffset.ofTotalSeconds( (int) long3 ) ); + // For performance reasons cannot be redirected to writeString, due to byte[] reuse + short bytesLength = cursor.getShort(); + if ( bytesLength <= 0 || bytesLength > maxSize ) + { + initializeToDummyValue(); + return; + } + setBytesLength( bytesLength ); + cursor.getBytes( byteArray, 0, bytesLength ); } - private static boolean isHighestText( long long3 ) + private void readDuration( PageCursor cursor ) { - return long3 == TRUE; + // TODO unify order of fields + long totalAvgSeconds = cursor.getLong(); + int nanosOfSecond = cursor.getInt(); + long months = cursor.getLong(); + long days = cursor.getLong(); + writeDuration( months, days, totalAvgSeconds, nanosOfSecond ); } - private static boolean booleanOf( long longValue ) + private void readLocalTime( PageCursor cursor ) { - return longValue == TRUE; + writeLocalTime( cursor.getLong() ); } - private static int compareNumber( - long this_long0, long this_long1, - long that_long0, long that_long1 ) + private void readZonedTime( PageCursor cursor ) { - return RawBits.compare( this_long0, (byte) this_long1, that_long0, (byte) that_long1 ); + writeTime( cursor.getLong(), cursor.getInt() ); } - private static int compareBoolean( - long this_long0, - long that_long0 ) + private void readDate( PageCursor cursor ) { - return Long.compare( this_long0, that_long0 ); + writeDate( cursor.getLong() ); } - private static int compareText( - byte[] this_byteArray, long this_long0, long this_long2, long this_long3, - byte[] that_byteArray, long that_long0, long that_long2, long that_long3 ) + private void readLocalDateTime( PageCursor cursor ) { - if ( this_byteArray != that_byteArray ) + writeLocalDateTime( cursor.getLong(), cursor.getInt() ); + } + + private void readZonedDateTime( PageCursor cursor ) + { + long epochSecondUTC = cursor.getLong(); + int nanoOfSecond = cursor.getInt(); + int encodedZone = cursor.getInt(); + if ( isZoneId( encodedZone ) ) { - if ( this_byteArray == null ) - { - return isHighestText( this_long3 ) ? 1 : -1; - } - if ( that_byteArray == null ) - { - return isHighestText( that_long3 ) ? -1 : 1; - } + writeDateTime( epochSecondUTC, nanoOfSecond, asZoneId( encodedZone ) ); } else { - return 0; + writeDateTime( epochSecondUTC, nanoOfSecond, asZoneOffset( encodedZone ) ); } - - return unsignedByteArrayCompare( this_byteArray, (int) this_long0, that_byteArray, (int) that_long0, booleanOf( this_long2 ) | booleanOf( that_long2 ) ); } + /* */ - private static int compareZonedDateTime( - long this_long0, long this_long1, long this_long2, long this_long3, - long that_long0, long that_long1, long that_long2, long that_long3 ) + /* (write to field state from Value or cursor) */ + @Override + protected void writeDate( long epochDay ) throws RuntimeException { - int compare = Long.compare( this_long0, that_long0 ); - if ( compare == 0 ) + if ( !isArray ) { - compare = Integer.compare( (int) this_long1, (int) that_long1 ); - if ( compare == 0 && - // We need to check validity upfront without throwing exceptions, because the PageCursor might give garbage bytes - TimeZones.validZoneOffset( (int) this_long3 ) && - TimeZones.validZoneOffset( (int) that_long3 ) ) - { - // In the rare case of comparing the same instant in different time zones, we settle for - // mapping to values and comparing using the general values comparator. - compare = Values.COMPARATOR.compare( - zonedDateTimeAsValue( this_long0, this_long1, this_long2, this_long3 ), - zonedDateTimeAsValue( that_long0, that_long1, that_long2, that_long3 ) ); - } + type = Type.DATE; + long0 = epochDay; } - return compare; - } - - private static int compareLocalDateTime( - long this_long0, long this_long1, - long that_long0, long that_long1 ) - { - int compare = Long.compare( this_long1, that_long1 ); - if ( compare == 0 ) + else { - compare = Integer.compare( (int) this_long0, (int) that_long0 ); + long0Array[currentArrayOffset] = epochDay; + currentArrayOffset++; } - return compare; } - private static int compareDate( - long this_long0, - long that_long0 ) - { - return Long.compare( this_long0, that_long0 ); - } - - private static int compareZonedTime( - long this_long0, long this_long1, - long that_long0, long that_long1 ) + @Override + protected void writeLocalTime( long nanoOfDay ) throws RuntimeException { - int compare = Long.compare( this_long0, that_long0 ); - if ( compare == 0 ) + if ( !isArray ) { - compare = Integer.compare( (int) this_long1, (int) that_long1 ); + type = Type.LOCAL_TIME; + long0 = nanoOfDay; + } + else + { + long0Array[currentArrayOffset] = nanoOfDay; + currentArrayOffset++; } - return compare; } - private static int compareLocalTime( - long this_long0, - long that_long0 ) + @Override + protected void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException { - return Long.compare( this_long0, that_long0 ); + if ( !isArray ) + { + type = Type.ZONED_TIME; + long0 = nanosOfDayUTC; + long1 = offsetSeconds; + } + else + { + long0Array[currentArrayOffset] = nanosOfDayUTC; + long1Array[currentArrayOffset] = offsetSeconds; + currentArrayOffset++; + } } - private static int compareDuration( - long this_long0, long this_long1, long this_long2, long this_long3, - long that_long0, long that_long1, long that_long2, long that_long3 ) + @Override + protected void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException { - int comparison = Long.compare( this_long0, that_long0 ); - if ( comparison == 0 ) + if ( !isArray ) { - comparison = Integer.compare( (int) this_long1, (int) that_long1 ); - if ( comparison == 0 ) - { - comparison = Long.compare( this_long2, that_long2 ); - if ( comparison == 0 ) - { - comparison = Long.compare( this_long3, that_long3 ); - } - } + type = Type.LOCAL_DATE_TIME; + long0 = nano; + long1 = epochSecond; + } + else + { + long0Array[currentArrayOffset] = nano; + long1Array[currentArrayOffset] = epochSecond; + currentArrayOffset++; } - return comparison; } - void copyFrom( GenericKeyState key ) + @Override + protected void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException { - this.type = key.type; - this.long0 = key.long0; - this.long1 = key.long1; - this.long2 = key.long2; - this.long3 = key.long3; - this.copyByteArrayFromIfExists( key, (int) key.long0 ); - this.inclusion = key.inclusion; + if ( !isArray ) + { + type = Type.ZONED_DATE_TIME; + long0 = epochSecondUTC; + long1 = nano; + long2 = -1; + long3 = offsetSeconds; + } + else + { + long0Array[currentArrayOffset] = epochSecondUTC; + long1Array[currentArrayOffset] = nano; + long2Array[currentArrayOffset] = -1; + long3Array[currentArrayOffset] = offsetSeconds; + currentArrayOffset++; + } } - int size() + @Override + protected void writeDateTime( long epochSecondUTC, int nano, String zoneId ) { - return valueSize() + TYPE_ID_SIZE; + writeDateTime( epochSecondUTC, nano, TimeZones.map( zoneId ) ); } - private int valueSize() + protected void writeDateTime( long epochSecondUTC, int nano, short zoneId ) throws RuntimeException { - // TODO copy-pasted from individual keys - // TODO also put this in Type enum - switch ( type ) + if ( !isArray ) { - case ZONED_DATE_TIME: - return Long.BYTES + /* epochSecond */ - Integer.BYTES + /* nanoOfSecond */ - Integer.BYTES; /* timeZone */ - case LOCAL_DATE_TIME: - return Long.BYTES + /* epochSecond */ - Integer.BYTES; /* nanoOfSecond */ - case DATE: - return Long.BYTES; /* epochDay */ - case ZONED_TIME: - return Long.BYTES + /* nanosOfDayUTC */ - Integer.BYTES; /* zoneOffsetSeconds */ - case LOCAL_TIME: - return Long.BYTES; /* nanoOfDay */ - case DURATION: - return Long.BYTES + /* totalAvgSeconds */ - Integer.BYTES + /* nanosOfSecond */ - Long.BYTES + /* months */ - Long.BYTES; /* days */ - case TEXT: - return Short.SIZE + /* short field with bytesLength value */ - (int) long0; /* bytesLength */ - case BOOLEAN: - return Byte.BYTES; /* byte for this boolean value */ - case NUMBER: - return Byte.BYTES + /* type of value */ - Long.BYTES; /* raw value bits */ - default: - throw new IllegalArgumentException( "Unknown type " + type ); + type = Type.ZONED_DATE_TIME; + long0 = epochSecondUTC; + long1 = nano; + long2 = zoneId; + long3 = 0; } - } + else + { + long0Array[currentArrayOffset] = epochSecondUTC; + long1Array[currentArrayOffset] = nano; + long2Array[currentArrayOffset] = zoneId; + long3Array[currentArrayOffset] = 0; + currentArrayOffset++; + } } - void put( PageCursor cursor ) + @Override + public void writeBoolean( boolean value ) throws RuntimeException { - cursor.putByte( type.typeId ); - switch ( type ) + if ( !isArray ) { - case ZONED_DATE_TIME: - putZonedDateTime( cursor, long0, long1, long2, long3 ); - break; - case LOCAL_DATE_TIME: - putLocalDateTime( cursor, long0, long1 ); - break; - case DATE: - putDate( cursor, long0 ); - break; - case ZONED_TIME: - putZonedTime( cursor, long0, long1 ); - break; - case LOCAL_TIME: - putLocalTime( cursor, long0 ); - break; - case DURATION: - putDuration( cursor, long0, long1, long2, long3 ); - break; - case TEXT: - putText( cursor, byteArray, long0 ); - break; - case BOOLEAN: - putBoolean( cursor, long0 ); - break; - case NUMBER: - putNumber( cursor, long0, long1 ); - break; - case ZONED_DATE_TIME_ARRAY: - putArray( cursor, ( c, i ) -> putZonedDateTime( c, long0Array[i], long1Array[i], long2Array[i], long3Array[i] ) ); - break; - case LOCAL_DATE_TIME_ARRAY: - putArray( cursor, ( c, i ) -> putLocalDateTime( c, long0Array[i], long1Array[i] ) ); - break; - case DATE_ARRAY: - putArray( cursor, ( c, i ) -> putDate( c, long0Array[i] ) ); - break; - case ZONED_TIME_ARRAY: - putArray( cursor, ( c, i ) -> putZonedTime( c, long0Array[i], long1Array[i] ) ); - break; - case LOCAL_TIME_ARRAY: - putArray( cursor, ( c, i ) -> putLocalTime( c, long0Array[i] ) ); - break; - case DURATION_ARRAY: - putArray( cursor, ( c, i ) -> putDuration( c, long0Array[i], long1Array[i], long2Array[i], long3Array[i] ) ); - break; - case TEXT_ARRAY: - putArray( cursor, ( c, i ) -> putText( c, byteArrayArray[i], long0Array[i] ) ); - break; - case BOOLEAN_ARRAY: - putArray( cursor, ( c, i ) -> putBoolean( c, long0Array[i] ) ); - break; - case NUMBER_ARRAY: - putArray( cursor, ( c, i ) -> putNumber( c, long0Array[i], long1Array[i] ) ); - break; - default: - throw new IllegalArgumentException( "Unknown type " + type ); + type = Type.BOOLEAN; + long0 = value ? TRUE : FALSE; } - } - - interface ArrayElementWriter - { - void write( PageCursor cursor, int i ); - } - - private void putArray( PageCursor cursor, ArrayElementWriter writer ) - { - cursor.putInt( arrayLength ); - for ( int i = 0; i < arrayLength; i++ ) + else { - writer.write( cursor, i ); + long0Array[currentArrayOffset] = value ? TRUE : FALSE; + currentArrayOffset++; } } - private static void putNumber( PageCursor cursor, long long0, long long1 ) + @Override + public void writeInteger( byte value ) { - cursor.putByte( (byte) long1 ); - cursor.putLong( long0 ); + if ( !isArray ) + { + type = Type.NUMBER; + long0 = value; + long1 = RawBits.BYTE; + } + else + { + long0Array[currentArrayOffset] = value; + currentArrayOffset++; + } } - private static void putBoolean( PageCursor cursor, long long0 ) + @Override + public void writeInteger( short value ) { - cursor.putByte( (byte) long0 ); + if ( !isArray ) + { + type = Type.NUMBER; + long0 = value; + long1 = RawBits.SHORT; + } + else + { + long0Array[currentArrayOffset] = value; + currentArrayOffset++; + } } - private static void putText( PageCursor cursor, byte[] byteArray, long long0 ) + @Override + public void writeInteger( int value ) { - // TODO short/int weird asymmetry ey? - cursor.putShort( (short) long0 ); - cursor.putBytes( byteArray, 0, (int) long0 ); + if ( !isArray ) + { + type = Type.NUMBER; + long0 = value; + long1 = RawBits.INT; + } + else + { + long0Array[currentArrayOffset] = value; + currentArrayOffset++; + } } - private static void putDuration( PageCursor cursor, long long0, long long1, long long2, long long3 ) + @Override + public void writeInteger( long value ) { - cursor.putLong( long0 ); - cursor.putInt( (int) long1 ); - cursor.putLong( long2 ); - cursor.putLong( long3 ); + if ( !isArray ) + { + type = Type.NUMBER; + long0 = value; + long1 = RawBits.LONG; + } + else + { + long0Array[currentArrayOffset] = value; + currentArrayOffset++; + } } - private static void putLocalTime( PageCursor cursor, long long0 ) + @Override + public void writeFloatingPoint( float value ) { - cursor.putLong( long0 ); + if ( !isArray ) + { + type = Type.NUMBER; + long0 = Float.floatToIntBits( value ); + long1 = RawBits.FLOAT; + } + else + { + long0Array[currentArrayOffset] = Float.floatToIntBits( value );; + currentArrayOffset++; + } } - private static void putZonedTime( PageCursor cursor, long long0, long long1 ) + @Override + public void writeFloatingPoint( double value ) { - cursor.putLong( long0 ); - cursor.putInt( (int) long1 ); + if ( !isArray ) + { + type = Type.NUMBER; + long0 = Double.doubleToLongBits( value ); + long1 = RawBits.DOUBLE; + } + else + { + long0Array[currentArrayOffset] = Double.doubleToLongBits( value ); + currentArrayOffset++; + } } - private static void putDate( PageCursor cursor, long long0 ) + @Override + public void writeString( String value ) throws RuntimeException { - cursor.putLong( long0 ); + byte[] encoded = UTF8.encode( value ); + if ( !isArray ) + { + type = Type.TEXT; + byteArray = encoded; + long0 = byteArray.length; + } + else + { + byteArrayArray[currentArrayOffset] = encoded; + long0Array[currentArrayOffset] = byteArray.length; + currentArrayOffset++; + } + long1 = FALSE; } - private static void putLocalDateTime( PageCursor cursor, long long0, long long1 ) + @Override + public void writeString( char value ) throws RuntimeException { - cursor.putLong( long1 ); - cursor.putInt( (int) long0 ); + writeString( String.valueOf( value ) ); } - private static void putZonedDateTime( PageCursor cursor, long long0, long long1, long long2, long long3 ) + @Override + public void writeDuration( long months, long days, long seconds, int nanos ) { - cursor.putLong( long0 ); - cursor.putInt( (int) long1 ); - if ( long2 >= 0 ) + if ( !isArray ) { - cursor.putInt( (int) long2 | ZONE_ID_FLAG ); + type = Type.DURATION; + long0 = months * AVG_MONTH_SECONDS + days * AVG_DAY_SECONDS + seconds; + long1 = nanos; + long2 = months; + long3 = days; } else { - cursor.putInt( (int) long3 & ZONE_ID_MASK ); + long0Array[currentArrayOffset] = months * AVG_MONTH_SECONDS + days * AVG_DAY_SECONDS + seconds; + long1Array[currentArrayOffset] = nanos; + long2Array[currentArrayOffset] = months; + long3Array[currentArrayOffset] = days; + currentArrayOffset++; } } + /* */ @Override public void beginArray( int size, ArrayType arrayType ) throws RuntimeException { - switch ( arrayType ) - { - case BYTE: - type = Type.NUMBER_ARRAY; - long1 = RawBits.BYTE; - break; - case SHORT: - type = Type.NUMBER_ARRAY; - long1 = RawBits.SHORT; - break; - case INT: - type = Type.NUMBER_ARRAY; - long1 = RawBits.INT; - break; - case LONG: - type = Type.NUMBER_ARRAY; - long1 = RawBits.LONG; - break; - case FLOAT: - type = Type.NUMBER_ARRAY; - long1 = RawBits.FLOAT; - break; - case DOUBLE: - type = Type.NUMBER_ARRAY; - long1 = RawBits.DOUBLE; - break; - case BOOLEAN: - type = Type.BOOLEAN_ARRAY; - break; - case STRING: - type = Type.TEXT_ARRAY; - break; - case CHAR: - type = Type.TEXT_ARRAY; - break; - case POINT: - throw new UnsupportedOperationException( "Not implemented yet" ); - case ZONED_DATE_TIME: - type = Type.ZONED_DATE_TIME_ARRAY; - break; - case LOCAL_DATE_TIME: - type = Type.LOCAL_DATE_TIME_ARRAY; - break; - case DATE: - type = Type.DATE_ARRAY; - break; - case ZONED_TIME: - type = Type.ZONED_TIME_ARRAY; - break; - case LOCAL_TIME: - type = Type.LOCAL_TIME_ARRAY; - break; - case DURATION: - type = Type.DURATION_ARRAY; - break; - default: - throw new IllegalArgumentException( "Unknown array type " + arrayType ); - } + initializeTypeFromArrayType( arrayType ); isArray = true; arrayLength = size; currentArrayOffset = 0; @@ -1207,378 +1310,258 @@ public void beginArray( int size, ArrayType arrayType ) throws RuntimeException } } - private void initializeNumberArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - // plain long1 for number type - } - - private void initializeBooleanArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - } - - private void initializeTextArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - // plain long1 for bytesDereferenced - byteArrayArray = ensureBigEnough( byteArrayArray, size ); - } - - private void initializeDurationArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - long1Array = ensureBigEnough( long1Array, size ); - long2Array = ensureBigEnough( long2Array, size ); - long3Array = ensureBigEnough( long3Array, size ); - } - - private void initializeLocalTimeArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - } - - private void initializeZonedTimeArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - long1Array = ensureBigEnough( long1Array, size ); - } - - private void initializeDateArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - } - - private void initializeLocalDateTimeArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - long1Array = ensureBigEnough( long1Array, size ); - } - - private void initializeZonedDateTimeArray( int size ) - { - long0Array = ensureBigEnough( long0Array, size ); - long1Array = ensureBigEnough( long1Array, size ); - long2Array = ensureBigEnough( long2Array, size ); - long3Array = ensureBigEnough( long3Array, size ); - } - - private byte[][] ensureBigEnough( byte[][] array, int targetLength ) - { - return array == null || array.length < targetLength ? new byte[targetLength][] : array; - } - - private static long[] ensureBigEnough( long[] array, int targetLength ) - { - return array == null || array.length < targetLength ? new long[targetLength] : array; - } - @Override public void endArray() throws RuntimeException { // TODO do something here? } - private Type arrayType( ArrayType arrayType ) + private void initializeTypeFromArrayType( ArrayType arrayType ) { switch ( arrayType ) { case BYTE: - return Type.NUMBER_ARRAY; + type = Type.NUMBER_ARRAY; + long1 = RawBits.BYTE; + break; case SHORT: - return Type.NUMBER_ARRAY; + type = Type.NUMBER_ARRAY; + long1 = RawBits.SHORT; + break; case INT: - return Type.NUMBER_ARRAY; + type = Type.NUMBER_ARRAY; + long1 = RawBits.INT; + break; case LONG: - return Type.NUMBER_ARRAY; + type = Type.NUMBER_ARRAY; + long1 = RawBits.LONG; + break; case FLOAT: - return Type.NUMBER_ARRAY; + type = Type.NUMBER_ARRAY; + long1 = RawBits.FLOAT; + break; case DOUBLE: - return Type.NUMBER_ARRAY; + type = Type.NUMBER_ARRAY; + long1 = RawBits.DOUBLE; + break; case BOOLEAN: - return Type.BOOLEAN_ARRAY; + type = Type.BOOLEAN_ARRAY; + break; case STRING: - return Type.TEXT_ARRAY; + type = Type.TEXT_ARRAY; + break; case CHAR: - return Type.TEXT_ARRAY; + type = Type.TEXT_ARRAY; + break; case POINT: throw new UnsupportedOperationException( "Not implemented yet" ); case ZONED_DATE_TIME: - return Type.ZONED_DATE_TIME_ARRAY; - case LOCAL_DATE_TIME: - return Type.LOCAL_DATE_TIME_ARRAY; - case DATE: - return Type.DATE_ARRAY; - case ZONED_TIME: - return Type.ZONED_TIME_ARRAY; - case LOCAL_TIME: - return Type.LOCAL_TIME_ARRAY; - case DURATION: - return Type.DURATION_ARRAY; - default: - throw new IllegalArgumentException( "Unknown array type " + arrayType ); - } - } - - void read( PageCursor cursor, int size ) - { - if ( size <= TYPE_ID_SIZE ) - { - initializeToDummyValue(); - return; - } - - byte typeId = cursor.getByte(); - if ( typeId < 0 || typeId >= GenericLayout.TYPES.length ) - { - initializeToDummyValue(); - return; - } - - size -= TYPE_ID_SIZE; - type = GenericLayout.TYPE_BY_ID[typeId]; - inclusion = NEUTRAL; - switch ( type ) - { - case ZONED_DATE_TIME: - readZonedDateTime( cursor ); + type = Type.ZONED_DATE_TIME_ARRAY; break; case LOCAL_DATE_TIME: - readLocalDateTime( cursor ); + type = Type.LOCAL_DATE_TIME_ARRAY; break; case DATE: - readDate( cursor ); + type = Type.DATE_ARRAY; break; case ZONED_TIME: - readZonedTime( cursor ); + type = Type.ZONED_TIME_ARRAY; break; case LOCAL_TIME: - readLocalTime( cursor ); - break; - case DURATION: - readDuration( cursor ); - break; - case TEXT: - readText( cursor, size ); - break; - case BOOLEAN: - readBoolean( cursor ); - break; - case NUMBER: - readNumber( cursor ); - break; - case ZONED_DATE_TIME_ARRAY: - readArray( cursor, ArrayType.ZONED_DATE_TIME, this::readZonedDateTime ); - break; - case LOCAL_DATE_TIME_ARRAY: - readArray( cursor, ArrayType.LOCAL_DATE_TIME, this::readLocalDateTime ); - break; - case DATE_ARRAY: - readArray( cursor, ArrayType.DATE, this::readDate ); - break; - case ZONED_TIME_ARRAY: - readArray( cursor, ArrayType.ZONED_DATE_TIME, this::readZonedDateTime ); - break; - case LOCAL_TIME_ARRAY: - readArray( cursor, ArrayType.LOCAL_TIME, this::readLocalTime ); - break; - case DURATION_ARRAY: - readArray( cursor, ArrayType.DURATION, this::readDuration ); - break; - case TEXT_ARRAY: - readTextArray( cursor, size ); - break; - case BOOLEAN_ARRAY: - readArray( cursor, ArrayType.BOOLEAN, this::readBoolean ); - break; - case NUMBER_ARRAY: - readNumberArray( cursor ); + type = Type.LOCAL_TIME_ARRAY; + break; + case DURATION: + type = Type.DURATION_ARRAY; break; default: - throw new IllegalArgumentException( "Unknown type " + type ); + throw new IllegalArgumentException( "Unknown array type " + arrayType ); } } - private void readNumberArray( PageCursor cursor ) + private void initializeNumberArray( int size ) { - if ( setArrayLengthWhenReading( cursor ) ) - { - long1 = cursor.getByte(); // number type, like: byte, int, short a.s.o. - initializeNumberArray( arrayLength ); - ArrayType numberType = arrayTypeOf( (byte) long1 ); - if ( numberType == null ) - { - initializeToDummyValue(); - return; - } + long0Array = ensureBigEnough( long0Array, size ); + // plain long1 for number type + } - beginArray( arrayLength, numberType ); - for ( int i = 0; i < arrayLength; i++ ) - { - long0Array[i] = cursor.getLong(); - } - endArray(); - } + private void initializeBooleanArray( int size ) + { + long0Array = ensureBigEnough( long0Array, size ); } - private ArrayType arrayTypeOf( byte numberType ) + private void initializeTextArray( int size ) { - switch ( numberType ) - { - case RawBits.BYTE: - return ArrayType.BYTE; - case RawBits.SHORT: - return ArrayType.SHORT; - case RawBits.INT: - return ArrayType.INT; - case RawBits.LONG: - return ArrayType.LONG; - case RawBits.FLOAT: - return ArrayType.FLOAT; - case RawBits.DOUBLE: - return ArrayType.DOUBLE; - } - // bad read, hopefully - return null; + long0Array = ensureBigEnough( long0Array, size ); + // plain long1 for bytesDereferenced + byteArrayArray = ensureBigEnough( byteArrayArray, size ); } - private void readTextArray( PageCursor cursor, int maxSize ) + private void initializeDurationArray( int size ) { - if ( setArrayLengthWhenReading( cursor ) ) - { - initializeTextArray( arrayLength ); - beginArray( arrayLength, ArrayType.STRING ); - for ( int i = 0; i < arrayLength; i++ ) - { - short bytesLength = cursor.getShort(); - if ( bytesLength <= 0 || bytesLength > maxSize ) - { - initializeToDummyValue(); - return; - } - // long1 == text byte[] have been handed out to a UTF8StringValue and so must not be overwritten with next value - if ( booleanOf( long1 ) || byteArrayArray[i] == null || byteArrayArray[i].length < bytesLength ) - { - long1 = FALSE; + long0Array = ensureBigEnough( long0Array, size ); + long1Array = ensureBigEnough( long1Array, size ); + long2Array = ensureBigEnough( long2Array, size ); + long3Array = ensureBigEnough( long3Array, size ); + } - // allocate a bit more than required so that there's a higher chance that this byte[] instance - // can be used for more keys than just this one - byteArrayArray[i] = new byte[bytesLength + bytesLength / 2]; - } - long0Array[i] = bytesLength; - cursor.getBytes( byteArrayArray[i], 0, bytesLength ); - } - endArray(); - } + private void initializeLocalTimeArray( int size ) + { + long0Array = ensureBigEnough( long0Array, size ); } - private void readArray( PageCursor cursor, ArrayType type, ArrayElementReader reader ) + private void initializeZonedTimeArray( int size ) { - if ( setArrayLengthWhenReading( cursor ) ) - { - beginArray( arrayLength, type ); - for ( int i = 0; i < arrayLength; i++ ) - { - reader.readFrom( cursor ); - } - endArray(); - } + long0Array = ensureBigEnough( long0Array, size ); + long1Array = ensureBigEnough( long1Array, size ); } - private boolean setArrayLengthWhenReading( PageCursor cursor ) + private void initializeDateArray( int size ) { - arrayLength = cursor.getInt(); - if ( arrayLength < 0 || arrayLength >= BIGGEST_REASONABLE_ARRAY_LENGTH ) + long0Array = ensureBigEnough( long0Array, size ); + } + + private void initializeLocalDateTimeArray( int size ) + { + long0Array = ensureBigEnough( long0Array, size ); + long1Array = ensureBigEnough( long1Array, size ); + } + + private void initializeZonedDateTimeArray( int size ) + { + long0Array = ensureBigEnough( long0Array, size ); + long1Array = ensureBigEnough( long1Array, size ); + long2Array = ensureBigEnough( long2Array, size ); + long3Array = ensureBigEnough( long3Array, size ); + } + /* */ + /* */ + + /* */ + private void setBytesLength( int length ) + { + if ( booleanOf( long1 ) || byteArray == null || byteArray.length < length ) { - initializeToDummyValue(); - return false; + long1 = FALSE; + + // allocate a bit more than required so that there's a higher chance that this byte[] instance + // can be used for more keys than just this one + byteArray = new byte[length + length / 2]; } + long0 = length; + } - isArray = true; - return true; + private static byte[][] ensureBigEnough( byte[][] array, int targetLength ) + { + return array == null || array.length < targetLength ? new byte[targetLength][] : array; } - void initializeToDummyValue() + private static long[] ensureBigEnough( long[] array, int targetLength ) { - type = Type.NUMBER; - long0 = 0; - long1 = 0; - inclusion = NEUTRAL; + return array == null || array.length < targetLength ? new long[targetLength] : array; } - private void readNumber( PageCursor cursor ) + private static NumberValue numberAsValue( long long0, long long1 ) { - long1 = cursor.getByte(); - long0 = cursor.getLong(); + // There's a difference between composing a single text value and a array text value + // and there's therefore no common "raw" variant of it + return RawBits.asNumberValue( long0, (byte) long1 ); } - private void readBoolean( PageCursor cursor ) + private static BooleanValue booleanAsValue( long long0 ) { - writeBoolean( cursor.getByte() == TRUE ); + return Values.booleanValue( booleanAsValueRaw( long0 ) ); } - private void readText( PageCursor cursor, int maxSize ) + private static boolean booleanAsValueRaw( long long0 ) { - // For performance reasons cannot be redirected to writeString, due to byte[] reuse - short bytesLength = cursor.getShort(); - if ( bytesLength <= 0 || bytesLength > maxSize ) - { - initializeToDummyValue(); - return; - } - setBytesLength( bytesLength ); - cursor.getBytes( byteArray, 0, bytesLength ); + return booleanOf( long0 ); } - private void readDuration( PageCursor cursor ) + private static Value textAsValue( byte[] byteArray, long long0 ) { - // TODO unify order - long totalAvgSeconds = cursor.getLong(); - int nanosOfSecond = cursor.getInt(); - long months = cursor.getLong(); - long days = cursor.getLong(); - writeDuration( months, days, totalAvgSeconds, nanosOfSecond ); + // There's a difference between composing a single text value and a array text value + // and there's therefore no common "raw" variant of it + return byteArray == null ? NO_VALUE : Values.utf8Value( byteArray, 0, (int) long0 ); } - private void readLocalTime( PageCursor cursor ) + private String textAsValueRaw( byte[] byteArray, long long0 ) { - writeLocalTime( cursor.getLong() ); + return byteArray == null ? null : UTF8.decode( byteArray, 0, (int) long0 ); } - private void readZonedTime( PageCursor cursor ) + private static DurationValue durationAsValue( long long0, long long1, long long2, long long3 ) { - writeTime( cursor.getLong(), cursor.getInt() ); + // DurationValue has no "raw" variant + long seconds = long0 - long2 * AVG_MONTH_SECONDS - long3 * AVG_DAY_SECONDS; + return DurationValue.duration( long2, long3, seconds, long1 ); } - private void readDate( PageCursor cursor ) + private static LocalTimeValue localTimeAsValue( long long0 ) { - writeDate( cursor.getLong() ); + return LocalTimeValue.localTime( localTimeAsValueRaw( long0 ) ); } - private void readLocalDateTime( PageCursor cursor ) + private static LocalTime localTimeAsValueRaw( long long0 ) { - writeLocalDateTime( cursor.getLong(), cursor.getInt() ); + return LocalTimeValue.localTimeRaw( long0 ); } - private void readZonedDateTime( PageCursor cursor ) + private static Value zonedTimeAsValue( long long0, long long1 ) { - long epochSecondUTC = cursor.getLong(); - int nanoOfSecond = cursor.getInt(); - int encodedZone = cursor.getInt(); - if ( isZoneId( encodedZone ) ) - { - writeDateTime( epochSecondUTC, nanoOfSecond, asZoneId( encodedZone ) ); - } - else + OffsetTime time = zonedTimeAsValueRaw( long0, long1 ); + return time != null ? TimeValue.time( time ) : NO_VALUE; + } + + private static OffsetTime zonedTimeAsValueRaw( long long0, long long1 ) + { + if ( TimeZones.validZoneOffset( (int) long1 ) ) { - writeDateTime( epochSecondUTC, nanoOfSecond, asZoneOffset( encodedZone ) ); + return TimeValue.timeRaw( long0, ZoneOffset.ofTotalSeconds( (int) long1 ) ); } + // TODO Getting here means that after a proper read this value is plain wrong... shouldn't something be thrown instead? + return null; } - void writeValue( Value value, NativeIndexKey.Inclusion inclusion ) + private static DateValue dateAsValue( long long0 ) { - value.writeTo( this ); - this.inclusion = inclusion; + return DateValue.date( dateAsValueRaw( long0 ) ); + } + + private static LocalDate dateAsValueRaw( long long0 ) + { + return DateValue.epochDateRaw( long0 ); + } + + private static LocalDateTimeValue localDateTimeAsValue( long long0, long long1 ) + { + return LocalDateTimeValue.localDateTime( localDateTimeAsValueRaw( long0, long1 ) ); + } + + private static LocalDateTime localDateTimeAsValueRaw( long long0, long long1 ) + { + return LocalDateTimeValue.localDateTimeRaw( long1, long0 ); + } + + private static DateTimeValue zonedDateTimeAsValue( long long0, long long1, long long2, long long3 ) + { + return DateTimeValue.datetime( zonedDateTimeAsValueRaw( long0, long1, long2, long3 ) ); + } + + private static ZonedDateTime zonedDateTimeAsValueRaw( long long0, long long1, long long2, long long3 ) + { + return TimeZones.validZoneId( (short) long2 ) ? + DateTimeValue.datetimeRaw( long0, long1, ZoneId.of( TimeZones.map( (short) long2 ) ) ) : + DateTimeValue.datetimeRaw( long0, long1, ZoneOffset.ofTotalSeconds( (int) long3 ) ); + } + + private static boolean isHighestText( long long3 ) + { + return long3 == TRUE; + } + + private static boolean booleanOf( long longValue ) + { + return longValue == TRUE; } @FunctionalInterface @@ -1598,4 +1581,5 @@ interface ArrayElementValueFactory { T from( int i ); } + /* */ }