Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- Read query results as Arrow directly from `POST /v1/query`, so a fast query takes one request instead of two. Async and too-large results still come from the Results API.
- Show the real error for failed queries: read `error_message` from query runs and failed results, and show the `message` and `code` from Hotdata error bodies instead of raw JSON.
- Follow pagination on `/v1/databases` and `/v1/information_schema`, so the database picker and SQL autocomplete list every database and table, not just the first page.
- Handle the Results API's `202` (still persisting, honoring `Retry-After`) and `409` (failed) responses.
- Handle the Results API's `202` (still persisting) and `409` (failed) responses. Polling keeps its fast 50ms backoff rather than the server's `Retry-After: 1`, which would add a second to large results.
- The health check validates credentials with one `GET /v1/databases?limit=1` instead of listing every database.

## 1.0.4
Expand Down
15 changes: 7 additions & 8 deletions pkg/hotdata/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,12 @@ func (c *Client) pollRun(ctx context.Context, runID, databaseID string) (RunMeta
// is database-scoped. The caller must close the reader.
//
// A result that is still being persisted answers 202 with a JSON body
// {"result_id":…,"status":"processing"} and a Retry-After header, so poll
// until the response is an actual Arrow stream, backing off up to PollMax and
// honoring ctx. A failed result answers 409, which surfaces as an APIError.
// {"result_id":…,"status":"processing"}, so poll until the response is an
// actual Arrow stream, backing off up to PollMax and honoring ctx. A failed
// result answers 409, which surfaces as an APIError.
//
// The 202 carries Retry-After: 1, but persistence usually finishes ~100ms
// later; honoring it added a full second to every result too large to inline.
func (c *Client) FetchResultArrow(ctx context.Context, resultID, databaseID string) (io.ReadCloser, error) {
// Persistence of a just-minted result usually takes milliseconds, so start
// with a much shorter backoff than the run-poll cadence.
Expand Down Expand Up @@ -258,14 +261,10 @@ func (c *Client) FetchResultArrow(ctx context.Context, resultID, databaseID stri
return nil, fmt.Errorf("result not available (status %q): %s", st.Status, msg)
}

wait := retryAfterDelay(resp.Header.Get("Retry-After"), interval)
if wait > c.PollMax {
wait = c.PollMax
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(wait):
case <-time.After(interval):
}
if interval = interval * 3 / 2; interval > c.PollMax {
interval = c.PollMax
Expand Down
7 changes: 4 additions & 3 deletions pkg/plugin/datasource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,12 @@ func newContractServer(t *testing.T, opts serverOpts) (*httptest.Server, *atomic
w.WriteHeader(http.StatusBadRequest)
return
}
// The result is not persisted yet: 202 with a JSON placeholder and
// Retry-After before the Arrow stream is served.
// The result is not persisted yet: 202 with a JSON placeholder before
// the Arrow stream is served. Production sends Retry-After: 1, which
// the client deliberately ignores in favor of its faster backoff.
if resultFetches.Add(1) <= opts.resultNotReady {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Retry-After", "0")
w.Header().Set("Retry-After", "1")
w.WriteHeader(http.StatusAccepted)
_, _ = w.Write([]byte(`{"result_id":"rslt1","status":"processing"}`))
return
Expand Down
Loading