collection.go

Summary

Maintainability
A
45 mins
Test Coverage
A
100%
package rel

import (
    "reflect"
)

type slice interface {
    table
    Reset()
    Add() *Document
    Get(index int) *Document
    Len() int
}

// Collection provides an abstraction over reflect to easily works with slice for database purpose.
type Collection struct {
    v       interface{}
    rv      reflect.Value
    rt      reflect.Type
    data    documentData
    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 {
    if tn, ok := c.v.(table); ok {
        return tn.Table()
    }

    return c.tableName()
}

func (c Collection) tableName() string {
    var (
        rt = c.rt.Elem()
    )

    // check for cache
    if name, cached := tablesCache.Load(rt); cached {
        return name.(string)
    }

    if rt.Implements(rtTable) {
        var (
            v = reflect.Zero(rt).Interface().(table)
        )

        tablesCache.Store(rt, v.Table())
        return v.Table()
    }

    return tableName(rt)
}

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

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

    return c.data.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() []interface{} {
    if p, ok := c.v.(primary); ok {
        return p.PrimaryValues()
    }

    var (
        index   = c.data.primaryIndex
        pValues = make([]interface{}, len(c.PrimaryFields()))
    )

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

            for j := range values {
                values[j] = c.rv.Index(j).Field(index[i]).Interface()
            }

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

        for i := 0; i < c.rv.Len(); i++ {
            for j, id := range c.rv.Index(i).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() interface{} {
    if values := c.PrimaryValues(); len(values) == 1 {
        return values[0]
    }

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

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

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

// 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 {
    var (
        index = c.Len()
        typ   = c.rt.Elem()
        drv   = reflect.Zero(typ)
    )

    c.rv.Set(reflect.Append(c.rv, drv))

    return NewDocument(c.rv.Index(index).Addr())
}

// 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(records interface{}, readonly ...bool) *Collection {
    switch v := records.(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 interface{}, 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,
        data: extractDocumentData(rt.Elem(), false),
    }
}