Skip to content

Commit 947a271

Browse files
Set converter defaults once in Convert method (#21)
Co-authored-by: Erik Dubbelboer <[email protected]>
1 parent 289c9a4 commit 947a271

File tree

3 files changed

+81
-8
lines changed

3 files changed

+81
-8
lines changed

filter/converter.go

+18-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"sort"
99
"strings"
10+
"sync"
1011
)
1112

1213
var basicOperatorMap = map[string]string{
@@ -19,11 +20,12 @@ var basicOperatorMap = map[string]string{
1920
"$regex": "~*",
2021
}
2122

22-
// DefaultPlaceholderName is the default placeholder name used in the generated SQL query.
23+
// defaultPlaceholderName is the default placeholder name used in the generated SQL query.
2324
// This name should not be used in the database or any JSONB column. It can be changed using
2425
// the WithPlaceholderName option.
25-
const DefaultPlaceholderName = "__filter_placeholder"
26+
const defaultPlaceholderName = "__filter_placeholder"
2627

28+
// Converter converts MongoDB filter queries to SQL conditions and values. Use [filter.NewConverter] to create a new instance.
2729
type Converter struct {
2830
nestedColumn string
2931
nestedExemptions []string
@@ -33,23 +35,22 @@ type Converter struct {
3335
}
3436
emptyCondition string
3537
placeholderName string
38+
39+
once sync.Once
3640
}
3741

38-
// NewConverter creates a new Converter with optional nested JSONB field mapping.
42+
// NewConverter creates a new [Converter] with optional nested JSONB field mapping.
3943
//
40-
// Note: When using github.com/lib/pq, the filter.WithArrayDriver should be set to pq.Array.
44+
// Note: When using https://github.com/lib/pq, the [filter.WithArrayDriver] should be set to pq.Array.
4145
func NewConverter(options ...Option) *Converter {
4246
converter := &Converter{
43-
emptyCondition: "FALSE",
47+
// don't set defaults, use the once.Do in #Convert()
4448
}
4549
for _, option := range options {
4650
if option != nil {
4751
option(converter)
4852
}
4953
}
50-
if converter.placeholderName == "" {
51-
converter.placeholderName = DefaultPlaceholderName
52-
}
5354
return converter
5455
}
5556

@@ -58,6 +59,15 @@ func NewConverter(options ...Option) *Converter {
5859
// startAtParameterIndex is the index to start the parameter numbering at.
5960
// Passing X will make the first indexed parameter $X, the second $X+1, and so on.
6061
func (c *Converter) Convert(query []byte, startAtParameterIndex int) (conditions string, values []any, err error) {
62+
c.once.Do(func() {
63+
if c.emptyCondition == "" {
64+
c.emptyCondition = "FALSE"
65+
}
66+
if c.placeholderName == "" {
67+
c.placeholderName = defaultPlaceholderName
68+
}
69+
})
70+
6171
if startAtParameterIndex < 1 {
6272
return "", nil, fmt.Errorf("startAtParameterIndex must be greater than 0")
6373
}

filter/converter_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
11
package filter_test
22

33
import (
4+
"database/sql"
45
"fmt"
56
"reflect"
67
"testing"
78

89
"github.com/poki/mongodb-filter-to-postgres/filter"
910
)
1011

12+
func ExampleNewConverter() {
13+
// Remeber to use `filter.WithArrayDriver(pg.Array)` when using github.com/lib/pq
14+
converter := filter.NewConverter(filter.WithNestedJSONB("meta", "created_at", "updated_at"))
15+
16+
mongoFilterQuery := `{
17+
"name": "John",
18+
"created_at": {
19+
"$gte": "2020-01-01T00:00:00Z"
20+
}
21+
}`
22+
conditions, values, err := converter.Convert([]byte(mongoFilterQuery), 1)
23+
if err != nil {
24+
// handle error
25+
}
26+
27+
var db *sql.DB // setup your database connection
28+
29+
_, _ = db.Query("SELECT * FROM users WHERE "+conditions, values)
30+
// SELECT * FROM users WHERE (("created_at" >= $1) AND ("meta"->>'name' = $2)), 2020-01-01T00:00:00Z, "John"
31+
}
32+
1133
func TestConverter_Convert(t *testing.T) {
1234
tests := []struct {
1335
name string
@@ -402,3 +424,39 @@ func TestConverter_WithEmptyCondition(t *testing.T) {
402424
t.Errorf("Converter.Convert() values = %v, want nil", values)
403425
}
404426
}
427+
428+
func TestConverter_NoConstructor(t *testing.T) {
429+
c := &filter.Converter{}
430+
conditions, values, err := c.Convert([]byte(`{"name": "John"}`), 1)
431+
if err != nil {
432+
t.Fatal(err)
433+
}
434+
if want := `("name" = $1)`; conditions != want {
435+
t.Errorf("Converter.Convert() conditions = %v, want %v", conditions, want)
436+
}
437+
if !reflect.DeepEqual(values, []any{"John"}) {
438+
t.Errorf("Converter.Convert() values = %v, want %v", values, []any{"John"})
439+
}
440+
441+
conditions, values, err = c.Convert([]byte(``), 1)
442+
if err != nil {
443+
t.Fatal(err)
444+
}
445+
if want := "FALSE"; conditions != want {
446+
t.Errorf("Converter.Convert() conditions = %v, want %v", conditions, want)
447+
}
448+
if len(values) != 0 {
449+
t.Errorf("Converter.Convert() values = %v, want nil", values)
450+
}
451+
}
452+
453+
func TestConverter_CopyReference(t *testing.T) {
454+
c := filter.Converter{}
455+
conditions, _, err := c.Convert([]byte(``), 1)
456+
if err != nil {
457+
t.Fatal(err)
458+
}
459+
if want := "FALSE"; conditions != want {
460+
t.Errorf("Converter.Convert() conditions = %v, want %v", conditions, want)
461+
}
462+
}

filter/doc.go

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// This package converts MongoDB query filters into PostgreSQL WHERE clauses.
2+
// It's designed to be simple, secure, and free of dependencies.
3+
//
4+
// See: https://www.mongodb.com/docs/compass/current/query/filter
5+
package filter

0 commit comments

Comments
 (0)