-
Notifications
You must be signed in to change notification settings - Fork 19
Description
I ran into a very odd bug and I don't know the root cause, however Claude was able to "fix" the behavior with a simple change to inferPrimitiveType of a time.Time to a TYPE_TIMESTAMPTZ instead of a TYPE_TIMESTAMP. Here is a test to produce the bug.
func TestTimestampBinding(t *testing.T) {
db := openDbWrapper(t, ``)
defer closeDbWrapper(t, db)
startTime := time.Date(2026, 1, 8, 12, 0, 0, 0, time.UTC)
endTime := time.Date(2026, 1, 8, 13, 0, 0, 0, time.UTC)
// Interpolated query returns 13 rows
interpolatedQuery := fmt.Sprintf(`
SELECT COUNT(*) FROM generate_series(
time_bucket(INTERVAL '5 minutes', '%s'::timestamptz),
'%s'::timestamptz,
INTERVAL '5 minutes'
) AS t(epoch_ts)
`, startTime.Format(time.RFC3339), endTime.Format(time.RFC3339))
var expectedCount int
err := db.QueryRow(interpolatedQuery).Scan(&expectedCount)
require.NoError(t, err)
require.Equal(t, 13, expectedCount)
// Bound query returns 73 rows with parameters without the fix.
boundedQuery := `
SELECT COUNT(*) FROM generate_series(
time_bucket(INTERVAL '5 minutes', ?::timestamptz),
?::timestamptz,
INTERVAL '5 minutes'
) AS t(epoch_ts)
`
err = db.QueryRow(boundedQuery, startTime, endTime).Scan(&expectedCount)
require.NoError(t, err)
require.Equal(t, 13, expectedCount)
}The first query does string interpolation of the same dates, but the second one uses parameters. In this particular case, the second query returns 60 more rows that are zero values than the correct 13 rows.
Regarding the change to infer to TYPE_TIMESTAMPTZ, I actually think this is necessary regardless since Go's time.Time always has a time zone associated. By default, if you don't specify, it will assume UTC, but you can't have a time without one set. It would be on the developer to ensure they set the correct timezone when working with time.
I can put this in a PR, but wanted to get folk's thoughts.