* 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
166 lines
5.0 KiB
Go
166 lines
5.0 KiB
Go
package pgsql
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestExtensionRegistryConsistency(t *testing.T) {
|
|
for name, ext := range postgresExtensions {
|
|
if name != ext.Name {
|
|
t.Errorf("extension registered as %q has Name %q", name, ext.Name)
|
|
}
|
|
if name != strings.ToLower(name) {
|
|
t.Errorf("extension %q must be registered lowercase", name)
|
|
}
|
|
if ext.Description == "" || ext.Category == "" {
|
|
t.Errorf("extension %q is missing a category or description", name)
|
|
}
|
|
for _, dependency := range ext.Requires {
|
|
if !IsKnownExtension(dependency) {
|
|
t.Errorf("extension %q requires unregistered extension %q", name, dependency)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every extension named by a type in the type registry must itself be registered,
|
|
// otherwise a column type would ask for a CREATE EXTENSION nothing knows how to order.
|
|
func TestTypeExtensionsAreRegistered(t *testing.T) {
|
|
for typeName, spec := range postgresBaseTypes {
|
|
if spec.Extension == "" {
|
|
continue
|
|
}
|
|
if !IsKnownExtension(spec.Extension) {
|
|
t.Errorf("type %q declares unregistered extension %q", typeName, spec.Extension)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIndexMethodExtension(t *testing.T) {
|
|
tests := map[string]string{
|
|
"hnsw": "vector",
|
|
"ivfflat": "vector",
|
|
"HNSW": "vector",
|
|
"vchordrq": "vchord",
|
|
"vchordg": "vchord",
|
|
"bm25": "pg_search",
|
|
"btree": "",
|
|
"gin": "",
|
|
"": "",
|
|
}
|
|
|
|
for method, want := range tests {
|
|
if got := IndexMethodExtension(method); got != want {
|
|
t.Errorf("IndexMethodExtension(%q) = %q, want %q", method, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOperatorClassExtension(t *testing.T) {
|
|
tests := map[string]string{
|
|
"gin_trgm_ops": "pg_trgm",
|
|
"gist_trgm_ops": "pg_trgm",
|
|
"vector_cosine_ops": "vector",
|
|
"halfvec_l2_ops": "vector",
|
|
"gist_ltree_ops": "ltree",
|
|
"gist_geometry_ops_2d": "postgis",
|
|
"jsonb_path_ops": "",
|
|
"array_ops": "",
|
|
"": "",
|
|
}
|
|
|
|
for opClass, want := range tests {
|
|
if got := OperatorClassExtension(opClass); got != want {
|
|
t.Errorf("OperatorClassExtension(%q) = %q, want %q", opClass, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtensionsForExpression(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
expression string
|
|
want []string
|
|
}{
|
|
{"empty", "", nil},
|
|
{"no functions", "status = 'active'", nil},
|
|
{"builtin only", "now()", nil},
|
|
{"uuid-ossp default", "uuid_generate_v4()", []string{"uuid-ossp"}},
|
|
{"gen_random_uuid is builtin", "gen_random_uuid()", nil},
|
|
{"pgcrypto", "crypt(password, gen_salt('bf'))", []string{"pgcrypto"}},
|
|
{"postgis prefix", "ST_Area(geom) > 0", []string{"postgis"}},
|
|
{"paradedb prefix", "paradedb.snippet(body)", []string{"pg_search"}},
|
|
{"jsonschema", "json_matches_schema('{}', payload)", []string{"pg_jsonschema"}},
|
|
{"whitespace before paren", "unaccent ('crème')", []string{"unaccent"}},
|
|
{"multiple sorted", "ST_X(geom) = levenshtein(a, b)::float", []string{"fuzzystrmatch", "postgis"}},
|
|
{"column named like function", "similarity_score > 0.5", nil},
|
|
{"numeric prefix ignored", "2(3)", nil},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := ExtensionsForExpression(tt.expression); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("ExtensionsForExpression(%q) = %v, want %v", tt.expression, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSortExtensions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []string
|
|
want []string
|
|
}{
|
|
{"empty", nil, nil},
|
|
{"alphabetical", []string{"pg_trgm", "citext"}, []string{"citext", "pg_trgm"}},
|
|
{"deduplicated", []string{"vector", "vector", " VECTOR "}, []string{"vector"}},
|
|
{"dependency first", []string{"vchord", "vector"}, []string{"vector", "vchord"}},
|
|
{
|
|
"postgis dependants",
|
|
[]string{"postgis_topology", "pgrouting", "postgis"},
|
|
[]string{"postgis", "pgrouting", "postgis_topology"},
|
|
},
|
|
{"dependency not requested", []string{"vchord"}, []string{"vchord"}},
|
|
{"unknown names kept", []string{"zzz_custom", "citext"}, []string{"citext", "zzz_custom"}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := SortExtensions(tt.input); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("SortExtensions(%v) = %v, want %v", tt.input, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQuoteExtensionName(t *testing.T) {
|
|
tests := map[string]string{
|
|
"vector": "vector",
|
|
"pg_trgm": "pg_trgm",
|
|
"uuid-ossp": `"uuid-ossp"`,
|
|
"PostGIS": `"PostGIS"`,
|
|
"": "",
|
|
}
|
|
|
|
for name, want := range tests {
|
|
if got := QuoteExtensionName(name); got != want {
|
|
t.Errorf("QuoteExtensionName(%q) = %q, want %q", name, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtensionDependencies(t *testing.T) {
|
|
if got := ExtensionDependencies("vchord"); !reflect.DeepEqual(got, []string{"vector"}) {
|
|
t.Errorf("ExtensionDependencies(vchord) = %v, want [vector]", got)
|
|
}
|
|
if got := ExtensionDependencies("citext"); got != nil {
|
|
t.Errorf("ExtensionDependencies(citext) = %v, want nil", got)
|
|
}
|
|
if got := ExtensionDependencies("not_an_extension"); got != nil {
|
|
t.Errorf("ExtensionDependencies(not_an_extension) = %v, want nil", got)
|
|
}
|
|
}
|