Ver código fonte

add arango package with aql generation

Aneurin Barker Snook 1 ano atrás
commit
fa4f403c46
5 arquivos alterados com 198 adições e 0 exclusões
  1. 3 0
      go.mod
  2. 22 0
      params.go
  3. 35 0
      params_test.go
  4. 87 0
      query.go
  5. 51 0
      query_test.go

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module github.com/recipeer/go/arango
+
+go 1.21.1

+ 22 - 0
params.go

@@ -0,0 +1,22 @@
+package arango
+
+import (
+	"regexp"
+)
+
+// https://docs.arangodb.com/3.11/aql/fundamentals/bind-parameters/#syntax
+var paramRegexp = regexp.MustCompile("@{1,2}([A-z0-9_]+)")
+
+// ReadParams reads out named parameters from an AQL string.
+func ReadParams(input string) []string {
+	params := []string{}
+
+	matches := paramRegexp.FindAllStringSubmatch(input, -1)
+	if matches != nil {
+		for _, match := range matches {
+			params = append(params, match[1])
+		}
+	}
+
+	return params
+}

+ 35 - 0
params_test.go

@@ -0,0 +1,35 @@
+package arango
+
+import "testing"
+
+func TestReadParams(t *testing.T) {
+	type TestCase struct {
+		Input  string
+		Output []string
+	}
+
+	testCases := []TestCase{
+		{
+			Input:  "FOR doc IN @@collection FILTER doc.title == @title RETURN doc",
+			Output: []string{"collection", "title"},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Log("Input:", testCase.Input)
+		t.Log("Expected output:", testCase.Output)
+
+		params := ReadParams(testCase.Input)
+
+		if len(params) != len(testCase.Output) {
+			t.Errorf("Expected %d parameters", len(testCase.Output))
+			break
+		}
+
+		for i, name := range testCase.Output {
+			if params[i] != name {
+				t.Errorf("Expected parameter %d to be %q", i, name)
+			}
+		}
+	}
+}

+ 87 - 0
query.go

@@ -0,0 +1,87 @@
+package arango
+
+import (
+	"strings"
+)
+
+// Query provides a simple way to build ArangoDB queries.
+type Query struct {
+	Lines  []string
+	Params map[string]any
+}
+
+// Append adds a line to the query, attaching (optional) values to bind parameters.
+//
+// Any variables given are assigned to bind parameters in the same order as they appear in the line.
+// Keep in mind the following behaviours:
+//
+//   - If more variables are provided than there are parameters in the line, leftover variables are discarded
+//   - If more parameters are used than variables are given, remaining parameters are left unmapped
+//   - Reused parameters are overwritten
+func (query *Query) Append(line string, values ...any) *Query {
+	var params map[string]any = nil
+
+	names := ReadParams(line)
+	if len(names) > 0 {
+		params = map[string]any{}
+		for i, name := range names {
+			if i > len(values) {
+				break
+			}
+			params[name] = values[i]
+		}
+	}
+
+	query.Lines = append(query.Lines, line)
+	return query.AssignMap(params)
+}
+
+// Assign assigns a value to a single bind parameter.
+func (query *Query) Assign(name string, value any) *Query {
+	query.Params[name] = value
+	return query
+}
+
+// AssignMap assigns values to bind parameters.
+func (query *Query) AssignMap(params map[string]any) *Query {
+	if params != nil {
+		for name, value := range params {
+			query.Params[name] = value
+		}
+	}
+	return query
+}
+
+// Copy creates a copy of the query.
+func (query *Query) Copy() *Query {
+	newQuery := NewQuery()
+	for _, line := range query.Lines {
+		newQuery.Lines = append(newQuery.Lines, line)
+	}
+	for name, value := range query.Params {
+		newQuery.Params[name] = value
+	}
+	return newQuery
+}
+
+// L (for "Line") is a shorthand for Append.
+func (query *Query) L(line string, values ...any) *Query {
+	return query.Append(line, values...)
+}
+
+// P (for "Parameter") is a shorthand for Assign.
+func (query *Query) P(name string, value any) *Query {
+	return query.Assign(name, value)
+}
+
+func (query *Query) String() string {
+	return strings.Join(query.Lines, "\n")
+}
+
+// NewQuery creates a new Query.
+func NewQuery() *Query {
+	return &Query{
+		Lines:  []string{},
+		Params: map[string]any{},
+	}
+}

+ 51 - 0
query_test.go

@@ -0,0 +1,51 @@
+package arango
+
+import (
+	"testing"
+)
+
+func TestQueryAppend(t *testing.T) {
+	type TestCase struct {
+		Input          *Query
+		ExpectedStr    string
+		ExpectedParams map[string]any
+	}
+
+	testCases := []TestCase{
+		{
+			Input: NewQuery().
+				Append("FOR doc IN @@collection", "recipes").
+				Append("FILTER doc.title == @title", "Spaghetti").
+				Append("RETURN doc"),
+			ExpectedStr: `FOR doc IN @@collection
+FILTER doc.title == @title
+RETURN doc`,
+			ExpectedParams: map[string]any{
+				"collection": "recipes",
+				"title":      "Spaghetti",
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		actualStr := testCase.Input.String()
+
+		if actualStr != testCase.ExpectedStr {
+			t.Logf("Expected: %q", testCase.ExpectedStr)
+			t.Logf("Actual: %q", actualStr)
+			t.Fail()
+		}
+
+		if len(testCase.Input.Params) != len(testCase.ExpectedParams) {
+			t.Errorf("Expected %d parameters; got %d", len(testCase.ExpectedParams), len(testCase.Input.Params))
+		}
+
+		for name, value := range testCase.ExpectedParams {
+			if testCase.Input.Params[name] == nil {
+				t.Errorf("Expected parameter %q to be %q; got nil", name, value)
+			} else if testCase.Input.Params[name] != value {
+				t.Errorf("Expected parameter %q to be %q; got %q", name, value, testCase.Input.Params[name])
+			}
+		}
+	}
+}