Skip to content

Commit 4c3d129

Browse files
authored
Merge pull request #383 from PDOK/cql-text-implementation
feat: setup listener to parse CQL to SQLite.
2 parents 0dc4ed5 + b686416 commit 4c3d129

File tree

152 files changed

+1128
-110
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

152 files changed

+1128
-110
lines changed

internal/engine/types/stack.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package types
2+
3+
// Stack of strings. See https://en.wikipedia.org/wiki/Stack_(abstract_data_type)
4+
type Stack struct {
5+
stack []string
6+
}
7+
8+
func NewStack() *Stack {
9+
return &Stack{make([]string, 0)}
10+
}
11+
12+
func (s *Stack) Push(value string) {
13+
s.stack = append(s.stack, value)
14+
}
15+
16+
func (s *Stack) Pop() string {
17+
length := len(s.stack)
18+
if length == 0 {
19+
return ""
20+
}
21+
value := s.stack[length-1]
22+
s.stack = s.stack[:length-1]
23+
return value
24+
}
25+
26+
func (s *Stack) PopMany(count int) []string {
27+
if count <= 1 {
28+
return nil
29+
}
30+
if count > len(s.stack) {
31+
count = len(s.stack)
32+
}
33+
items := make([]string, count)
34+
35+
// Pop in reverse to maintain the correct ordering
36+
for i := count - 1; i >= 0; i-- {
37+
items[i] = s.Pop()
38+
}
39+
return items
40+
}
41+
42+
func (s *Stack) Peek() string {
43+
length := len(s.stack)
44+
if length == 0 {
45+
return ""
46+
}
47+
return s.stack[length-1]
48+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package types
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestStack_Push(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
values []string
13+
expected []string
14+
}{
15+
{name: "single item", values: []string{"a"}, expected: []string{"a"}},
16+
{name: "multiple items", values: []string{"a", "b", "c"}, expected: []string{"a", "b", "c"}},
17+
}
18+
19+
for _, tt := range tests {
20+
t.Run(tt.name, func(t *testing.T) {
21+
stack := NewStack()
22+
for _, v := range tt.values {
23+
stack.Push(v)
24+
}
25+
26+
require.Equal(t, tt.expected, stack.stack)
27+
})
28+
}
29+
}
30+
31+
func TestStack_Pop(t *testing.T) {
32+
tests := []struct {
33+
name string
34+
initialStack []string
35+
popResult string
36+
finalStack []string
37+
}{
38+
{name: "pop from empty stack", initialStack: []string{}, popResult: "", finalStack: []string{}},
39+
{name: "pop last item", initialStack: []string{"a"}, popResult: "a", finalStack: []string{}},
40+
{name: "pop multiple items", initialStack: []string{"a", "b", "c"}, popResult: "c", finalStack: []string{"a", "b"}},
41+
}
42+
43+
for _, tt := range tests {
44+
t.Run(tt.name, func(t *testing.T) {
45+
stack := &Stack{stack: tt.initialStack}
46+
result := stack.Pop()
47+
48+
require.Equal(t, tt.popResult, result)
49+
require.Equal(t, tt.finalStack, stack.stack)
50+
})
51+
}
52+
}
53+
54+
func TestStack_PopMany(t *testing.T) {
55+
tests := []struct {
56+
name string
57+
initialStack []string
58+
count int
59+
popResult []string
60+
finalStack []string
61+
}{
62+
{name: "pop many on empty stack", initialStack: []string{}, count: 3, popResult: []string{}, finalStack: []string{}},
63+
{name: "pop 0 items", initialStack: []string{"a", "b", "c"}, count: 0, popResult: nil, finalStack: []string{"a", "b", "c"}},
64+
{name: "pop less than stack size", initialStack: []string{"a", "b", "c"}, count: 2, popResult: []string{"b", "c"}, finalStack: []string{"a"}},
65+
{name: "pop all items", initialStack: []string{"a", "b", "c"}, count: 3, popResult: []string{"a", "b", "c"}, finalStack: []string{}},
66+
{name: "pop more than available", initialStack: []string{"a", "b"}, count: 5, popResult: []string{"a", "b"}, finalStack: []string{}},
67+
}
68+
69+
for _, tt := range tests {
70+
t.Run(tt.name, func(t *testing.T) {
71+
stack := &Stack{stack: tt.initialStack}
72+
result := stack.PopMany(tt.count)
73+
74+
if tt.popResult == nil {
75+
require.Nil(t, result)
76+
} else {
77+
require.Equal(t, tt.popResult, result)
78+
}
79+
require.Equal(t, tt.finalStack, stack.stack)
80+
})
81+
}
82+
}
83+
84+
func TestStack_Peek(t *testing.T) {
85+
tests := []struct {
86+
name string
87+
initialStack []string
88+
expected string
89+
finalStack []string
90+
}{
91+
{name: "peek on empty stack", initialStack: []string{}, expected: "", finalStack: []string{}},
92+
{name: "peek single item", initialStack: []string{"a"}, expected: "a", finalStack: []string{"a"}},
93+
{name: "peek multiple items", initialStack: []string{"a", "b", "c"}, expected: "c", finalStack: []string{"a", "b", "c"}},
94+
}
95+
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
stack := &Stack{stack: tt.initialStack}
99+
result := stack.Peek()
100+
101+
require.Equal(t, tt.expected, result)
102+
require.Equal(t, tt.finalStack, stack.stack)
103+
})
104+
}
105+
}

