Summary
Storing a numeric value that does not fit an INTEGER (or SHORT/BYTE) property silently truncates it by integer overflow, with no error and no warning. INSERT INTO V SET n = 3000000000 into an INTEGER property stores -1294967296. The write succeeds; the value is corrupted.
Version: 26.8.1. Type.convert() in engine/schema/Type.java.
Reproducer
CREATE VERTEX TYPE V;
CREATE PROPERTY V.n INTEGER;
INSERT INTO V SET n = 3000000000, id = 'big';
SELECT n FROM V WHERE id = 'big';
n = -1294967296 -- stored 3000000000, got back a negative number
Same result through the typed API:
v.set("n", 3_000_000_000L); // property declared INTEGER
v.save();
// reads back as -1294967296
Directly against Type.convert:
| call |
result |
convert(3000000000L, Integer.class) |
-1294967296 |
convert(2147483648L, Integer.class) |
-2147483648 |
convert(9223372036854775807L, Integer.class) |
-1 |
The conversion narrows with .intValue(), which wraps on overflow instead of rejecting.
Why this is the worst option
A typed integer column has three possible behaviours for an out-of-range value, in decreasing order of safety:
- Reject with an error (Postgres:
integer out of range; MySQL strict mode: rejected).
- Widen / preserve the value.
- Silently wrap to a different number.
ArcadeDB does (3), the only one that loses data without telling anyone. The INSERT returns success, the application believes it stored 3 000 000 000, and a later WHERE n = 3000000000 finds nothing while WHERE n = -1294967296 finds the row. A range query or sum over the column is silently wrong.
It is trivially triggered: any value between Integer.MAX_VALUE+1 and Long.MAX_VALUE assigned to an INTEGER property, which is an ordinary mistake (a count, a timestamp-derived id, an epoch-millis value in an INT column).
Same root cause as #5900
The narrowing is the storage-side twin of the comparator narrowing in #5900: both reduce a wider numeric to int/short/byte with .intValue()/.shortValue()/.byteValue(), which overflows rather than rejecting. SHORT and BYTE properties truncate even more aggressively (16 / 8 bits).
Suggested fix
In Type.convert, when narrowing an integral value to a smaller integral target, range-check before converting and throw a typed error (e.g. CommandExecutionException / a validation exception) naming the value, the target type and the property. Math.toIntExact(long) throws ArithmeticException on overflow and is the standard primitive for exactly this; the same shape covers short/byte.
Rejecting is the correct behaviour for a typed column, and it turns a silent corruption into a clear error at the point of the bad write.
Related string case (lower priority, same method): convert("2147483648", Integer.class) and convert("abc", Integer.class) throw a raw NumberFormatException rather than a typed conversion error — worth mapping while the narrowing is being fixed.
Summary
Storing a numeric value that does not fit an
INTEGER(orSHORT/BYTE) property silently truncates it by integer overflow, with no error and no warning.INSERT INTO V SET n = 3000000000into anINTEGERproperty stores -1294967296. The write succeeds; the value is corrupted.Version: 26.8.1.
Type.convert()inengine/schema/Type.java.Reproducer
Same result through the typed API:
Directly against
Type.convert:convert(3000000000L, Integer.class)-1294967296convert(2147483648L, Integer.class)-2147483648convert(9223372036854775807L, Integer.class)-1The conversion narrows with
.intValue(), which wraps on overflow instead of rejecting.Why this is the worst option
A typed integer column has three possible behaviours for an out-of-range value, in decreasing order of safety:
integer out of range; MySQL strict mode: rejected).ArcadeDB does (3), the only one that loses data without telling anyone. The
INSERTreturns success, the application believes it stored 3 000 000 000, and a laterWHERE n = 3000000000finds nothing whileWHERE n = -1294967296finds the row. A range query or sum over the column is silently wrong.It is trivially triggered: any value between
Integer.MAX_VALUE+1andLong.MAX_VALUEassigned to anINTEGERproperty, which is an ordinary mistake (a count, a timestamp-derived id, an epoch-millis value in an INT column).Same root cause as #5900
The narrowing is the storage-side twin of the comparator narrowing in #5900: both reduce a wider numeric to
int/short/bytewith.intValue()/.shortValue()/.byteValue(), which overflows rather than rejecting.SHORTandBYTEproperties truncate even more aggressively (16 / 8 bits).Suggested fix
In
Type.convert, when narrowing an integral value to a smaller integral target, range-check before converting and throw a typed error (e.g.CommandExecutionException/ a validation exception) naming the value, the target type and the property.Math.toIntExact(long)throwsArithmeticExceptionon overflow and is the standard primitive for exactly this; the same shape coversshort/byte.Rejecting is the correct behaviour for a typed column, and it turns a silent corruption into a clear error at the point of the bad write.
Related string case (lower priority, same method):
convert("2147483648", Integer.class)andconvert("abc", Integer.class)throw a rawNumberFormatExceptionrather than a typed conversion error — worth mapping while the narrowing is being fixed.