collection.go

Summary

Maintainability
A
2 hrs
Test Coverage
A
100%
package rel

import (
    "reflect"
)

type slice interface {
    table
    Reset()
    NewDocument() *Document
    Append(doc *Document)
    Get(index int) *Document
    Len() int
    Meta() DocumentMeta
}

// Collection provides an abstraction over reflect to easily works with slice for database purpose.
type Collection struct {
    v       any
    rv      reflect.Value
    rt      reflect.Type
    meta    DocumentMeta
    swapper func(i, j int)
}

// ReflectValue of referenced document.
func (c Collection) ReflectValue() reflect.Value {
    return c.rv
}

// Table returns name of the table.
func (c *Collection) Table() string {
    return c.meta.Table()
}

// PrimaryFields column name of this collection.
func (c Collection) PrimaryFields() []string {
    if p, ok := c.v.(primary); ok {
        return p.PrimaryFields()
    }

    if len(c.meta.primaryField) == 0 {
        panic("rel: failed to infer primary key for type " + c.rt.String())
    }

    return c.meta.primaryField
}

// PrimaryField column name of this document.
// panic if document uses composite key.
func (c Collection) PrimaryField() string {
    if fields := c.PrimaryFields(); len(fields) == 1 {
        return fields[0]
    }

    panic("rel: composite primary key is not supported")
}

// PrimaryValues of collection.
// Returned value will be interface of slice interface.
func (c Collection) PrimaryValues() []any {
    if p, ok := c.v.(primary); ok {
        return p.PrimaryValues()
    }

    var (
        index   = c.meta.primaryIndex
        pValues = make([]any, len(c.PrimaryFields()))
    )

    if index != nil {
        for i := range index {
            var (
                idxLen = c.rv.Len()
                values = make([]any, 0, idxLen)
            )

            for j := 0; j < idxLen; j++ {
                if item := c.rvIndex(j); item.IsValid() {
                    values = append(values, reflectValueFieldByIndex(item, index[i], false).Interface())
                }
            }

            pValues[i] = values
        }
    } else {
        // using interface.
        var (
            tmp = make([][]any, len(pValues))
        )

        for i := 0; i < c.rv.Len(); i++ {
            item := c.rvIndex(i)
            if !item.IsValid() {
                continue
            }
            for j, id := range item.Interface().(primary).PrimaryValues() {
                tmp[j] = append(tmp[j], id)
            }
        }

        for i := range tmp {
            pValues[i] = tmp[i]
        }
    }

    return pValues
}

// PrimaryValue of this document.
// panic if document uses composite key.
func (c Collection) PrimaryValue() any {
    if values := c.PrimaryValues(); len(values) == 1 {
        return values[0]
    }

    panic("rel: composite primary key is not supported")
}

func (c Collection) rvIndex(index int) reflect.Value {
    return reflect.Indirect(c.rv.Index(index))
}

// Get an element from the underlying slice as a document.
func (c Collection) Get(index int) *Document {
    return NewDocument(c.rvIndex(index).Addr())
}

// Len of the underlying slice.
func (c Collection) Len() int {
    return c.rv.Len()
}

// Meta returns document meta.
func (c Collection) Meta() DocumentMeta {
    return c.meta
}

// Reset underlying slice to be zero length.
func (c Collection) Reset() {
    c.rv.Set(reflect.MakeSlice(c.rt, 0, 0))
}

// Add new document into collection.
func (c Collection) Add() *Document {
    c.Append(c.NewDocument())
    return c.Get(c.Len() - 1)
}

// NewDocument returns new document with zero values.
func (c Collection) NewDocument() *Document {
    return newZeroDocument(c.rt.Elem())
}

// Append new document into collection.
func (c Collection) Append(doc *Document) {
    if c.rt.Elem().Kind() == reflect.Ptr {
        c.rv.Set(reflect.Append(c.rv, doc.rv.Addr()))
    } else {
        c.rv.Set(reflect.Append(c.rv, doc.rv))
    }
}

// Truncate collection.
func (c Collection) Truncate(i, j int) {
    c.rv.Set(c.rv.Slice(i, j))
}

// Slice returns a new collection that is a slice of the original collection.s
func (c Collection) Slice(i, j int) *Collection {
    return NewCollection(c.rv.Slice(i, j), true)
}

// Swap element in the collection.
func (c Collection) Swap(i, j int) {
    if c.swapper == nil {
        c.swapper = reflect.Swapper(c.rv.Interface())
    }

    c.swapper(i, j)
}

// NewCollection used to create abstraction to work with slice.
// COllection can be created using interface or reflect.Value.
func NewCollection(entities any, readonly ...bool) *Collection {
    switch v := entities.(type) {
    case *Collection:
        return v
    case reflect.Value:
        return newCollection(v.Interface(), v, len(readonly) > 0 && readonly[0])
    case reflect.Type:
        panic("rel: cannot use reflect.Type")
    case nil:
        panic("rel: cannot be nil")
    default:
        return newCollection(v, reflect.ValueOf(v), len(readonly) > 0 && readonly[0])
    }
}

func newCollection(v any, rv reflect.Value, readonly bool) *Collection {
    var (
        rt = rv.Type()
    )

    if rt.Kind() != reflect.Ptr {
        if !readonly {
            panic("rel: must be a pointer to slice")
        }
    } else {
        rv = rv.Elem()
        rt = rt.Elem()
    }

    if rt.Kind() != reflect.Slice {
        panic("rel: must be a slice or pointer to a slice")
    }

    return &Collection{
        v:    v,
        rv:   rv,
        rt:   rt,
        meta: getDocumentMeta(indirectReflectType(rt.Elem()), false),
    }
}