apache / apache/age

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

Open
#2,471 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
C
Stars
4.8k
Forks
523
Avg merge
1d 2h
Merged PRs (30d)
9

Description

### Apache AGE version

Observed on Docker image:

```text
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:

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

AGE returns:

```text
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

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

### Minimal reproduction

```pgsql
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:

```pgsql
SELECT 9223372036854775807::bigint + 1::bigint;
```

returns:

```text
ERROR: bigint out of range
```

Neo4j `2026.05.0` also rejects:

```cypher
RETURN 9223372036854775807 + 1 AS r;
```

with an arithmetic overflow/client error.

### Actual behavior

AGE returns a wrapped value:

```text
r
---------------------
-9223372036854775808
```

No error or warning is reported.

### Additional affected expressions

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

AGE returns:

```text
r = -2
```

Expected: overflow error.

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

AGE returns:

```text
r = 9223372036854775807
```

Expected: overflow error.

### Control query

Arithmetic within range works correctly:

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

AGE returns:

```text
r = 2
```

### Impact: silent wrong results

The overflowed value participates in further query evaluation.

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

AGE returns:

```text
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:

```pgsql
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:

```text
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:

```text
-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.

Contributor guide

Open the contributing guide

Research direction

Start with the minimal reproduction through ag_catalog.cypher and trace the integer arithmetic evaluation path in AGE's C sources; the issue names no source file or test. Done means out-of-range addition, multiplication, and subtraction fail with an overflow error, while in-range arithmetic continues to work.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, postgresql, sql
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.