Skip to content

Can a simple extension be written in Go? #42

@loic-alleyne

Description

@loic-alleyne

I tried wanted to see if I could get a basic echo scalar string function working, I got as far as being able to load the extension, but when running the function.

D load './string_extension.duckdb_extension';
Initializing custom DuckDB extension
Scalar function registered successfully
D SELECT string_function('Hello, DuckDB!');
Segmentation fault (core dumped)

CoPilot suggested something about a C-compatible function pointer, I tried that but got exactly the same results. Any hints on where to go from here? My C is very rusty (no pun intended)...

package main

/*
#include <duckdb.h>
*/
import "C"
import (
    "fmt"
    "unsafe"

    "github.com/duckdb/duckdb-go-bindings"
)

//export string_extension_init_c_api
func string_extension_init_c_api(db *C.duckdb_database) bool{
    fmt.Println("Initializing custom DuckDB extension")

    // Wrap the database pointer in the duckdb-go-bindings Database struct
    database := duckdb_go_bindings.Database{Ptr: unsafe.Pointer(db)}

    // Create a connection to the database
    var conn duckdb_go_bindings.Connection
    if duckdb_go_bindings.Connect(database, &conn) != duckdb_go_bindings.StateSuccess {
        fmt.Println("Failed to connect to DuckDB")
        return false 
    }
    defer duckdb_go_bindings.Disconnect(&conn)

    // Register the scalar function
    if err := registerScalarFunction(conn, "string_function", stringFunction); err != nil {
        fmt.Printf("Failed to register scalar function: %v\n", err)
        return false
    }

    fmt.Println("Scalar function registered successfully")
	return true
}

//export string_function_c
func string_function_c(info *C.duckdb_function_info, input *C.duckdb_data_chunk, output *C.duckdb_data_chunk) {
    // Extract the input string
    inputChunk := duckdb_go_bindings.DataChunk{Ptr: unsafe.Pointer(input)}
    outputChunk := duckdb_go_bindings.DataChunk{Ptr: unsafe.Pointer(output)}

    inputVector := inputChunk.GetVector(0)
    inputValue := inputVector.GetValue(0)
    inputString := duckdb_go_bindings.GetVarchar(inputValue)

    // Process the input
    result := fmt.Sprintf("Received: %s", inputString)

    // Set the output value
    outputVector := outputChunk.GetVector(0)
    outputVector.SetValue(0, duckdb_go_bindings.CreateVarchar(result))
}

// stringFunction is the implementation of the scalar function
func stringFunction(args []duckdb_go_bindings.Value) (duckdb_go_bindings.Value, error) {
    // Ensure the input is a string
    if len(args) != 1 {
        return duckdb_go_bindings.CreateNullValue(), fmt.Errorf("string_function expects exactly one argument")
    }

    input := duckdb_go_bindings.GetVarchar(args[0])
    if input == "" {
        return duckdb_go_bindings.CreateNullValue(), fmt.Errorf("string_function expects a non-empty string argument")
    }

    // Return the input string with a custom message
    result := fmt.Sprintf("Received: %s", input)
    return duckdb_go_bindings.CreateVarchar(result), nil
}

// registerScalarFunction registers a scalar function with DuckDB
func registerScalarFunction(conn duckdb_go_bindings.Connection, name string, fn func([]duckdb_go_bindings.Value) (duckdb_go_bindings.Value, error)) error {
    // Create a new scalar function
    scalarFunc := duckdb_go_bindings.CreateScalarFunction()

    // Set the function name
    duckdb_go_bindings.ScalarFunctionSetName(scalarFunc, name)

    // Define the input parameter type (VARCHAR)
    inputType := duckdb_go_bindings.CreateLogicalType(duckdb_go_bindings.TypeVarchar)
    duckdb_go_bindings.ScalarFunctionAddParameter(scalarFunc, inputType)
	duckdb_go_bindings.DestroyLogicalType(&inputType)

    // Define the return type (VARCHAR)
    returnType := duckdb_go_bindings.CreateLogicalType(duckdb_go_bindings.TypeVarchar)
    duckdb_go_bindings.ScalarFunctionSetReturnType(scalarFunc, returnType)
	duckdb_go_bindings.DestroyLogicalType(&returnType)
    // Set the function implementation
   duckdb_go_bindings.ScalarFunctionSetFunction(scalarFunc, unsafe.Pointer(C.string_function_c))
    // duckdb_go_bindings.ScalarFunctionSetFunction(scalarFunc, unsafe.Pointer(&fn))

    // Register the function with the connection
    if duckdb_go_bindings.RegisterScalarFunction(conn, scalarFunc) != duckdb_go_bindings.StateSuccess {
        return fmt.Errorf("failed to register scalar function: %s", name)
    }
	duckdb_go_bindings.DestroyScalarFunction(&scalarFunc)
    return nil
}

func main() {
    // This is the entry point for the extension
    // Typically, DuckDB will call `duckdb_extension_init` automatically
}

build script

# Set paths and variables
LIBRARY_FILE="string_extension.so"
OUTPUT_FILE="string_extension.duckdb_extension"
DUCKDB_PLATFORM="linux_amd64"  # Adjust this to your platform
DUCKDB_VERSION="v1.2.0"         # Replace with your DuckDB version
EXTENSION_VERSION="0.0.1"      # Replace with your extension version

# Build the shared library
echo "Building the shared library..."
CGO_ENABLED=1 CPPFLAGS="-DDUCKDB_STATIC_BUILD" CGO_LDFLAGS="-lduckdb -lstdc++ -lm -ldl -L/path/to/duckdb-go-bindings/linux-amd64" \
go build -tags=duckdb_use_static_lib -buildmode=c-shared -o $LIBRARY_FILE main.go

# Append metadata to the shared library
echo "Appending DuckDB extension metadata..."
python3 append_extension_metadata.py \
    --library-file $LIBRARY_FILE \
    --extension-name "string_extension" \
    --duckdb-platform $DUCKDB_PLATFORM \
    --duckdb-version $DUCKDB_VERSION \
    --extension-version $EXTENSION_VERSION \
    --out-file $OUTPUT_FILE

echo "Build complete. Output file: $OUTPUT_FILE"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions