Skip to content

Commit a06c3a9

Browse files
committed
GH-4950 LMDB: fix handling of doubles and code cleanup
1 parent f4b189d commit a06c3a9

6 files changed

Lines changed: 99 additions & 136 deletions

File tree

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TripleStore.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,9 @@ protected void filterUsedIds(Collection<Long> ids) throws IOException {
630630
if (component != 2) {
631631
// optimization: ensure that literals are only tested if they appear in object
632632
// position
633-
if (ValueIds.getIdType(id) == ValueIds.T_LITERAL) {
633+
switch (ValueIds.getIdType(id)) {
634+
case ValueIds.T_DOUBLE:
635+
case ValueIds.T_LITERAL:
634636
// id is a literal, don't test it
635637
continue;
636638
}

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/ValueIds.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
* Constants and functions for working with ids encoded into long values.
1515
*/
1616
public class ValueIds {
17+
/**
18+
* An inlined double value. The least significant bit of the value is set to 1 to distinguish it from other inlined
19+
* values and references.
20+
*/
21+
public static final int T_DOUBLE = -1;
22+
1723
/**
1824
* Pointer to an arbitrary value in the value store. This is not used as RDF value.
1925
*/
@@ -57,6 +63,9 @@ public class ValueIds {
5763
* @return The id's type.
5864
*/
5965
public static int getIdType(long id) {
66+
if (isDouble(id)) {
67+
return T_DOUBLE;
68+
}
6069
return (int) ((id >> 1) & 0x3F);
6170
}
6271

@@ -78,7 +87,7 @@ public static long getValue(long id) {
7887
* @return A composite id.
7988
*/
8089
public static long createId(int idType, long value) {
81-
return value << 7 | idType << 1;
90+
return value << 7 | (long) idType << 1;
8291
}
8392

8493
/**
@@ -88,6 +97,17 @@ public static long createId(int idType, long value) {
8897
* @return <code>true</code> if the value is inlined, else <code>false</code>
8998
*/
9099
public static boolean isInlined(long id) {
91-
return getIdType(id) >= T_INTEGER;
100+
return isDouble(id) || getIdType(id) >= T_INTEGER;
101+
}
102+
103+
/**
104+
* Tests if the given id is an inlined double value, which is identified by the least significant bit being set to
105+
* 1.
106+
*
107+
* @param value The id's value
108+
* @return <code>true</code> if the value is an inlined double, else <code>false</code>
109+
*/
110+
public static boolean isDouble(long value) {
111+
return (value & 1L) != 0;
92112
}
93113
}

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/ValueStore.java

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -405,24 +405,13 @@ private void open() throws IOException {
405405
}
406406

407407
private long nextId(byte valueType) throws IOException {
408-
int idType;
409-
switch (valueType) {
410-
case URI_VALUE:
411-
idType = ValueIds.T_URI;
412-
break;
413-
case BNODE_VALUE:
414-
idType = ValueIds.T_BNODE;
415-
break;
416-
case LITERAL_VALUE:
417-
idType = ValueIds.T_LITERAL;
418-
break;
419-
case NAMESPACE_VALUE:
420-
idType = ValueIds.T_PTR;
421-
break;
422-
default:
423-
throw new IllegalArgumentException("Unexpected value type: " + valueType);
424-
425-
}
408+
int idType = switch (valueType) {
409+
case URI_VALUE -> ValueIds.T_URI;
410+
case BNODE_VALUE -> ValueIds.T_BNODE;
411+
case LITERAL_VALUE -> ValueIds.T_LITERAL;
412+
case NAMESPACE_VALUE -> ValueIds.T_PTR;
413+
default -> throw new IllegalArgumentException("Unexpected value type: " + valueType);
414+
};
426415
if (freeIdsAvailable) {
427416
// next id from store
428417
Long reusedId = writeTransaction((stack, txn) -> {
@@ -569,6 +558,7 @@ public LmdbValue getLazyValue(long id) throws IOException {
569558
case ValueIds.T_URI:
570559
resultValue = new LmdbIRI(lazyRevision, id);
571560
break;
561+
case ValueIds.T_DOUBLE:
572562
case ValueIds.T_LITERAL:
573563
resultValue = new LmdbLiteral(lazyRevision, id);
574564
break;
@@ -580,7 +570,7 @@ public LmdbValue getLazyValue(long id) throws IOException {
580570
resultValue = new LmdbLiteral(lazyRevision, id);
581571
break;
582572
}
583-
throw new IOException("Unsupported value with id type: " + idType);
573+
throw new IOException("Unsupported value with id=" + id + " and id type " + idType);
584574
}
585575
}
586576

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/inlined/Decimals.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,9 @@ static long packDecimal(BigDecimal value) {
5353
* @return Encoded 10-bit exponent as int (0-1023), or -1 if not encodable.
5454
*/
5555
public static int encodeExponent10Bits(int exponent11) {
56-
boolean isNaN = exponent11 == 0x7FF && (exponent11 & 0xFFFFFFFFFFFFFL) != 0;
57-
boolean isInf = exponent11 == 0x7FF && (exponent11 & 0xFFFFFFFFFFFFFL) == 0;
58-
59-
if (isNaN || isInf) {
56+
// isNaN or Inf - we do not distinguish between them in the compact representation, but reserve a special
57+
// pattern for both
58+
if (exponent11 == 0x7FF) {
6059
// Reserve special pattern, e.g., 0x3FF (all 10 bits set) for NaN/Inf
6160
return 0x3FF;
6261
}
@@ -106,8 +105,7 @@ static long packDouble(double value) {
106105
// encoding of exponent was possible
107106
int sign = value < 0 ? 1 : 0;
108107
long mantissa = valueBits & 0x000fffffffffffffL;
109-
long encoded = ((long) exponent10) << 54 | mantissa << 2 | sign << 1 | 1;
110-
return encoded;
108+
return ((long) exponent10) << 54 | mantissa << 2 | sign << 1 | 1;
111109
}
112110
return 0L;
113111
}
@@ -123,10 +121,6 @@ static Literal unpackDecimal(long value, ValueFactory valueFactory) {
123121
return valueFactory.createLiteral(new BigDecimal(BigInteger.valueOf(unscaled), scale));
124122
}
125123

126-
static boolean isDouble(long value) {
127-
return (value & 1L) != 0;
128-
}
129-
130124
static Literal unpackDouble(long value, ValueFactory valueFactory) {
131125
if ((value & 1L) == 0) {
132126
throw new IllegalArgumentException("Invalid packed double value: zero bit not set.");

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/inlined/Values.java

Lines changed: 49 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -35,106 +35,59 @@ public static long packLiteral(Literal literal) {
3535
if (xsdDataType == null) {
3636
return 0L;
3737
}
38-
switch (xsdDataType) {
39-
case DECIMAL:
40-
return packDecimal(literal.decimalValue());
41-
case DOUBLE:
42-
return packDouble(literal.doubleValue());
43-
case FLOAT:
44-
return packFloat(literal.floatValue());
45-
case INTEGER:
46-
return packInteger(literal);
47-
case LONG:
48-
return packLong(literal);
49-
case INT:
50-
return packInt(literal);
51-
case SHORT:
52-
return packShort(literal);
53-
case BYTE:
54-
return packByte(literal);
55-
case UNSIGNED_LONG:
56-
return packUnsignedLong(literal);
57-
case UNSIGNED_INT:
58-
return packUnsignedInt(literal);
59-
case UNSIGNED_SHORT:
60-
return packUnsignedShort(literal);
61-
case UNSIGNED_BYTE:
62-
return packUnsignedByte(literal);
63-
case POSITIVE_INTEGER:
64-
return packPositiveInteger(literal);
65-
case NEGATIVE_INTEGER:
66-
return packNegativeInteger(literal);
67-
case NON_NEGATIVE_INTEGER:
68-
return packNonNegativeInteger(literal);
69-
case NON_POSITIVE_INTEGER:
70-
return packNonPositiveInteger(literal);
71-
case STRING:
72-
return packString(literal);
73-
case DATETIME:
74-
return packDateTime(literal);
75-
case DATETIMESTAMP:
76-
return packDateTimeStamp(literal);
77-
case DATE:
78-
return packDate(literal);
79-
case BOOLEAN:
80-
return packBoolean(literal);
81-
default:
38+
return switch (xsdDataType) {
39+
case DECIMAL -> packDecimal(literal.decimalValue());
40+
case DOUBLE -> packDouble(literal.doubleValue());
41+
case FLOAT -> packFloat(literal.floatValue());
42+
case INTEGER -> packInteger(literal);
43+
case LONG -> packLong(literal);
44+
case INT -> packInt(literal);
45+
case SHORT -> packShort(literal);
46+
case BYTE -> packByte(literal);
47+
case UNSIGNED_LONG -> packUnsignedLong(literal);
48+
case UNSIGNED_INT -> packUnsignedInt(literal);
49+
case UNSIGNED_SHORT -> packUnsignedShort(literal);
50+
case UNSIGNED_BYTE -> packUnsignedByte(literal);
51+
case POSITIVE_INTEGER -> packPositiveInteger(literal);
52+
case NEGATIVE_INTEGER -> packNegativeInteger(literal);
53+
case NON_NEGATIVE_INTEGER -> packNonNegativeInteger(literal);
54+
case NON_POSITIVE_INTEGER -> packNonPositiveInteger(literal);
55+
case STRING -> packString(literal);
56+
case DATETIME -> packDateTime(literal);
57+
case DATETIMESTAMP -> packDateTimeStamp(literal);
58+
case DATE -> packDate(literal);
59+
case BOOLEAN -> packBoolean(literal);
60+
default ->
8261
// unsupported type
83-
return 0L;
84-
}
62+
0L;
63+
};
8564
}
8665

8766
public static Literal unpackLiteral(long value, ValueFactory valueFactory) {
88-
// special handling for double values
89-
if (isDouble(value)) {
90-
return unpackDouble(value, valueFactory);
91-
}
92-
9367
int idType = ValueIds.getIdType(value);
94-
95-
switch (idType) {
96-
case ValueIds.T_DECIMAL:
97-
return unpackDecimal(value, valueFactory);
98-
case ValueIds.T_FLOAT:
99-
return unpackFloat(value, valueFactory);
100-
case ValueIds.T_INTEGER:
101-
return unpackInteger(value, valueFactory);
102-
case ValueIds.T_LONG:
103-
return unpackLong(value, valueFactory);
104-
case ValueIds.T_INT:
105-
return unpackInt(value, valueFactory);
106-
case ValueIds.T_SHORT:
107-
return unpackShort(value, valueFactory);
108-
case ValueIds.T_BYTE:
109-
return unpackByte(value, valueFactory);
110-
case ValueIds.T_UNSIGNEDLONG:
111-
return unpackUnsignedLong(value, valueFactory);
112-
case ValueIds.T_UNSIGNEDINT:
113-
return unpackUnsignedInt(value, valueFactory);
114-
case ValueIds.T_UNSIGNEDSHORT:
115-
return unpackUnsignedShort(value, valueFactory);
116-
case ValueIds.T_UNSIGNEDBYTE:
117-
return unpackUnsignedByte(value, valueFactory);
118-
case ValueIds.T_POSITIVE_INTEGER:
119-
return unpackPositiveInteger(value, valueFactory);
120-
case ValueIds.T_NEGATIVE_INTEGER:
121-
return unpackNegativeInteger(value, valueFactory);
122-
case ValueIds.T_NON_NEGATIVE_INTEGER:
123-
return unpackNonNegativeInteger(value, valueFactory);
124-
case ValueIds.T_NON_POSITIVE_INTEGER:
125-
return unpackNonPositiveInteger(value, valueFactory);
126-
case ValueIds.T_SHORTSTRING:
127-
return unpackString(value, valueFactory);
128-
case ValueIds.T_DATETIME:
129-
return unpackDateTime(value, valueFactory);
130-
case ValueIds.T_DATETIMESTAMP:
131-
return unpackDateTimeStamp(value, valueFactory);
132-
case ValueIds.T_DATE:
133-
return unpackDate(value, valueFactory);
134-
case ValueIds.T_BOOLEAN:
135-
return unpackBoolean(value, valueFactory);
136-
default:
137-
throw new IllegalArgumentException("Invalid packed value with id type: " + idType);
138-
}
68+
return switch (idType) {
69+
case ValueIds.T_DOUBLE -> unpackDouble(value, valueFactory);
70+
case ValueIds.T_DECIMAL -> unpackDecimal(value, valueFactory);
71+
case ValueIds.T_FLOAT -> unpackFloat(value, valueFactory);
72+
case ValueIds.T_INTEGER -> unpackInteger(value, valueFactory);
73+
case ValueIds.T_LONG -> unpackLong(value, valueFactory);
74+
case ValueIds.T_INT -> unpackInt(value, valueFactory);
75+
case ValueIds.T_SHORT -> unpackShort(value, valueFactory);
76+
case ValueIds.T_BYTE -> unpackByte(value, valueFactory);
77+
case ValueIds.T_UNSIGNEDLONG -> unpackUnsignedLong(value, valueFactory);
78+
case ValueIds.T_UNSIGNEDINT -> unpackUnsignedInt(value, valueFactory);
79+
case ValueIds.T_UNSIGNEDSHORT -> unpackUnsignedShort(value, valueFactory);
80+
case ValueIds.T_UNSIGNEDBYTE -> unpackUnsignedByte(value, valueFactory);
81+
case ValueIds.T_POSITIVE_INTEGER -> unpackPositiveInteger(value, valueFactory);
82+
case ValueIds.T_NEGATIVE_INTEGER -> unpackNegativeInteger(value, valueFactory);
83+
case ValueIds.T_NON_NEGATIVE_INTEGER -> unpackNonNegativeInteger(value, valueFactory);
84+
case ValueIds.T_NON_POSITIVE_INTEGER -> unpackNonPositiveInteger(value, valueFactory);
85+
case ValueIds.T_SHORTSTRING -> unpackString(value, valueFactory);
86+
case ValueIds.T_DATETIME -> unpackDateTime(value, valueFactory);
87+
case ValueIds.T_DATETIMESTAMP -> unpackDateTimeStamp(value, valueFactory);
88+
case ValueIds.T_DATE -> unpackDate(value, valueFactory);
89+
case ValueIds.T_BOOLEAN -> unpackBoolean(value, valueFactory);
90+
default -> throw new IllegalArgumentException("Invalid packed value " + value + " with id type: " + idType);
91+
};
13992
}
14093
}

core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/inlined/ValuesTest.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,18 @@ class ValuesTest {
144144
@Test
145145
void testPackAndUnpack_AllLiteralTypesWithEdgeAndStandardCases() {
146146
for (Literal literal : literals) {
147-
long packed = Values.packLiteral(literal);
148-
// If the literal is not inlined, packed==0. Only test roundtrip if it is inlined.
149-
if (packed != 0L) {
150-
Literal unpacked = Values.unpackLiteral(packed, vf);
151-
assertEqualLiterals(unpacked, literal);
152-
} else {
153-
// (optional) ensure non-inlined values can be detected
154-
assertThat(packed).isZero();
147+
try {
148+
long packed = Values.packLiteral(literal);
149+
// If the literal is not inlined, packed==0. Only test roundtrip if it is inlined.
150+
if (packed != 0L) {
151+
Literal unpacked = Values.unpackLiteral(packed, vf);
152+
assertEqualLiterals(unpacked, literal);
153+
} else {
154+
// (optional) ensure non-inlined values can be detected
155+
assertThat(packed).isZero();
156+
}
157+
} catch (Throwable e) {
158+
throw new AssertionError("Failed to pack/unpack literal: " + literal, e);
155159
}
156160
}
157161
}

0 commit comments

Comments
 (0)