summaryrefslogtreecommitdiff
path: root/vendor/github.com/go-pg/pg/v10/orm/model.go
blob: 333a90dd77906a99093ba65cc47c296d5b31a6be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package orm

import (
	"database/sql"
	"errors"
	"fmt"
	"reflect"

	"github.com/go-pg/pg/v10/types"
)

var errModelNil = errors.New("pg: Model(nil)")

type useQueryOne interface {
	useQueryOne() bool
}

type HooklessModel interface {
	// Init is responsible to initialize/reset model state.
	// It is called only once no matter how many rows were returned.
	Init() error

	// NextColumnScanner returns a ColumnScanner that is used to scan columns
	// from the current row. It is called once for every row.
	NextColumnScanner() ColumnScanner

	// AddColumnScanner adds the ColumnScanner to the model.
	AddColumnScanner(ColumnScanner) error
}

type Model interface {
	HooklessModel

	AfterScanHook
	AfterSelectHook

	BeforeInsertHook
	AfterInsertHook

	BeforeUpdateHook
	AfterUpdateHook

	BeforeDeleteHook
	AfterDeleteHook
}

func NewModel(value interface{}) (Model, error) {
	return newModel(value, false)
}

func newScanModel(values []interface{}) (Model, error) {
	if len(values) > 1 {
		return Scan(values...), nil
	}
	return newModel(values[0], true)
}

func newModel(value interface{}, scan bool) (Model, error) {
	switch value := value.(type) {
	case Model:
		return value, nil
	case HooklessModel:
		return newModelWithHookStubs(value), nil
	case types.ValueScanner, sql.Scanner:
		if !scan {
			return nil, fmt.Errorf("pg: Model(unsupported %T)", value)
		}
		return Scan(value), nil
	}

	v := reflect.ValueOf(value)
	if !v.IsValid() {
		return nil, errModelNil
	}
	if v.Kind() != reflect.Ptr {
		return nil, fmt.Errorf("pg: Model(non-pointer %T)", value)
	}

	if v.IsNil() {
		typ := v.Type().Elem()
		if typ.Kind() == reflect.Struct {
			return newStructTableModel(GetTable(typ)), nil
		}
		return nil, errModelNil
	}

	v = v.Elem()

	if v.Kind() == reflect.Interface {
		if !v.IsNil() {
			v = v.Elem()
			if v.Kind() != reflect.Ptr {
				return nil, fmt.Errorf("pg: Model(non-pointer %s)", v.Type().String())
			}
		}
	}

	switch v.Kind() {
	case reflect.Struct:
		if v.Type() != timeType {
			return newStructTableModelValue(v), nil
		}
	case reflect.Slice:
		elemType := sliceElemType(v)
		switch elemType.Kind() {
		case reflect.Struct:
			if elemType != timeType {
				return newSliceTableModel(v, elemType), nil
			}
		case reflect.Map:
			if err := validMap(elemType); err != nil {
				return nil, err
			}
			slicePtr := v.Addr().Interface().(*[]map[string]interface{})
			return newMapSliceModel(slicePtr), nil
		}
		return newSliceModel(v, elemType), nil
	case reflect.Map:
		typ := v.Type()
		if err := validMap(typ); err != nil {
			return nil, err
		}
		mapPtr := v.Addr().Interface().(*map[string]interface{})
		return newMapModel(mapPtr), nil
	}

	if !scan {
		return nil, fmt.Errorf("pg: Model(unsupported %T)", value)
	}
	return Scan(value), nil
}

type modelWithHookStubs struct {
	hookStubs
	HooklessModel
}

func newModelWithHookStubs(m HooklessModel) Model {
	return modelWithHookStubs{
		HooklessModel: m,
	}
}

func validMap(typ reflect.Type) error {
	if typ.Key().Kind() != reflect.String || typ.Elem().Kind() != reflect.Interface {
		return fmt.Errorf("pg: Model(unsupported %s, expected *map[string]interface{})",
			typ.String())
	}
	return nil
}