From 19e1d8ab3bc96c8b4488f21ceacdf565ef7aa034 Mon Sep 17 00:00:00 2001 From: Lukas Malkmus Date: Sat, 10 Aug 2024 15:40:54 +0200 Subject: [PATCH] feat(query): go 1.23 iterator support --- .github/workflows/test_examples.yaml | 2 +- README.md | 1 - axiom/query/result.go | 15 ------ axiom/query/result_iter_go122.go | 21 +++++++++ axiom/query/result_iter_go123.go | 21 +++++++++ axiom/query/row.go | 37 --------------- .../query/{row_test.go => row_go122_test.go} | 2 + axiom/query/row_go123_test.go | 45 ++++++++++++++++++ axiom/query/row_iter_go122.go | 40 ++++++++++++++++ axiom/query/row_iter_go123.go | 39 +++++++++++++++ examples/query/{main.go => main_go122.go} | 2 + examples/query/main_go123.go | 47 +++++++++++++++++++ 12 files changed, 218 insertions(+), 54 deletions(-) create mode 100644 axiom/query/result_iter_go122.go create mode 100644 axiom/query/result_iter_go123.go rename axiom/query/{row_test.go => row_go122_test.go} (98%) create mode 100644 axiom/query/row_go123_test.go create mode 100644 axiom/query/row_iter_go122.go create mode 100644 axiom/query/row_iter_go123.go rename examples/query/{main.go => main_go122.go} (98%) create mode 100644 examples/query/main_go123.go diff --git a/.github/workflows/test_examples.yaml b/.github/workflows/test_examples.yaml index 1c7f1299..8c21d073 100644 --- a/.github/workflows/test_examples.yaml +++ b/.github/workflows/test_examples.yaml @@ -101,7 +101,7 @@ jobs: if: matrix.setup run: ${{ matrix.setup }} - name: Run example - run: go run ./examples/${{ matrix.example }}/main.go + run: go run ./examples/${{ matrix.example }} - name: Verify example if: matrix.verify run: ${{ matrix.verify }} diff --git a/README.md b/README.md index d91b76be..7ac3372a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ [![Latest Release][release_badge]][release] [![License][license_badge]][license] - [Axiom](https://axiom.co) unlocks observability at any scale. - **Ingest with ease, store without limits:** Axiom's next-generation datastore diff --git a/axiom/query/result.go b/axiom/query/result.go index e46d531a..9a6c6c7d 100644 --- a/axiom/query/result.go +++ b/axiom/query/result.go @@ -1,11 +1,8 @@ package query import ( - "context" "encoding/json" "time" - - "github.com/axiomhq/axiom-go/axiom/query/iter" ) // Result is the result of an APL query. @@ -46,11 +43,6 @@ type Table struct { Columns []Column `json:"columns"` } -// Rows returns an iterator over the rows build from the columns the table. -func (t Table) Rows() iter.Iter[Row] { - return Rows(t.Columns) -} - // Source that was consulted in order to create a [Table]. type Source struct { // Name of the source. @@ -99,13 +91,6 @@ type Buckets struct { // Column in a [Table] containing the raw values of a [Field]. type Column []any -// Values returns an iterator over the values of the column. -func (c Column) Values() iter.Iter[any] { - return iter.Slice(c, func(_ context.Context, v any) (any, error) { - return v, nil - }) -} - // Status of an APL query [Result]. type Status struct { // MinCursor is the id of the oldest row, as seen server side. May be lower diff --git a/axiom/query/result_iter_go122.go b/axiom/query/result_iter_go122.go new file mode 100644 index 00000000..7e40c40b --- /dev/null +++ b/axiom/query/result_iter_go122.go @@ -0,0 +1,21 @@ +//go:build !go1.23 + +package query + +import ( + "context" + + "github.com/axiomhq/axiom-go/axiom/query/iter" +) + +// Rows returns an iterator over the rows build from the columns the table. +func (t Table) Rows() iter.Iter[Row] { + return Rows(t.Columns) +} + +// Values returns an iterator over the values of the column. +func (c Column) Values() iter.Iter[any] { + return iter.Slice(c, func(_ context.Context, v any) (any, error) { + return v, nil + }) +} diff --git a/axiom/query/result_iter_go123.go b/axiom/query/result_iter_go123.go new file mode 100644 index 00000000..bf7238cf --- /dev/null +++ b/axiom/query/result_iter_go123.go @@ -0,0 +1,21 @@ +//go:build go1.23 + +package query + +import "iter" + +// Rows returns an iterator over the rows build from the columns the table. +func (t Table) Rows() iter.Seq[Row] { + return Rows(t.Columns) +} + +// Values returns an iterator over the values of the column. +func (c Column) Values() iter.Seq[any] { + return func(yield func(any) bool) { + for _, v := range c { + if !yield(v) { + return + } + } + } +} diff --git a/axiom/query/row.go b/axiom/query/row.go index 207041f6..a2d01a73 100644 --- a/axiom/query/row.go +++ b/axiom/query/row.go @@ -1,41 +1,4 @@ package query -import ( - "context" - - "github.com/axiomhq/axiom-go/axiom/query/iter" -) - // Row represents a single row of a tabular query [Result]. type Row []any - -// Values returns an iterator over the values of the row. -func (r Row) Values() iter.Iter[any] { - return iter.Slice(r, func(_ context.Context, v any) (any, error) { - return v, nil - }) -} - -// Rows returns an iterator over the rows build from the columns of a tabular -// query [Result]. -func Rows(columns []Column) iter.Iter[Row] { - // Return an empty iterator if there are no columns or column values. - if len(columns) == 0 || len(columns[0]) == 0 { - return func(context.Context) (Row, error) { - return nil, iter.Done - } - } - - return iter.Range(0, len(columns[0]), func(_ context.Context, idx int) (Row, error) { - if idx >= len(columns[0]) { - return nil, iter.Done - } - - row := make(Row, len(columns)) - for columnIdx, column := range columns { - row[columnIdx] = column[idx] - } - - return row, nil - }) -} diff --git a/axiom/query/row_test.go b/axiom/query/row_go122_test.go similarity index 98% rename from axiom/query/row_test.go rename to axiom/query/row_go122_test.go index 219df475..c05de0ad 100644 --- a/axiom/query/row_test.go +++ b/axiom/query/row_go122_test.go @@ -1,3 +1,5 @@ +//go:build !go1.23 + package query_test import ( diff --git a/axiom/query/row_go123_test.go b/axiom/query/row_go123_test.go new file mode 100644 index 00000000..e4be5a34 --- /dev/null +++ b/axiom/query/row_go123_test.go @@ -0,0 +1,45 @@ +//go:build go1.23 + +package query_test + +import ( + "fmt" + "strings" + + "github.com/axiomhq/axiom-go/axiom/query" +) + +func ExampleRows() { + columns := []query.Column{ + []any{ + "2020-11-19T11:06:31.569475746Z", + "2020-11-19T11:06:31.569479846Z", + }, + []any{ + "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)", + "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)", + }, + []any{ + "93.180.71.3", + "93.180.71.3", + }, + []any{ + "GET /downloads/product_1 HTTP/1.1", + "GET /downloads/product_1 HTTP/1.1", + }, + []any{ + 304, + 304, + }, + } + + var buf strings.Builder + for row := range query.Rows(columns) { + _, _ = fmt.Fprintln(&buf, row) + } + + // Output: + // [2020-11-19T11:06:31.569475746Z Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21) 93.180.71.3 GET /downloads/product_1 HTTP/1.1 304] + // [2020-11-19T11:06:31.569479846Z Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21) 93.180.71.3 GET /downloads/product_1 HTTP/1.1 304] + fmt.Print(buf.String()) +} diff --git a/axiom/query/row_iter_go122.go b/axiom/query/row_iter_go122.go new file mode 100644 index 00000000..fdf42d90 --- /dev/null +++ b/axiom/query/row_iter_go122.go @@ -0,0 +1,40 @@ +//go:build !go1.23 + +package query + +import ( + "context" + + "github.com/axiomhq/axiom-go/axiom/query/iter" +) + +// Values returns an iterator over the values of the row. +func (r Row) Values() iter.Iter[any] { + return iter.Slice(r, func(_ context.Context, v any) (any, error) { + return v, nil + }) +} + +// Rows returns an iterator over the rows build from the columns of a tabular +// query [Result]. +func Rows(columns []Column) iter.Iter[Row] { + // Return an empty iterator if there are no columns or column values. + if len(columns) == 0 || len(columns[0]) == 0 { + return func(context.Context) (Row, error) { + return nil, iter.Done + } + } + + return iter.Range(0, len(columns[0]), func(_ context.Context, idx int) (Row, error) { + if idx >= len(columns[0]) { + return nil, iter.Done + } + + row := make(Row, len(columns)) + for columnIdx, column := range columns { + row[columnIdx] = column[idx] + } + + return row, nil + }) +} diff --git a/axiom/query/row_iter_go123.go b/axiom/query/row_iter_go123.go new file mode 100644 index 00000000..236fdc5c --- /dev/null +++ b/axiom/query/row_iter_go123.go @@ -0,0 +1,39 @@ +//go:build go1.23 + +package query + +import ( + "iter" +) + +// Values returns an iterator over the values of the row. +func (r Row) Values() iter.Seq[any] { + return func(yield func(any) bool) { + for _, v := range r { + if !yield(v) { + return + } + } + } +} + +// Rows returns an iterator over the rows build from the columns of a tabular +// query [Result]. +func Rows(columns []Column) iter.Seq[Row] { + // Return an empty iterator if there are no columns or column values. + if len(columns) == 0 || len(columns[0]) == 0 { + return func(func(Row) bool) {} + } + + return func(yield func(Row) bool) { + for i := range columns[0] { + row := make(Row, len(columns)) + for j, column := range columns { + row[j] = column[i] + } + if !yield(row) { + return + } + } + } +} diff --git a/examples/query/main.go b/examples/query/main_go122.go similarity index 98% rename from examples/query/main.go rename to examples/query/main_go122.go index a9f5fb29..910c6be9 100644 --- a/examples/query/main.go +++ b/examples/query/main_go122.go @@ -1,3 +1,5 @@ +//go:build !go1.23 + // The purpose of this example is to show how to query a dataset using the Axiom // Processing Language (APL). package main diff --git a/examples/query/main_go123.go b/examples/query/main_go123.go new file mode 100644 index 00000000..687b91f4 --- /dev/null +++ b/examples/query/main_go123.go @@ -0,0 +1,47 @@ +//go:build go1.23 + +// The purpose of this example is to show how to query a dataset using the Axiom +// Processing Language (APL). +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/axiomhq/axiom-go/axiom" +) + +func main() { + // Export "AXIOM_DATASET" in addition to the required environment variables. + + dataset := os.Getenv("AXIOM_DATASET") + if dataset == "" { + log.Fatal("AXIOM_DATASET is required") + } + + ctx := context.Background() + + // 1. Initialize the Axiom API client. + client, err := axiom.NewClient() + if err != nil { + log.Fatal(err) + } + + // 2. Query all events using APL ⚡ + apl := fmt.Sprintf("['%s']", dataset) // E.g. ['test'] + res, err := client.Query(ctx, apl) + if err != nil { + log.Fatal(err) + } else if res.Status.RowsMatched == 0 { + log.Fatal("No matches found") + } + + // 3. Print the queried results by creating a iterator for the rows from the + // tabular query result (as it is organized in columns) and iterating over + // the rows. + for row := range res.Tables[0].Rows() { + _, _ = fmt.Println(row) + } +}