internal/engine/types/text.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package types
2+
3+
import (
4+
"unicode"
5+
"unicode/utf8"
6+
)
7+
8+
// IsValidString returns true if the given string is valid UTF-8
9+
func IsValidString(s string) bool {
10+
if !utf8.ValidString(s) {
11+
return false
12+
}
13+
14+
for _, r := range s {
15+
// check for non-printable control characters
16+
if unicode.IsControl(r) {
17+
return false
18+
}
19+
}
20+
return true
21+
}

internal/engine/types/text_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package types
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestIsValidString(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
input string
13+
expected bool
14+
}{
15+
{
16+
name: "valid UTF-8 string",
17+
input: "Hello, World!",
18+
expected: true,
19+
},
20+
{
21+
name: "string with control character",
22+
input: "Hello\x00World",
23+
expected: false,
24+
},
25+
{
26+
name: "valid empty string",
27+
input: "",
28+
expected: true,
29+
},
30+
{
31+
name: "invalid UTF-8 string",
32+
input: string([]byte{0xff, 0xfe, 0xfd}),
33+
expected: false,
34+
},
35+
{
36+
name: "string with only printable characters",
37+
input: "Hello, World!",
38+
expected: true,
39+
},
40+
{
41+
name: "string with newline character",
42+
input: "Hello\nWorld",
43+
expected: false,
44+
},
45+
{
46+
name: "string with tab character",
47+
input: "Hello\tWorld",
48+
expected: false,
49+
},
50+
}
51+
52+
for _, tt := range tests {
53+
t.Run(tt.name, func(t *testing.T) {
54+
result := IsValidString(tt.input)
55+
assert.Equal(t, tt.expected, result, "IsValidString(%q)", tt.input)
56+
})
57+
}
58+
}

internal/engine/util/area.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"github.com/twpayne/go-geom"
77
)
88

9-
// Copied from https://github.com/PDOK/gokoala/blob/070ec77b2249553959330ff8029bfdf48d7e5d86/internal/ogc/features/url.go#L264
109
func SurfaceArea(bbox *geom.Bounds) float64 {
1110
// Use the same logic as bbox.Area() in https://github.com/go-spatial/geom to calculate surface area.
1211
// The bounds.Area() in github.com/twpayne/go-geom behaves differently and is not what we're looking for.

internal/engine/util/random.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package util
2+
3+
import "math/rand/v2"
4+
5+
// Randomizer is used to generate random numbers
6+
type Randomizer interface {
7+
IntN(n int) int
8+
}
9+
10+
// DefaultRandomizer is used for production and wraps the stdlib math/rand/v2 package
11+
var DefaultRandomizer = defaultRandomizer{}
12+
13+
type defaultRandomizer struct{}
14+
15+
func (defaultRandomizer) IntN(n int) int { return rand.IntN(n) } //nolint:gosec
16+
17+
// MockRandomizer is used for testing and provides predictable results
18+
type MockRandomizer struct {
19+
counter int
20+
}
21+
22+
func (m *MockRandomizer) IntN(n int) int {
23+
if n <= 0 {
24+
return 0
25+
}
26+
m.counter++ // not so random, instead it's predictable for testing
27+
return m.counter % n
28+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package util
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestDefaultRandomizer(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
input int
14+
expectPanic bool
15+
}{
16+
{name: "positive input", input: 10, expectPanic: false},
17+
{name: "zero input", input: 0, expectPanic: true},
18+
{name: "negative input", input: -5, expectPanic: true},
19+
}
20+
for _, tt := range tests {
21+
t.Run(tt.name, func(t *testing.T) {
22+
// given
23+
randomizer := DefaultRandomizer
24+
25+
if tt.expectPanic {
26+
require.Panics(t, func() {
27+
randomizer.IntN(tt.input)
28+
})
29+
return
30+
}
31+
32+
require.NotPanics(t, func() {
33+
_ = randomizer.IntN(tt.input)
34+
})
35+
36+
// when
37+
result := randomizer.IntN(tt.input)
38+
39+
// then
40+
assert.GreaterOrEqual(t, result, 0)
41+
assert.Less(t, result, tt.input)
42+
})
43+
}
44+
}
45+
46+
func TestMockRandomizer(t *testing.T) {
47+
tests := []struct {
48+
name string
49+
counter int
50+
}{
51+
{name: "initial state", counter: 0},
52+
{name: "incremented once", counter: 1},
53+
{name: "incremented twice", counter: 2},
54+
}
55+
for _, tt := range tests {
56+
t.Run(tt.name, func(t *testing.T) {
57+
// given
58+
mock := MockRandomizer{counter: tt.counter}
59+
60+
// when
61+
result := mock.IntN(100)
62+
63+
// then
64+
assert.Equal(t, tt.counter+1, result)
65+
})
66+
}
67+
}

0 commit comments

Comments
 (0)