Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* Added custom converter interface for `database/sql` query parameters

## v3.121.0
* Changed internal pprof label to pyroscope supported format
* Added `query.ImplicitTxControl()` transaction control (the same as `query.NoTx()` and `query.EmptyTxControl()`). See more about implicit transactions on [ydb.tech](https://ydb.tech/docs/en/concepts/transactions?version=v25.2#implicit)
Expand Down
117 changes: 117 additions & 0 deletions bind/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package bind

import (
"github.com/ydb-platform/ydb-go-sdk/v3/internal/bind"
"github.com/ydb-platform/ydb-go-sdk/v3/internal/value"
)

// Converter defines the interface for custom conversion of database/sql query parameters
// to YDB values. Implementations can handle specific types that require special conversion
// logic beyond the standard type conversions.
//
// Example:
//
// type MyCustomType struct {
// Field string
// }
//
// func (c *MyCustomConverter) Convert(v any) (value.Value, bool) {
// if custom, ok := v.(MyCustomType); ok {
// return value.TextValue(custom.Field), true
// }
// return nil, false
// }
type Converter = bind.Converter

// NamedValueConverter extends Converter to handle driver.NamedValue types
//
// This is useful when you need access to both the name and value of a parameter
// for conversion logic.
//
// Example:
//
// func (c *MyNamedConverter) ConvertNamedValue(nv driver.NamedValue) (value.Value, bool) {
// if nv.Name == "special_param" {
// // Custom handling for named parameter
// return value.TextValue(fmt.Sprintf("special_%v", nv.Value)), true
// }
// return c.Convert(nv.Value)
// }
type NamedValueConverter = bind.NamedValueConverter

// RegisterConverter registers a custom converter with the default registry
//
// Custom converters are tried before the standard conversion logic, allowing
// you to override or extend the default behavior for specific types.
//
// Example:
//
// bind.RegisterConverter(&MyCustomConverter{})
func RegisterConverter(converter Converter) {
bind.RegisterConverter(converter)
}
Comment on lines +42 to +52
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation should mention that RegisterConverter registers converters globally and that registrations persist for the lifetime of the application. This is important for users to understand potential side effects, especially in testing scenarios where converters may need to be cleaned up.

Copilot uses AI. Check for mistakes.

// RegisterNamedValueConverter registers a named value converter with the default registry
//
// Named value converters are tried before standard converters when handling
// driver.NamedValue instances.
//
// Example:
//
// bind.RegisterNamedValueConverter(&MyNamedConverter{})
func RegisterNamedValueConverter(converter NamedValueConverter) {
bind.RegisterNamedValueConverter(converter)
}
Comment on lines +54 to +64
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation should mention that RegisterNamedValueConverter registers converters globally and that registrations persist for the lifetime of the application. This is important for users to understand potential side effects, especially in testing scenarios where converters may need to be cleaned up.

Copilot uses AI. Check for mistakes.

// CustomTypeConverter is a generic converter that can be configured with custom conversion functions
//
// This provides a convenient way to create converters without defining a new type.
//
// Example:
//
// converter := bind.NewCustomTypeConverter(
// func(v any) bool { _, ok := v.(MyType); return ok },
// func(v any) (value.Value, error) { return value.TextValue(v.(MyType).String()), nil },
// )
// bind.RegisterConverter(converter)
type CustomTypeConverter = bind.CustomTypeConverter

// NewCustomTypeConverter creates a new custom type converter
//
// typeCheck: function that returns true if the converter can handle the given value
// convertFunc: function that converts the value to a YDB value
func NewCustomTypeConverter(
typeCheck func(any) bool,
convertFunc func(any) (value.Value, error),
) *CustomTypeConverter {
return bind.NewCustomTypeConverter(typeCheck, convertFunc)
}

// JSONConverter handles conversion of JSON documents to YDB JSON values
//
// This converter automatically handles any type that implements json.Marshaler
type JSONConverter = bind.JSONConverter

// UUIDConverter handles conversion of UUID types to YDB UUID values
//
// This converter handles google/uuid.UUID and pointer types
type UUIDConverter = bind.UUIDConverter

// ConverterRegistry manages a collection of custom converters
//
// You can create your own registry if you want to use a separate set of converters
// from the global default registry.
type ConverterRegistry = bind.ConverterRegistry

// NewConverterRegistry creates a new converter registry
func NewConverterRegistry() *ConverterRegistry {
return bind.NewConverterRegistry()
}

// NamedValueConverterRegistry manages a collection of named value converters
type NamedValueConverterRegistry = bind.NamedValueConverterRegistry

// NewNamedValueConverterRegistry creates a new named value converter registry
func NewNamedValueConverterRegistry() *NamedValueConverterRegistry {
return bind.NewNamedValueConverterRegistry()
}
Loading
Loading