Skip to content

Bound query with time.Time produces extra rows #99

@bruth

Description

@bruth

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions