Skip to content

64-bit integer arithmetic overflow silently wraps around instead of failing #2471

Description

@shulei5831sl

Apache AGE version

Observed on Docker image:

docker pull apache/age:latest
Pulled: 2026-07-09

Apache AGE: PostgreSQL 18.1

Endpoint:
postgres://127.0.0.1:5432

How are you accessing AGE

psql and Python psycopg2 driver

Environment

  • Host OS: Linux 5.4.0-216-generic

  • Deployment: Docker apache/age:latest

  • Query interface: psql and psycopg2

  • Query language: Cypher via ag_catalog.cypher

  • Configuration: default AGE on PostgreSQL 18, no extensions beyond AGE

  • Differential comparison targets:

    • Neo4j 2026.05.0
    • PostgreSQL native arithmetic

Description

AGE Cypher integer arithmetic silently wraps around on 64-bit overflow instead of failing the query.

For example:

SELECT * FROM cypher('test_graph', $$
  RETURN 9223372036854775807 + 1 AS r
$$) AS (r agtype);

AGE returns:

r
---------------------
-9223372036854775808

This is a two's-complement wraparound from INT64_MAX to INT64_MIN.

The mathematical result is outside the signed 64-bit integer range, so the query should fail instead of returning another valid-looking integer value.

PostgreSQL native arithmetic rejects the same operation with bigint out of range, and Neo4j also rejects the equivalent Cypher expression with an arithmetic overflow error.

Setup

LOAD 'age';
SET search_path = ag_catalog, '$user', public;
SELECT * FROM ag_catalog.create_graph('test_graph');

Minimal reproduction

SELECT * FROM cypher('test_graph', $$
  RETURN 9223372036854775807 + 1 AS r
$$) AS (r agtype);

Expected behavior

The query should fail with an integer overflow error.

PostgreSQL native arithmetic:

SELECT 9223372036854775807::bigint + 1::bigint;

returns:

ERROR: bigint out of range

Neo4j 2026.05.0 also rejects:

RETURN 9223372036854775807 + 1 AS r;

with an arithmetic overflow/client error.

Actual behavior

AGE returns a wrapped value:

r
---------------------
-9223372036854775808

No error or warning is reported.

Additional affected expressions

SELECT * FROM cypher('test_graph', $$
  RETURN 9223372036854775807 * 2 AS r
$$) AS (r agtype);

AGE returns:

r = -2

Expected: overflow error.

SELECT * FROM cypher('test_graph', $$
  RETURN -9223372036854775808 - 1 AS r
$$) AS (r agtype);

AGE returns:

r = 9223372036854775807

Expected: overflow error.

Control query

Arithmetic within range works correctly:

SELECT * FROM cypher('test_graph', $$
  RETURN 1 + 1 AS r
$$) AS (r agtype);

AGE returns:

r = 2

Impact: silent wrong results

The overflowed value participates in further query evaluation.

SELECT * FROM cypher('test_graph', $$
  RETURN 9223372036854775807 + 1 > 0 AS r
$$) AS (r agtype);

AGE returns:

r = false

This result is derived from the wrapped negative value, not from the mathematical result of the expression.

A write context can also persist the wrapped value:

SELECT * FROM cypher('test_graph', $$
  CREATE (:Overflow {v: 9223372036854775807 + 1})
  RETURN 1 AS ok
$$) AS (ok agtype);

SELECT * FROM cypher('test_graph', $$
  MATCH (n:Overflow)
  RETURN n.v AS v
$$) AS (v agtype);

AGE returns:

v = -9223372036854775808

Expected: the write query should fail before persisting an overflowed value.

Cross-engine comparison

Engine MAX_INT + 1 MAX_INT * 2 MIN_INT - 1
Neo4j 2026.05.0 ArithmeticError ArithmeticError ArithmeticError
PostgreSQL native bigint ERROR: bigint out of range ERROR: bigint out of range ERROR: bigint out of range
Apache AGE Cypher -9223372036854775808 -2 9223372036854775807

Why this looks like a bug

AGE documents agtype integer as a 64-bit field with the signed 64-bit range:

-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

and states that attempts to store values outside this range result in an error.

The tested Cypher expressions produce mathematical results outside that range. Instead of rejecting them, AGE returns wrapped in-range values. This behavior is consistent with unchecked int64 arithmetic.

Summary

64-bit integer arithmetic overflow should fail the query. Silently wrapping the result changes arithmetic semantics, can produce wrong predicates, and can persist incorrect property values.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions