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