Skip to content

Timeseries TIME_KEY declared TIMESTAMP returns OID 25 text carrying raw epoch millis instead of a timestamp #298

Description

@emanzx

Version / build tested against

origin/main @ 2886155

Deployment mode

Origin — single node (local)

Engine(s) involved

Timeseries

Summary

A timeseries TIME_KEY column declared TIMESTAMP comes back over pgwire as OID 25 (TEXT) carrying the raw epoch-milliseconds integer as a string ('1788692400000'), instead of OID 1114 with a timestamp rendering. Every consumer downstream misreads it: psql displays a bare number, drivers hand applications a string, ORMs fail to map the column to a datetime type, and BI tools cannot treat the time axis as time. The declared DDL type and the wire type disagree — the same fidelity contract that RowDescription preserves for integer widths on other engines is dropped on the timeseries time key.

Steps to reproduce

CREATE COLLECTION tsp (ts TIMESTAMP TIME_KEY, val DOUBLE PRECISION) WITH (engine = 'timeseries');
INSERT INTO tsp (ts, val) VALUES ('2026-09-06T10:00:00Z', 1.5);

SELECT ts, val FROM tsp;
--       ts       | val
-- ---------------+-----
--  1788688800000 | 1.5      <- epoch millis as text; expected 2026-09-06 10:00:00

Driver-level (psycopg2) RowDescription:

cur.execute('SELECT ts, val FROM tsp')
cur.description  -> [('ts', 25), ('val', 701)]     # ts = OID 25 (TEXT); expected 1114 (TIMESTAMP)
cur.fetchone()   -> ('1788692400000', 2.5)         # string of millis

Expected behavior

The wire type follows the declared type: OID 1114 (TIMESTAMP) in RowDescription and a PostgreSQL-format timestamp in the text protocol (2026-09-06 10:00:00), so psql, drivers, and ORMs read the time key as a timestamp. Ingest already accepts the RFC3339 timestamp literal, so the asymmetry is only on the read side.

Actual behavior

RowDescription declares TEXT and the value is the internal epoch-millis representation serialized as a string. Round-tripping a value the server itself accepted (INSERT ... SELECT-style workflows, dump/reload) yields a format the insert path does not document as canonical, and applications must special-case the column.

What actually happened? (check all that are true)

  • Acknowledged/committed data was lost, corrupted, or silently wrong
  • The server crashed, hung, or failed to start
  • A security or isolation boundary was crossed
  • Core functionality is broken with no acceptable workaround
  • A workaround exists (rewrite the query, avoid one path, etc.)

Proposed severity

SEV-3 — Medium: feature wrong, but operational and a workaround exists

Reproducibility

Always — every attempt

Last known-good version / commit (if a regression)

Unknown.

Environment & logs

Linux x86_64, release build from a fresh data directory, trust mode. Tested via psql 16 and psycopg2.

Before submitting

  • I searched existing issues and this is not a duplicate.
  • I reproduced this on a released tag or a current main build (not a stale local branch).
  • This is not a security vulnerability (those go to a private advisory).

Activity

  1. added
    type:bugA defect — broken, incorrect, or lost data
    sev:3-mediumFeature wrong, but operational and a workaround exists
    status:needs-triageAwaiting maintainer triage (severity + priority)
    area:pgwirePostgreSQL wire protocol / client compat
    on Sep 6, 2026
  2. farhan-syah commented on Sep 10, 2026

    @farhan-syah
    Member

    Fixed on the branch for PR #316, verified over pgwire.

    CREATE COLLECTION tsp (ts TIMESTAMP TIME_KEY, val DOUBLE PRECISION) WITH (engine = 'timeseries');
    INSERT INTO tsp (ts, val) VALUES ('2026-09-06T10:00:00Z', 1.5);
    SELECT ts, val FROM tsp;
    

    RowDescription now reports OID 1114 for ts and 701 for val. The cell renders 2026-09-06T10:00:00.000000Z instead of the raw epoch-millisecond string.

    Two separate defects produced the reported behaviour.

    • parse_type_str matched the whole declared string, so any column carrying a trailing modifier fell through to SqlDataType::String and announced OID 25. It now resolves the bare type token through one shared classifier in nodedb-types.
    • The timeseries engine stores a time key in milliseconds while the renderer reads microseconds. The scan path now converts at its client boundary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:pgwirePostgreSQL wire protocol / client compatengine:timeseriesTimeseries enginepriority:P2Scheduled, not urgentsev:3-mediumFeature wrong, but operational and a workaround existsstatus:needs-triageAwaiting maintainer triage (severity + priority)type:bugA defect — broken, incorrect, or lost data

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions