8000 Allow passing Query-Options and SubOptions for Queries in ArangoDB Datsource by coolwednesday · Pull Request #1711 · gofr-dev/gofr · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Allow passing Query-Options and SubOptions for Queries i 8000 n ArangoDB Datsource #1711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 15, 2025
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 docs/datasources/arangodb/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type ArangoDB interface {
GetEdges(ctx context.Context, dbName, graphName, edgeCollection, vertexID string, resp any) error

// Query executes an AQL query and binds the results
Query(ctx context.Context, dbName string, query string, bindVars map[string]any, result any) error
Query(ctx context.Context, dbName string, query string, bindVars map[string]any, result any, options ...map[string]any) error

HealthCheck(context.Context) (any, error)
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/gofr/container/datasources.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,10 +665,12 @@ type ArangoDB interface {
// - query: AQL query string to be executed.
// - bindVars: Map of bind variables to be used in the query.
// - result: Pointer to a slice of maps where the query results will be stored.
// - options : A flexible map[string]any to customize query behavior. Keys should be in camelCase
// and correspond to fields in ArangoDB’s QueryOptions and QuerySubOptions structs.
//
// Returns an error if the database connection fails, the query execution fails, or
// the result parameter is not a pointer to a slice of maps.
Query(ctx context.Context, dbName string, query string, bindVars map[string]any, result any) error
Query(ctx context.Context, dbName string, query string, bindVars map[string]any, result any, options ...map[string]any) error

HealthChecker
}
Expand Down
26 changes: 18 additions & 8 deletions pkg/gofr/container/mock_datasources.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

122 changes: 112 additions & 10 deletions pkg/gofr/datasource/arangodb/arango.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package arangodb

import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
Expand Down Expand Up @@ -153,18 +154,85 @@ func (c *Client) validateConfig() error {
return nil
}

// Query executes an AQL query and binds the results.
// Query executes an AQL (ArangoDB Query Language) query on the specified database and stores the result.
//
// Parameters:
// - ctx: Request context for tracing and cancellation.
// - dbName: Name of the database where the query will be executed.
// - query: AQL query string to be executed.
// - bindVars: Map of bind variables to be used in the query.
// - result: Pointer to a slice of maps where the query results will be stored.
// - ctx: Context for request-scoped values, cancellation, and tracing.
// - dbName: Name of the ArangoDB database where the query will be executed.
// - query: The AQL query string to execute.
// - bindVars: Map of bind parameters used in the AQL query.
// - result: Pointer to a slice of maps where the query results will be unmarshaled.
// Must be a valid pointer to avoid runtime errors.
// - options: A flexible map[string]any to customize query behavior. Keys should be in camelCase
// and correspond to fields in ArangoDB’s QueryOptions and QuerySubOptions structs.
//
// Returns an error if the database connection fails, the query execution fails, or the
// result parameter is not a pointer to a slice of maps.
func (c *Client) Query(ctx context.Context, dbName, query string, bindVars map[string]any, result any) error {
// Available option keys include (but are not limited to):
//
// QueryOptions:
// - count (bool): Include the total number of results in the result set.
// - batchSize (int): Number of results to return per batch.
// - cache (bool): Whether to cache the query results.
// - memoryLimit (int64): Maximum memory in bytes for query execution.
// - ttl (float64): Time-to-live for the cursor in seconds.
// - options (map[string]any): Nested options from QuerySubOptions.
//
// QuerySubOptions:
// - allowDirtyReads (bool)
// - allowRetry (bool)
// - failOnWarning (*bool)
// - fullCount (bool): Return full count ignoring LIMIT clause.
// - optimizer (map[string]any): Optimizer-specific directives.
// - maxRuntime (float64): Maximum query runtime in seconds.
// - stream (bool): Enable streaming cursor.
// - profile (uint): Enable query profiling (0-2).
// - skipInaccessibleCollections (*bool)
// - intermediateCommitCount (*int)
// - intermediateCommitSize (*int)
// - maxDNFConditionMembers (*int)
// - maxNodesPerCallstack (*int)
// - maxNumberOfPlans (*int)
// - maxTransactionSize (*int)
// - maxWarningCount (*int)
// - satelliteSyncWait (float64)
// - spillOverThresholdMemoryUsage (*int)
// - spillOverThresholdNumRows (*int)
// - maxPlans (int)
// - shardIds ([]string)
8000 // - forceOneShardAttributeValue (*string)
//
// Returns an error if:
// - The database connection fails.
// - The query execution fails.
// - The result parameter is not a valid pointer to a slice of maps.
//
// Example:
//
// var results []map[string]interface{}
//
// query := `FOR u IN users FILTER u.age > @minAge RETURN u`
//
// bindVars := map[string]interface{}{
// "minAge": 21,
// }
//
// options := map[string]any{
// "count": true,
// "batchSize": 100,
// "options": map[string]any{
// "fullCount": true,
// "profile": 2,
// },
// }
//
// err := Query(ctx, "myDatabase", query, bindVars, &results, options)
// if err != nil {
// log.Fatalf("Query failed: %v", err)
// }
//
// for _, doc := range results {
// fmt.Printf("User: %+v\n", doc)
// }
func (c *Client) Query(ctx context.Context, dbName, query string, bindVars map[string]any, result any, options ...map[string]any) error {
tracerCtx, span := c.addTrace(ctx, "query", map[string]string{"DB": dbName})
startTime := time.Now()

Expand All @@ -176,7 +244,17 @@ func (c *Client) Query(ctx context.Context, dbName, query string, bindVars map[s
return err
}

cursor, err := db.Query(tracerCtx, query, &arangodb.QueryOptions{BindVars: bindVars})
var queryOptions arangodb.QueryOptions

err = bindQueryOptions(&queryOptions, options)
if err != nil {
return err
}

queryOptions.BindVars = bindVars

cursor, err := db.Query(tracerCtx, query, &queryOptions)

if err != nil {
return err
}
Expand Down Expand Up @@ -206,6 +284,30 @@ func (c *Client) Query(ctx context.Context, dbName, query string, bindVars map[s
return nil
}

func bindQueryOptions(queryOptions *arangodb.QueryOptions, options []map[string]any) error {
if len(options) > 0 {
// Merge all options into a single map
mergedOpts := make(map[string]any)

for _, opts := range options {
for k, v := range opts {
mergedOpts[k] = v
}
}

bytes, err := json.Marshal(mergedOpts)
if err != nil {
return err
}

if err := json.Unmarshal(bytes, &queryOptions); err != nil {
return err
}
}

return nil
}

// addTrace adds tracing to context if tracer is configured.
func (c *Client) addTrace(ctx context.Context, operation string, attributes map[string]string) (context.Context, trace.Span) {
if c.tracer != nil {
Expand Down
8 changes: 0 additions & 8 deletions pkg/gofr/datasource/arangodb/arango_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,6 @@ func (c *Client) database(ctx context.Context, name string) (arangodb.Database,
return c.client.Database(ctx, name)
}

func (c *Client) databases(ctx context.Context) ([]arangodb.Database, error) {
return c.client.Databases(ctx)
}

func (c *Client) version(ctx context.Context) (arangodb.VersionInfo, error) {
return c.client.Version(ctx)
}

// createUser creates a new user in ArangoDB.
func (c *Client) createUser(ctx context.Context, username string, options any) error {
tracerCtx, span := c.addTrace(ctx, "createUser", map[string]string{"user": username})
Expand Down
96 changes: 96 additions & 0 deletions pkg/gofr/datasource/arangodb/arango_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,99 @@ func (*MockQueryCursor) Statistics() arangodb.CursorStats {
func (*MockQueryCursor) Plan() arangodb.CursorPlan {
return arangodb.CursorPlan{}
}

func TestClient_Query_WithBatchSizeAndFullCount(t *testing.T) {
test := setupGraphTest(t)
defer test.Ctrl.Finish()

dbName := "testDB"
query := "FOR doc IN collection RETURN doc"
bindVars := map[string]any{"key": "value"}

var result []map[string]any

expectedResult := []map[string]any{
{"_key": "doc1", "value": "v1"},
{"_key": "doc2", "value": "v2"},
}

// Define QueryOptions with batchSize and fullCount
queryOpts := map[string]any{
"batchSize": 50,
"options": map[string]any{
"fullCount": true,
},
}

test.MockArango.EXPECT().Database(test.Ctx, dbName).Return(test.MockDB, nil)

test.MockDB.EXPECT().
Query(test.Ctx, query, gomock.Any()).
DoAndReturn(func(_ context.Context, _ string, opts *arangodb.QueryOptions) (arangodb.Cursor, error) {
require.NotNil(t, opts)
require.Equal(t, 50, opts.BatchSize)
require.True(t, opts.Options.FullCount)
require.Equal(t, bindVars, opts.BindVars)

return NewMockQueryCursor(test.Ctrl, expectedResult), nil
})

err := test.Client.Query(test.Ctx, dbName, query, bindVars, &result, queryOpts)
require.NoError(t, err)
require.Equal(t, expectedResult, result)
}

func TestClient_Query_WithMaxPlans(t *testing.T) {
test := setupGraphTest(t)
defer test.Ctrl.Finish()

dbName := "testDB"
query := "FOR doc IN collection RETURN doc"
bindVars := map[string]any{"key": "value"}

var result []map[string]any

expectedResult := []map[string]any{
{"_key": "doc1", "value": "v1"},
}

// Define QueryOptions with maxPlans sub-option
queryOpts := map[string]any{
"options": map[string]any{
"maxPlans": 5,
},
}

test.MockArango.EXPECT().Database(test.Ctx, dbName).Return(test.MockDB, nil)

test.MockDB.EXPECT().
Query(test.Ctx, query, gomock.Any()).
DoAndReturn(func(_ context.Context, _ string, opts *arangodb.QueryOptions) (arangodb.Cursor, error) {
require.NotNil(t, opts)
require.Equal(t, 5, opts.Options.MaxPlans)

return NewMockQueryCursor(test.Ctrl, expectedResult), nil
})

err := test.Client.Query(test.Ctx, dbName, query, bindVars, &result, queryOpts)
require.NoError(t, err)
require.Equal(t, expectedResult, result)
}

func TestClient_Query_InvalidResultType(t *testing.T) {
test := setupGraphTest(t)
defer test.Ctrl.Finish()

dbName := "testDB"
query := "FOR doc IN collection RETURN doc"
bindVars := map[string]any{"key": "value"}

var result int // Incorrect type

test.MockArango.EXPECT().Database(test.Ctx, dbName).Return(test.MockDB, nil)
test.MockDB.EXPECT().Query(test.Ctx, query, gomock.Any()).Return(NewMockQueryCursor(test.Ctrl, nil), nil)

err := test.Client.Query(test.Ctx, dbName, query, bindVars, &result)
require.Error(t, err)
require.Equal(t, errInvalidResultType, err)
}
Loading
0