Skip to content

Commit abb0e8d

Browse files
authored
Merge pull request #2 from gabisonia/integration-reliability-and-filter-safety
Integration reliability and filter safety
2 parents 288a7f9 + 208c6f2 commit abb0e8d

8 files changed

Lines changed: 80 additions & 15 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
workflow_dispatch:
88
inputs:
99
version:
10-
description: "Semver version (e.g. 0.2.0 or v0.2.0)"
10+
description: "Semver version (e.g. 0.2.1 or v0.2.1)"
1111
required: true
1212
type: string
1313

Makefile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.PHONY: test-integration-all test-integration-stores test-integration-postgres test-integration-mssql test-integration-msql
2+
3+
GO ?= go
4+
GOTOOLCHAIN ?= local
5+
INTEGRATION_TAG ?= integration
6+
MOD_MODE ?= mod
7+
TEST_TIMEOUT ?= 30m
8+
TEST_FLAGS ?=
9+
GO_ENV ?= env -u GOROOT GOTOOLCHAIN=$(GOTOOLCHAIN)
10+
11+
test-integration-all:
12+
$(GO_ENV) $(GO) test -mod=$(MOD_MODE) -tags=$(INTEGRATION_TAG) -timeout=$(TEST_TIMEOUT) $(TEST_FLAGS) ./...
13+
14+
test-integration-stores:
15+
$(GO_ENV) $(GO) test -mod=$(MOD_MODE) -tags=$(INTEGRATION_TAG) -timeout=$(TEST_TIMEOUT) $(TEST_FLAGS) ./stores/...
16+
17+
test-integration-postgres:
18+
$(GO_ENV) $(GO) test -mod=$(MOD_MODE) -tags=$(INTEGRATION_TAG) -timeout=$(TEST_TIMEOUT) $(TEST_FLAGS) ./stores/postgres
19+
20+
test-integration-mssql:
21+
$(GO_ENV) $(GO) test -mod=$(MOD_MODE) -tags=$(INTEGRATION_TAG) -timeout=$(TEST_TIMEOUT) $(TEST_FLAGS) ./stores/mssql
22+
23+
test-integration-msql: test-integration-mssql

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,12 @@ GitHub Actions workflows are configured for:
229229

230230
Release options:
231231

232-
1. Manual (recommended): run `Release` workflow via GitHub UI with `version` input (`0.2.0` or `v0.2.0`).
232+
1. Manual (recommended): run `Release` workflow via GitHub UI with `version` input (`0.2.1` or `v0.2.1`).
233233
2. Tag-driven: push a semver tag and the workflow publishes release notes automatically:
234234

235235
```bash
236-
git tag v0.2.0
237-
git push origin v0.2.0
236+
git tag v0.2.1
237+
git push origin v0.2.1
238238
```
239239

