Skip to content

Bind SQL NULL for an invalid Date instead of the PostgreSQL epoch - #44

Open
sfc-gh-okalaci wants to merge 1 commit into
up/12-array-shapefrom
up/13-invalid-date
Open

Bind SQL NULL for an invalid Date instead of the PostgreSQL epoch#44
sfc-gh-okalaci wants to merge 1 commit into
up/12-array-shapefrom
up/13-invalid-date

Conversation

@sfc-gh-okalaci

Copy link
Copy Markdown
Collaborator

Stacked on #43.

A JavaScript Date whose getTime() is NaN has no epoch to convert. The conversion
read it as a double and did the epoch arithmetic anyway — and NaN came out the far side
finite: 2000-01-01, the PostgreSQL epoch, an offset of zero.

pljs.execute('SELECT $1::timestamptz AS v', [new Date(NaN)])
// Sat Jan 01 2000 02:00:00 GMT+0200

A caller holding no date at all stored a specific one, with nothing reported.

And it doesn't take new Date(NaN) to get there. An infinite timestamp read back into
JavaScript is an invalid Date, so an ordinary read-modify-write of a row holding
'infinity'::timestamptz converts that infinity into a real timestamp:

read=invalid Date wrote=Sat Jan 01 2000 02:00:00 GMT+0200

Such a value now becomes SQL NULL, via the same helper the other null-returning paths
use, so it behaves correctly whether or not fcinfo is present — a composite column is
converted without one.

The three date cases are merged while here: DATEOID, TIMESTAMPOID and TIMESTAMPTZOID
had separate but identical Is_Date blocks, and the check would otherwise be written
twice.

Test plan

sql/pg_invalid_date.sql covers a bound parameter (timestamptz and date), a returned
value, a composite column — the no-fcinfo path — the infinity round trip, and that a
valid Date still round-trips.

It discriminates: reverted, every NULL assertion returns Sat Jan 01 2000 02:00:00 or f
instead. Full suite green on PostgreSQL 17 (56 tests); builds clean on 16, 18 and 19beta3.

A JavaScript Date whose getTime() is NaN has no epoch to convert. The conversion
read it as a double and did the epoch arithmetic regardless, and NaN came out the
far side finite: 2000-01-01, the PostgreSQL epoch, an offset of zero. A caller
holding no date at all stored a specific one, and nothing said so.

    pljs.execute('SELECT $1::timestamptz AS v', [new Date(NaN)])
      -- Sat Jan 01 2000 02:00:00 GMT+0200

Reaching it does not require writing NaN by hand. An infinite timestamp read back
into JavaScript *is* an invalid Date, so an ordinary read-modify-write of a row
holding 'infinity'::timestamptz turns that infinity into a real timestamp:

    read=invalid Date wrote=Sat Jan 01 2000 02:00:00 GMT+0200

Such a value now becomes SQL NULL, through the same helper the other null-returning
paths use, so it is correct whether or not fcinfo is present -- a composite column
is converted without one.

The three date cases are also merged: DATEOID, TIMESTAMPOID and TIMESTAMPTZOID had
separate but identical Is_Date blocks, and the NaN check would otherwise have been
written twice.

Adds sql/pg_invalid_date.sql, covering a bound parameter, a returned value, a
composite column (the no-fcinfo path), the infinity round trip, and that a valid
Date is unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant