Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CASSGO-43: externally-defined type registration #1855

Draft
wants to merge 1 commit into
base: trunk
Choose a base branch
from

Conversation

jameshartig
Copy link
Contributor

The new RegisterType function can be used to register externally-defined types. You'll need to define your own marshalling and unmarshalling code as well as a TypeInfo implementation. The name and id MUST not collide with existing and future native CQL types.

Additionally, a lot of the type handling was refactored to use the new format for native types. Performance should be slightly improved thanks to some simplification. Benchmarks are coming soon.

The new RegisterType function can be used to register externally-defined
types. You'll need to define your own marshalling and unmarshalling code
as well as a TypeInfo implementation. The name and id MUST not collide
with existing and future native CQL types.

Additionally, a lot of the type handling was refactored to use the new
format for native types. Performance should be slightly improved thanks
to some simplification. Benchmarks are coming soon.
@jameshartig
Copy link
Contributor Author

jameshartig commented Jan 3, 2025

Here's an example of a JSONB type:

package mypackage

import "github.com/gocql/gocql"

var typeJSONB gocql.Type = 0x0080

func init() {
	gocql.RegisterType(typeJSONB, "jsonb", JSONBCQLType{})
}

// JSONBTypeInfo implements the gocql.TypeInfo interface
type JSONBTypeInfo struct {
	proto byte
}

// Type implements the gocql.TypeInfo interface
func (j JSONBTypeInfo) Type() gocql.Type {
	return typeJSONB
}

// Version implements the gocql.TypeInfo interface
func (j JSONBTypeInfo) Version() byte {
	return j.proto
}

// Custom implements the gocql.TypeInfo interface
func (j JSONBTypeInfo) Custom() string {
	return ""
}

// New implements the gocql.TypeInfo interface
func (j JSONBTypeInfo) New() interface{} {
	return new([]byte)
}

// NewWithError implements the gocql.TypeInfo interface
func (j JSONBTypeInfo) NewWithError() (interface{}, error) {
	return new([]byte), nil
}

// JSONBCQLType implements the gocql.CQLType interface
type JSONBCQLType struct {
}

// TypeInfo implements the gocql.CQLType interface
func (j JSONBCQLType) TypeInfo(proto int) gocql.TypeInfo {
	return JSONBTypeInfo{proto: byte(proto)}
}

// Marshal implements the gocql.CQLType interface
func (j JSONBCQLType) Marshal(info gocql.TypeInfo, value interface{}) ([]byte, error) {
	return gocql.Marshal(gocql.NewNativeType(info.Version(), gocql.TypeBlob, ""), value)
}

// Unmarshal implements the gocql.CQLType interface
func (j JSONBCQLType) Unmarshal(info gocql.TypeInfo, value []byte, dest interface{}) error {
	return gocql.Unmarshal(gocql.NewNativeType(info.Version(), gocql.TypeBlob, ""), value, dest)
}

Or they can write their own marshal/unmarshal methods:

func marshalJSONB(info gocql.TypeInfo, value interface{}) ([]byte, error) {
	switch v := value.(type) {
	case gocql.Marshaler:
		return v.MarshalCQL(info)
	case json.RawMessage:
		return v, nil
	case string:
		return []byte(v), nil
	case []byte:
		return v, nil
	}
	if value == nil {
		return nil, nil
	}

	rv := reflect.ValueOf(value)
	t := rv.Type()
	k := t.Kind()
	switch {
	case k == reflect.String:
		return []byte(rv.String()), nil
	case k == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
		return rv.Bytes(), nil
	}
	return nil, gocql.MarshalError(fmt.Sprintf("can not marshal %T into jsonb", value))
}

var typeSliceOfBytes = reflect.TypeOf([]byte(nil))

func unmarshalJSONB(info gocql.TypeInfo, data []byte, value interface{}) error {
	switch v := value.(type) {
	case gocql.Unmarshaler:
		return v.UnmarshalCQL(info, data)
	case *json.RawMessage:
		if data != nil {
			*v = append((*v)[:0], data...)
		} else {
			*v = nil
		}
		return nil
	case *string:
		*v = string(data)
		return nil
	case *[]byte:
		if data != nil {
			*v = append((*v)[:0], data...)
		} else {
			*v = nil
		}
		return nil
	}

	rv := reflect.ValueOf(value)
	if rv.Kind() != reflect.Ptr {
		return gocql.UnmarshalError(fmt.Sprintf("can not unmarshal jsonb into non-pointer %T", value))
	}
	rv = rv.Elem()
	t := rv.Type()
	k := t.Kind()
	switch {
	case k == reflect.String:
		rv.SetString(string(data))
		return nil
	case k == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
		if data != nil {
			if rv.Type() != typeSliceOfBytes {
				rv.Set(reflect.AppendSlice(rv.Slice(0, 0), reflect.ValueOf(data).Convert(t)))
			} else {
				rv.Set(reflect.AppendSlice(rv.Slice(0, 0), reflect.ValueOf(data)))
			}
		} else {
			rv.Set(reflect.Zero(t))
		}
		return nil
	}
	return gocql.UnmarshalError(fmt.Sprintf("can not unmarshal jsonb into %T", value))
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant