Skip to content

Silent integer truncation: storing an out-of-range value in an INTEGER/SHORT/BYTE property corrupts it (3000000000 -> -1294967296) with no error #5905

Description

@ruispereira

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:

  1. Reject with an error (Postgres: integer out of range; MySQL strict mode: rejected).
  2. Widen / preserve the value.
  3. 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions