Skip to content

Commit e593856

Browse files
committed
feat: Implement pagination for result fetching
- Implements pagination in the `Results` function to correctly handle cases where the number of results exceeds the API's page size limit (50,000). - This ensures that `--limit 0` fetches all results and that `--limit` values greater than 50,000 are respected. - The `JobStatus` function is updated to return the total result count. - All calls to `JobStatus` are updated accordingly.
1 parent 5baff7d commit e593856

4 files changed

Lines changed: 60078 additions & 39 deletions

File tree

cmd/results.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func resultsCmd(args []string, baseCfg splunk.Config) error {
3333
printDebugConfig(&baseCfg, client.Log)
3434
}
3535

36-
done, jobState, _, err := client.JobStatus(*sid)
36+
done, jobState, _, _, err := client.JobStatus(*sid)
3737
if err != nil {
3838
return err
3939
}

cmd/status.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func statusCmd(args []string, baseCfg splunk.Config) error {
3232
printDebugConfig(&baseCfg, client.Log)
3333
}
3434

35-
done, jobState, _, err := client.JobStatus(*sid)
35+
done, jobState, _, _, err := client.JobStatus(*sid)
3636
if err != nil {
3737
return err
3838
}

splunk/client.go

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package splunk
22

33
import (
4-
"bytes"
54
"context"
65
"crypto/tls"
76
"encoding/json"
@@ -202,17 +201,17 @@ type SplunkMessage struct {
202201
}
203202

204203
// JobStatus retrieves the current status of a search job.
205-
func (c *Client) JobStatus(sid string) (bool, string, []SplunkMessage, error) {
204+
func (c *Client) JobStatus(sid string) (bool, string, []SplunkMessage, int, error) {
206205
endpoint, err := c.createAPIURL("search", "jobs", sid)
207206
if err != nil {
208-
return false, "", nil, err
207+
return false, "", nil, 0, err
209208
}
210209
c.Log.Debugf(`Request: GET %s
211210
`, endpoint)
212211

213212
req, err := http.NewRequest("GET", endpoint, nil)
214213
if err != nil {
215-
return false, "", nil, err
214+
return false, "", nil, 0, err
216215
}
217216

218217
q := req.URL.Query()
@@ -221,12 +220,12 @@ func (c *Client) JobStatus(sid string) (bool, string, []SplunkMessage, error) {
221220

222221
resp, err := c.doRequest(req)
223222
if err != nil {
224-
return false, "", nil, err
223+
return false, "", nil, 0, err
225224
}
226225
defer resp.Body.Close()
227226

228227
if err := c.handleFailedResponse(resp, http.StatusOK); err != nil {
229-
return false, "", nil, err
228+
return false, "", nil, 0, err
230229
}
231230

232231
var status struct {
@@ -235,25 +234,27 @@ func (c *Client) JobStatus(sid string) (bool, string, []SplunkMessage, error) {
235234
IsDone bool `json:"isDone"`
236235
DispatchState string `json:"dispatchState"`
237236
Messages []SplunkMessage `json:"messages"`
237+
ResultCount int `json:"resultCount"`
238238
} `json:"content"`
239239
} `json:"entry"`
240240
}
241241
bodyBytes, err := io.ReadAll(resp.Body)
242242
if err != nil {
243-
return false, "", nil, fmt.Errorf(`failed to read job status response body: %w`, err)
243+
return false, "", nil, 0, fmt.Errorf(`failed to read job status response body: %w`, err)
244244
}
245245

246246
if err := json.Unmarshal(bodyBytes, &status); err != nil {
247-
return false, "", nil, fmt.Errorf(`failed to decode job status JSON: %w. Received: %s`, err, string(bodyBytes))
247+
return false, "", nil, 0, fmt.Errorf(`failed to decode job status JSON: %w. Received: %s`, err, string(bodyBytes))
248248
}
249249

250250
if len(status.Entry) == 0 {
251-
return false, "", nil, errors.New("job status not found in response")
251+
return false, "", nil, 0, errors.New("job status not found in response")
252252
}
253253
content := status.Entry[0].Content
254-
return content.IsDone, content.DispatchState, content.Messages, nil
254+
return content.IsDone, content.DispatchState, content.Messages, content.ResultCount, nil
255255
}
256256

257+
257258
// WaitForJob waits for a job to finish, with a timeout.
258259
func (c *Client) WaitForJob(ctx context.Context, sid string) error {
259260
c.Log.Println("Waiting for job to complete...")
@@ -265,7 +266,7 @@ func (c *Client) WaitForJob(ctx context.Context, sid string) error {
265266
case <-ctx.Done():
266267
return ctx.Err()
267268
case <-ticker.C:
268-
done, jobState, messages, err := c.JobStatus(sid)
269+
done, jobState, messages, _, err := c.JobStatus(sid)
269270
if err != nil {
270271
return err
271272
}
@@ -291,47 +292,84 @@ func (c *Client) WaitForJob(ctx context.Context, sid string) error {
291292
}
292293
}
293294

294-
// Results fetches the results of a completed search job.
295+
// Results fetches the results of a completed search job, handling pagination.
295296
func (c *Client) Results(sid string, limit int) (string, error) {
296-
endpoint, err := c.createAPIURL("search", "jobs", sid, "results")
297+
// 1. Get the total number of results for the job
298+
_, _, _, totalResults, err := c.JobStatus(sid)
297299
if err != nil {
298-
return "", err
300+
return "", fmt.Errorf("could not get job status before fetching results: %w", err)
299301
}
300-
c.Log.Debugf(`Request: GET %s
301-
`,
302-
endpoint)
303302

304-
req, err := http.NewRequest("GET", endpoint, nil)
305-
if err != nil {
306-
return "", err
303+
// 2. Determine the number of results to fetch
304+
fetchCount := limit
305+
if limit == 0 || (limit > 0 && limit > totalResults) {
306+
fetchCount = totalResults
307307
}
308-
q := req.URL.Query()
309-
q.Add("output_mode", "json")
310-
q.Add("count", fmt.Sprintf("%d", limit))
311-
req.URL.RawQuery = q.Encode()
312308

313-
resp, err := c.doRequest(req)
314-
if err != nil {
315-
return "", err
309+
// 3. Fetch results, with pagination if necessary
310+
const maxCount = 50000 // Max results per request
311+
var allResults []json.RawMessage
312+
313+
for offset := 0; offset < fetchCount; offset += maxCount {
314+
// Determine count for this specific request
315+
count := maxCount
316+
if offset+count > fetchCount {
317+
count = fetchCount - offset
318+
}
319+
320+
// Prepare request
321+
endpoint, err := c.createAPIURL("search", "jobs", sid, "results")
322+
if err != nil {
323+
return "", err
324+
}
325+
c.Log.Debugf(`Request: GET %s (offset: %d, count: %d)
326+
`, endpoint, offset, count)
327+
328+
req, err := http.NewRequest("GET", endpoint, nil)
329+
if err != nil {
330+
return "", err
331+
}
332+
q := req.URL.Query()
333+
q.Add("output_mode", "json")
334+
q.Add("offset", fmt.Sprintf("%d", offset))
335+
q.Add("count", fmt.Sprintf("%d", count))
336+
req.URL.RawQuery = q.Encode()
337+
338+
// Execute request
339+
resp, err := c.doRequest(req)
340+
if err != nil {
341+
return "", err
342+
}
343+
defer resp.Body.Close()
344+
345+
if err := c.handleFailedResponse(resp, http.StatusOK); err != nil {
346+
return "", err
347+
}
348+
349+
// Decode and append results
350+
var page struct {
351+
Results []json.RawMessage `json:"results"`
352+
}
353+
if err := json.NewDecoder(resp.Body).Decode(&page); err != nil {
354+
return "", fmt.Errorf("failed to decode results page: %w", err)
355+
}
356+
allResults = append(allResults, page.Results...)
316357
}
317-
defer resp.Body.Close()
318358

319-
if err := c.handleFailedResponse(resp, http.StatusOK); err != nil {
320-
return "", err
359+
// 4. Combine and format the final JSON output
360+
finalJSON := map[string][]json.RawMessage{
361+
"results": allResults,
321362
}
322363

323-
body, err := io.ReadAll(resp.Body)
364+
prettyJSON, err := json.MarshalIndent(finalJSON, "", " ")
324365
if err != nil {
325-
return "", err
366+
return "", fmt.Errorf("failed to marshal final results: %w", err)
326367
}
327368

328-
var prettyJSON bytes.Buffer
329-
if err := json.Indent(&prettyJSON, body, "", " "); err != nil {
330-
return string(body), nil
331-
}
332-
return prettyJSON.String(), nil
369+
return string(prettyJSON), nil
333370
}
334371

372+
335373
// CancelSearch sends a request to cancel a running job.
336374
func (c *Client) CancelSearch(sid string) error {
337375
c.Log.Println(`

0 commit comments

Comments
 (0)