Files
dolthub--doltgresql/core/extensions/extensions.go
T
2026-07-13 12:32:25 +08:00

200 lines
7.0 KiB
Go

// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extensions
import (
"fmt"
"strconv"
"sync"
"github.com/cockroachdb/errors"
"github.com/dolthub/doltgresql/core/extensions/pg_extension"
)
var (
cachedError error // TODO: is it better for this to be a local object instead of a global since it's always returned?
allExtensions map[string]*pg_extension.ExtensionFiles // TODO: should use id.Extension instead of a string
allLibraries map[string]*pg_extension.Library // TODO: close these at some point
extMutex = &sync.Mutex{}
libMutex = &sync.Mutex{}
)
// Version 0 identifiers are encoded like the following:
// 00AAA1111111111BBB
// `00` is a two-digit version specifier, and we only have version 0 for now
// `AAA` is the three-letter platform specifier
// `1111111111` is the ten-digit library version specifier (i.e. an encoded form of 1.0, 1.5, etc.)
// `BBB` is the extension name, and may be of any length (will have at least 1 character)
// LibraryIdentifier points to a specific extension, as extension functions are dependent on the extension name,
// version, and originating platform (as different platforms may encode data differently).
type LibraryIdentifier string
// InvalidIdentifierReason gives a reason as to why an Identifier is invalid.
type InvalidIdentifierReason uint8
const (
InvalidIdentifierReason_MismatchedPlatform InvalidIdentifierReason = iota
InvalidIdentifierReason_MissingLibrary
InvalidIdentifierReason_InvalidVersion
)
// InvalidIdentifier represents an invalid LibraryIdentifier, providing both the LibraryIdentifier and the reason that
// it is invalid.
type InvalidIdentifier struct {
Identifier LibraryIdentifier
Reason InvalidIdentifierReason
}
// GetExtension returns the extension matching the given name. Returns an error if the extension cannot be found.
func GetExtension(name string) (_ *pg_extension.ExtensionFiles, err error) {
extMutex.Lock()
defer extMutex.Unlock()
if cachedError != nil {
return nil, cachedError
}
if allExtensions == nil {
allLibraries = make(map[string]*pg_extension.Library)
allExtensions, err = pg_extension.LoadExtensions()
if err != nil {
allExtensions = make(map[string]*pg_extension.ExtensionFiles)
cachedError = err
return nil, err
}
}
ext, ok := allExtensions[name]
if !ok {
return nil, errors.Errorf(`could not open extension control file "%s.control"`, name)
}
return ext, nil
}
// GetExtensionFunction returns the function inside the extension matching the given names. Returns an error if the
// extension or function cannot be found.
func GetExtensionFunction(identifier LibraryIdentifier, funcName string) (_ pg_extension.Function, err error) {
libMutex.Lock()
defer libMutex.Unlock()
extName := identifier.ExtensionName()
if identifier.Platform() != pg_extension.PLATFORM {
return pg_extension.Function{}, errors.Errorf(
`function "%s" was initialized through "%s" on a different platform, which is not supported`, funcName, extName)
}
lib, ok := allLibraries[extName]
if !ok {
ext, err := GetExtension(extName)
if err != nil {
return pg_extension.Function{}, err
}
lib, err = ext.LoadLibrary()
if err != nil {
return pg_extension.Function{}, err
}
lib.Version = ext.Control.DefaultVersion
allLibraries[extName] = lib
}
if lib.Version != identifier.Version() {
return pg_extension.Function{}, errors.Errorf(
`function "%s" was initialized through "%s" v%s, the current platform only supports v%s"`,
funcName, extName, identifier.Version().String(), lib.Version.String())
}
libFunc, ok := lib.Funcs[funcName]
if !ok {
return pg_extension.Function{}, errors.Errorf(`extension "%s" does not declare the function "%s"`, extName, funcName)
}
return libFunc, nil
}
// FindInvalidIdentifiers returns all identifiers that are not valid for the current environment. If the return is
// empty, then that means all of the given identifiers are valid.
func FindInvalidIdentifiers(identifiers ...LibraryIdentifier) []InvalidIdentifier {
extMutex.Lock()
defer extMutex.Unlock()
var invalidIdentifiers []InvalidIdentifier
if cachedError != nil {
invalidIdentifiers = make([]InvalidIdentifier, 0, len(identifiers))
for _, identifier := range identifiers {
invalidIdentifiers = append(invalidIdentifiers, InvalidIdentifier{
Identifier: identifier,
Reason: InvalidIdentifierReason_MissingLibrary,
})
}
return invalidIdentifiers
}
for _, identifier := range identifiers {
if identifier.Platform() != pg_extension.PLATFORM {
invalidIdentifiers = append(invalidIdentifiers, InvalidIdentifier{
Identifier: identifier,
Reason: InvalidIdentifierReason_MismatchedPlatform,
})
continue
}
ext, ok := allExtensions[identifier.ExtensionName()]
if !ok {
invalidIdentifiers = append(invalidIdentifiers, InvalidIdentifier{
Identifier: identifier,
Reason: InvalidIdentifierReason_MissingLibrary,
})
continue
}
if ext.Control.DefaultVersion != identifier.Version() {
invalidIdentifiers = append(invalidIdentifiers, InvalidIdentifier{
Identifier: identifier,
Reason: InvalidIdentifierReason_InvalidVersion,
})
continue
}
}
return invalidIdentifiers
}
// GetPlatform returns the current platform that Doltgres is being executed on. This is encoded as a three-letter string.
func GetPlatform() string {
return pg_extension.PLATFORM
}
// CreateLibraryIdentifier creates a LibraryIdentifier using the given information.
func CreateLibraryIdentifier(name string, version pg_extension.Version) LibraryIdentifier {
return LibraryIdentifier(fmt.Sprintf("00%s%010d%s", pg_extension.PLATFORM, version, name))
}
// Platform returns the platform that this LibraryIdentifier was created on.
func (id LibraryIdentifier) Platform() string {
return string(id[2:5])
}
// Version returns the library version that this LibraryIdentifier references.
func (id LibraryIdentifier) Version() pg_extension.Version {
val, err := strconv.ParseUint(string(id[5:15]), 10, 32)
if err != nil {
// We'll panic for now since this should never happen
panic(err)
}
return pg_extension.Version(val)
}
// ExtensionName returns the extension referenced by this LibraryIdentifier.
func (id LibraryIdentifier) ExtensionName() string {
return string(id[15:])
}
// DisplayString returns the identifier as a human-readable string.
func (id LibraryIdentifier) DisplayString() string {
return fmt.Sprintf("%s--%s:%s", id.ExtensionName(), id.Version().String(), id.Platform())
}