* Add handling for pgvector and PostGIS extensions in migration scripts * Implement operator class and storage parameters for vector indexes * Update tests to validate new index behaviors and extension creation
112 lines
3.4 KiB
Go
112 lines
3.4 KiB
Go
package pgsql
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestExtractWithClause(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want string
|
|
}{
|
|
{"empty", "", ""},
|
|
{"no clause", "opclass=vector_cosine_ops", ""},
|
|
{"simple", "WITH (lists=100)", "lists=100"},
|
|
{"lowercase", "with (m = 16, ef_construction = 64)", "m = 16, ef_construction = 64"},
|
|
{
|
|
"index definition",
|
|
"CREATE INDEX i ON t USING ivfflat (embedding vector_cosine_ops) WITH (lists='100')",
|
|
"lists='100'",
|
|
},
|
|
{"paren inside quotes", "with (key_field='id(x)')", "key_field='id(x)'"},
|
|
{"dollar quoted", "with (options = $$f(x)$$)", "options = $$f(x)$$"},
|
|
{"word boundary", "swith (lists=100)", ""},
|
|
{"not followed by paren", "with lists=100", ""},
|
|
{"unterminated", "with (lists=100", ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := ExtractWithClause(tt.input); got != tt.want {
|
|
t.Errorf("ExtractWithClause(%q) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSplitStorageParameters(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want []string
|
|
}{
|
|
{"empty", "", []string{}},
|
|
{"single", "lists=100", []string{"lists=100"}},
|
|
{"multiple", "m = 16, ef_construction = 64", []string{"m = 16", "ef_construction = 64"}},
|
|
{"comma in quotes", "key_field='a,b', m=16", []string{"key_field='a,b'", "m=16"}},
|
|
{"comma in dollar quotes", "options=$$a,b$$, m=16", []string{"options=$$a,b$$", "m=16"}},
|
|
{"comma in brackets", "options=[1,2], m=16", []string{"options=[1,2]", "m=16"}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := SplitStorageParameters(tt.input); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("SplitStorageParameters(%q) = %v, want %v", tt.input, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseStorageParameter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantKey string
|
|
wantValue string
|
|
wantOK bool
|
|
}{
|
|
{"bare", "lists=100", "lists", "100", true},
|
|
{"spaced and uppercased key", " Lists = 100 ", "lists", "100", true},
|
|
{"quoted", "key_field='id'", "key_field", "'id'", true},
|
|
{"dollar quoted", "options=$$a$$", "options", "$$a$$", true},
|
|
{"boolean", "deduplicate_items=true", "deduplicate_items", "true", true},
|
|
{"float", "fillfactor=90.5", "fillfactor", "90.5", true},
|
|
{"no equals", "please drop everything", "", "", false},
|
|
{"empty value", "lists=", "", "", false},
|
|
{"quoted key rejected", "'lists'=100", "", "", false},
|
|
{"injection rejected", "lists=100); drop table t", "", "", false},
|
|
{"unterminated quote rejected", "key_field='id", "", "", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
key, value, ok := ParseStorageParameter(tt.input)
|
|
if key != tt.wantKey || value != tt.wantValue || ok != tt.wantOK {
|
|
t.Errorf("ParseStorageParameter(%q) = (%q, %q, %v), want (%q, %q, %v)",
|
|
tt.input, key, value, ok, tt.wantKey, tt.wantValue, tt.wantOK)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNormalizeStorageParameterValue(t *testing.T) {
|
|
tests := map[string]string{
|
|
"'100'": "100",
|
|
"'90.5'": "90.5",
|
|
"'true'": "true",
|
|
"'id'": "'id'",
|
|
"100": "100",
|
|
"$$a,b$$": "$$a,b$$",
|
|
"'": "'",
|
|
"''": "''",
|
|
}
|
|
|
|
for input, want := range tests {
|
|
if got := NormalizeStorageParameterValue(input); got != want {
|
|
t.Errorf("NormalizeStorageParameterValue(%q) = %q, want %q", input, got, want)
|
|
}
|
|
}
|
|
}
|