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) } }