240240
## Samples

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ require (
2828
github.com/go-logr/stdr v1.2.2 // indirect
2929
github.com/go-ole/go-ole v1.2.6 // indirect
3030
github.com/gogo/protobuf v1.3.2 // indirect
31+
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
32+
github.com/golang-sql/sqlexp v0.1.0 // indirect
3133
github.com/google/uuid v1.6.0 // indirect
3234
github.com/jackc/pgpassfile v1.0.0 // indirect
3335
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
3636
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
3737
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
3838
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
39-
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:C/Fq8+Vm3x8+3xB3SO2/3A8Q+7WmfYI8MViMbum/v8A=
40-
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:9x5EikdFyA8Kj4m2Vj93QShv2N6Yf4H6J5D9Q1QzM4Y=
41-
github.com/golang-sql/sqlexp v0.1.0 h1:DKM0nqnvWFfB6K6x2zI2Qf8F9Pwc7P0W8o2JQ7QY9MM=
42-
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:hxXw1K3Zk7K8Y6nG5N7f6d4rV1E8j9a7q6x5y4z3w2Q=
39+
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
40+
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
41+
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
42+
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
4343
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
4444
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
4545
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=

stores/mssql/mssql_integration_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ var (
3333
)
3434

3535
func TestMain(m *testing.M) {
36-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
36+
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Minute)
3737
defer cancel()
3838

3939
dsn := strings.TrimSpace(os.Getenv("MSSQL_TEST_DSN"))
@@ -67,15 +67,17 @@ func TestMain(m *testing.M) {
6767

6868
func startMSSQLContainer(ctx context.Context) (testcontainers.Container, string, error) {
6969
request := testcontainers.ContainerRequest{
70-
Image: "mcr.microsoft.com/mssql/server:2022-latest",
71-
ExposedPorts: []string{"1433/tcp"},
70+
Image: "mcr.microsoft.com/mssql/server:2022-latest",
71+
ImagePlatform: "linux/amd64",
72+
AlwaysPullImage: true,
73+
ExposedPorts: []string{"1433/tcp"},
7274
Env: map[string]string{
7375
"ACCEPT_EULA": "Y",
7476
"MSSQL_SA_PASSWORD": integrationMSSQLPassword,
7577
"MSSQL_PID": "Developer",
7678
},
7779
WaitingFor: wait.ForLog("SQL Server is now ready for client connections").
78-
WithStartupTimeout(4 * time.Minute),
80+
WithStartupTimeout(10 * time.Minute),
7981
}
8082

8183
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{

vectordata/filter_sql.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"strings"
77
)
88

9+
const numericTextPattern = `^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?$`
10+
911
// FilterSQLConfig configures filter compilation into SQL expressions.
1012
type FilterSQLConfig struct {
1113
// ColumnExpr maps logical column names to pre-quoted SQL expressions.
@@ -123,7 +125,7 @@ func (c *filterCompiler) compileGt(node GtFilter) (string, error) {
123125
return fmt.Sprintf("(%s > %s)", fieldExpr, c.bind(node.Value)), nil
124126
}
125127
if num, ok := toFloat64(node.Value); ok {
126-
return fmt.Sprintf("((%s)::double precision > %s)", metadataPathTextExpr(fieldExpr, path), c.bind(num)), nil
128+
return c.compileMetadataNumericCompare(fieldExpr, path, ">", num), nil
127129
}
128130
return fmt.Sprintf("(%s > %s)", metadataPathTextExpr(fieldExpr, path), c.bind(fmt.Sprint(node.Value))), nil
129131
}
@@ -137,11 +139,23 @@ func (c *filterCompiler) compileLt(node LtFilter) (string, error) {
137139
return fmt.Sprintf("(%s < %s)", fieldExpr, c.bind(node.Value)), nil
138140
}
139141
if num, ok := toFloat64(node.Value); ok {
140-
return fmt.Sprintf("((%s)::double precision < %s)", metadataPathTextExpr(fieldExpr, path), c.bind(num)), nil
142+
return c.compileMetadataNumericCompare(fieldExpr, path, "<", num), nil
141143
}
142144
return fmt.Sprintf("(%s < %s)", metadataPathTextExpr(fieldExpr, path), c.bind(fmt.Sprint(node.Value))), nil
143145
}
144146

147+
func (c *filterCompiler) compileMetadataNumericCompare(metadataExpr string, path []string, op string, value float64) string {
148+
textExpr := metadataPathTextExpr(metadataExpr, path)
149+
return fmt.Sprintf(
150+
"(CASE WHEN (%s) ~ %s THEN ((%s)::double precision %s %s) ELSE FALSE END)",
151+
textExpr,
152+
singleQuoted(numericTextPattern),
153+
textExpr,
154+
op,
155+
c.bind(value),
156+
)
157+
}
158+
145159
func (c *filterCompiler) compileExists(node ExistsFilter) (string, error) {
146160
fieldExpr, isMetadata, path, err := c.resolveField(node.Field)
147161
if err != nil {

vectordata/filter_sql_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func TestCompileFilterSQL_Complex(t *testing.T) {
3434
t.Fatalf("CompileFilterSQL error: %v", err)
3535
}
3636

37-
expectedSQL := `(("id" = $1) AND (((jsonb_extract_path_text("metadata", 'rank'))::double precision > $2) OR (("metadata" #> ARRAY['flags', 'pinned']) IS NOT NULL)))`
37+
expectedSQL := `(("id" = $1) AND ((CASE WHEN (jsonb_extract_path_text("metadata", 'rank')) ~ '^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?$' THEN ((jsonb_extract_path_text("metadata", 'rank'))::double precision > $2) ELSE FALSE END) OR (("metadata" #> ARRAY['flags', 'pinned']) IS NOT NULL)))`
3838
if sql != expectedSQL {
3939
t.Fatalf("unexpected SQL\nwant: %s\n got: %s", expectedSQL, sql)
4040
}
@@ -49,6 +49,30 @@ func TestCompileFilterSQL_Complex(t *testing.T) {
4949
}
5050
}
5151

52+
func TestCompileFilterSQL_MetadataGtNumericUsesSafeCast(t *testing.T) {
53+
// Arrange
54+
filter := Gt(Metadata("rank"), 10)
55+
56+
// Act
57+
sql, args, next, err := CompileFilterSQL(filter, testFilterConfig(), 1)
58+
59+
// Assert
60+
if err != nil {
61+
t.Fatalf("CompileFilterSQL error: %v", err)
62+
}
63+
expectedSQL := `(CASE WHEN (jsonb_extract_path_text("metadata", 'rank')) ~ '^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?$' THEN ((jsonb_extract_path_text("metadata", 'rank'))::double precision > $1) ELSE FALSE END)`
64+
if sql != expectedSQL {
65+
t.Fatalf("unexpected SQL\nwant: %s\n got: %s", expectedSQL, sql)
66+
}
67+
expectedArgs := []any{float64(10)}
68+
if !reflect.DeepEqual(args, expectedArgs) {
69+
t.Fatalf("unexpected args\nwant: %#v\n got: %#v", expectedArgs, args)
70+
}
71+
if next != 2 {
72+
t.Fatalf("unexpected next arg index: %d", next)
73+
}
74+
}
75+
5276
func TestCompileFilterSQL_StartArgOffset(t *testing.T) {
5377
// Arrange
5478
filter := Eq(Column("content"), "hello")

0 commit comments

Comments
 (0)