-
Notifications
You must be signed in to change notification settings - Fork 0
/
orm.go
345 lines (297 loc) · 9.35 KB
/
orm.go
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// Package orm provides a simple wrapper for Hyperledger Fabric tables.
//
// Add an anonymous field orm.Saveable to your entities. At initialization, create a table as follows:
//
// user := new(User)
// if err := orm.CreateTable(stub, user); err != nil {
// return nil, errors.Wrap(err, "Failed creating table.")
// }
//
// For more info, see the README.md on github
package orm
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/pkg/errors"
"reflect"
)
// Items need to implement this interface to use ORM. You can use an anonymous Saveable in your struct.
type BlockchainItemizer interface {
GetId() int64
SetId(int64)
}
// Place an anonymous Saveable in your struct to use ORM.
type Saveable struct {
Id int64 `json:"id" key:"true"`
}
func (s *Saveable) GetId() int64 { return s.Id }
func (s *Saveable) SetId(id int64) { s.Id = id }
//
var columnDefinitions = map[string]shim.ColumnDefinition_Type {
"bool": shim.ColumnDefinition_BOOL,
//"[]uint8": shim.ColumnDefinition_BYTES, // TODO
"int32": shim.ColumnDefinition_INT32,
"int64": shim.ColumnDefinition_INT64,
"string": shim.ColumnDefinition_STRING,
"uint32": shim.ColumnDefinition_UINT32,
"uint64": shim.ColumnDefinition_UINT64,
"Saveable": shim.ColumnDefinition_INT64, // Id field (TODO: recursively find subfields of anonymous fields)
}
var logger = shim.NewLogger("orm")
// Create a table of the passed item. Types are automatically inferred.
func CreateTable(stub shim.ChaincodeStubInterface, item BlockchainItemizer) error {
name := reflect.TypeOf(item).Elem().Name()
logger.Infof("Create Table %s", name)
cds := createColumnDefinitions(item)
logger.Debugf("Columns: %v", cds)
return stub.CreateTable(name, cds)
}
// Get an item by Id
func Get(stub shim.ChaincodeStubInterface, item BlockchainItemizer, id int64) error {
if (id == 0) {
return errors.New("Id should be larger than 0")
}
// Query
var columns []shim.Column
col1 := shim.Column{Value: &shim.Column_Int64{Int64: id}} // How to set key?
columns = append(columns, col1)
// Table / Item name
name := reflect.TypeOf(item).Elem().Name()
// Get table
if tbl, err := stub.GetTable(name); err != nil {
return errors.Wrap(err, "Could not get table "+name)
// Get row based on query
} else if row, err := stub.GetRow(name, columns); err != nil {
return errors.Wrap(err, "Could not get "+name+" with id "+string(id))
// Set values of item based on row values
} else if err = setValues(tbl, row, item); err != nil {
return errors.Wrap(err, "Error setting values")
}
if (item.GetId() == 0) {
return errors.New("Item not found.")
}
logger.Debugf("Got item %v", item)
return nil
}
// Get all items by passing a slice of the correct type
func GetAll(stub shim.ChaincodeStubInterface, items interface{}) error {
v := reflect.ValueOf(items).Elem()
if v.Kind() != reflect.Slice {
return errors.New("Object passed to GetAll should be a slice.")
}
t := reflect.TypeOf(items).Elem().Elem()
name := t.Name();
//logger.Debugf("Getting all %vs", name)
// Query (TODO)
columns := []shim.Column{
// shim.Column{Value: &shim.Column_Int64{Int64: 1}},
// shim.Column{Value: &shim.Column_Int64{Int64: 2}},
}
tbl, err := stub.GetTable(name)
if err != nil {
return errors.Wrap(err, "Could not get table "+name)
}
rowChannel, err := stub.GetRows(name, columns)
if err != nil {
return fmt.Errorf("getRows operation failed. %s", err)
}
for {
select {
case row, ok := <-rowChannel:
if !ok {
rowChannel = nil
} else {
logger.Debugf("Columns: %v", row.Columns)
item := reflect.New(t).Interface()
if err:= setValues(tbl, row, item); err != nil {
return errors.Wrap(err, "Error setting values.")
}
logger.Debugf("Adding item: %v", item)
v.Set(reflect.Append(v, reflect.ValueOf(item).Elem()))
}
}
if rowChannel == nil {
break
}
}
return nil
}
// Insert a row for the item in the database
func Create(stub shim.ChaincodeStubInterface, item BlockchainItemizer) error {
t := reflect.TypeOf(item).Elem()
v := reflect.ValueOf(item).Elem()
logger.Infof("Creating %v: %v", t.Name(), v)
if id, err := generateId(stub, t.Name()); err != nil {
return errors.Wrap(err, "Generate id failed.")
} else {
item.SetId(id)
}
if row, err := createRow(t, v); err != nil {
return err
} else {
_, err := stub.InsertRow(t.Name(), row)
return err
}
}
// Update an item
func Update(stub shim.ChaincodeStubInterface, item BlockchainItemizer) error {
t := reflect.TypeOf(item).Elem()
v := reflect.ValueOf(item).Elem()
logger.Infof("Updating %v: %v", t.Name(), v)
if item.GetId() == 0 {
return errors.New("Item cannot have id 0")
}
if row, err := createRow(t, v); err != nil {
return err
} else {
_, err := stub.ReplaceRow(t.Name(), row)
return err
}
}
// Delete an item
func Delete(stub shim.ChaincodeStubInterface, item BlockchainItemizer) error {
t := reflect.TypeOf(item).Elem()
v := reflect.ValueOf(item).Elem()
logger.Infof("Deleting %v: %v", t.Name(), v)
if item.GetId() == 0 {
return errors.New("Item cannot have id 0")
}
columns := []shim.Column {
shim.Column{Value: &shim.Column_Int64{Int64: item.GetId()}},
}
return stub.DeleteRow(t.Name(), columns)
}
// Set the values of a retrieved row to an item
func setValues(tbl *shim.Table, row shim.Row, item interface{}) error {
v := reflect.Indirect(reflect.ValueOf(item))
if !v.IsValid() {
return errors.New("Zero value passed to setValues")
} else if !v.CanSet() {
return errors.New("Cannot set item")
}
// Get the column names and set the value based on the row values
for i, c := range row.GetColumns() {
name := tbl.ColumnDefinitions[i].Name
fieldType := tbl.ColumnDefinitions[i].Type //ColumnDefinition_Type
logger.Debugf("[%v] %v = %v", fieldType, name, c.GetValue())
f := v.FieldByName(name)
switch fieldType {
case shim.ColumnDefinition_BOOL:
f.SetBool(c.GetBool())
break
case shim.ColumnDefinition_BYTES:
f.SetBytes(c.GetBytes())
break
case shim.ColumnDefinition_INT32:
f.SetInt(int64(c.GetInt32())) // ???
break
case shim.ColumnDefinition_INT64:
f.SetInt(c.GetInt64())
break
case shim.ColumnDefinition_STRING:
f.SetString(c.GetString_())
break
case shim.ColumnDefinition_UINT32:
f.SetUint(uint64(c.GetUint32())) // ???
break
case shim.ColumnDefinition_UINT64:
f.SetUint(c.GetUint64())
break
default:
return errors.New("Type " + fieldType.String() + " not recognized.")
}
}
return nil
}
// Create a row
func createRow(t reflect.Type, v reflect.Value) (shim.Row, error) {
row := shim.Row{}
for i := 0; i < t.NumField(); i++ {
f := v.Field(i);
if !f.CanSet() {
continue // Field not exported?
}
if column, err := createColumnValue(t.Field(i), f.Interface()); err != nil {
return row, errors.Wrap(err, "Create item failed - Can't create column value")
} else {
row.Columns = append(row.Columns, &column)
}
}
return row, nil
}
// Create definitions for the table that will be created.
func createColumnDefinitions(iface interface{}) []*shim.ColumnDefinition {
defs := make([]*shim.ColumnDefinition, 0)
t := reflect.TypeOf(iface).Elem()
v := reflect.ValueOf(iface).Elem()
for i := 0; i < t.NumField(); i++ {
if !v.Field(i).CanSet() {
continue // Field not exported?
}
f := t.Field(i)
logger.Debugf("field: %v", f)
isKey := f.Tag.Get("key") == "true"
name := f.Name
if typ, ok := columnDefinitions[t.Field(i).Type.Name()]; ok {
// FIXME: this is a hack. Should be solved recursively
if t.Field(i).Type.Name() == "Saveable" {
name = "Id"
isKey = true
}
defs = append(defs, &shim.ColumnDefinition{Name: name, Type: typ, Key: isKey})
} else {
logger.Errorf("Field type not recognized: %v %v", f.Name, t.Field(i).Type.Name())
}
}
return defs
}
// Set the value of a field
func createColumnValue(field reflect.StructField, val interface{}) (shim.Column, error) {
switch field.Type.Name() {
case "bool":
return shim.Column{Value: &shim.Column_Bool{Bool: val.(bool)}}, nil
// case "[]uint8":
// return shim.Column{Value: &shim.Column_Bytes{Bytes: val.([]uint8)}}, nil // TODO
case "int32":
return shim.Column{Value: &shim.Column_Int32{Int32: val.(int32)}}, nil
case "int64":
return shim.Column{Value: &shim.Column_Int64{Int64: val.(int64)}}, nil
case "string":
return shim.Column{Value: &shim.Column_String_{String_: val.(string)}}, nil
case "uint32":
return shim.Column{Value: &shim.Column_Uint32{Uint32: val.(uint32)}}, nil
case "uint64":
return shim.Column{Value: &shim.Column_Uint64{Uint64: val.(uint64)}}, nil
case "Saveable":
return shim.Column{Value: &shim.Column_Int64{Int64: val.(Saveable).Id}}, nil //FIXME
}
return shim.Column{}, errors.New("Type of " + field.Type.Name() + " not recognized.")
}
// Generates an id that's one higher than the latest update.
// FIXME: race condition when creating multiple items in one call
func generateId(stub shim.ChaincodeStubInterface, tableName string) (int64, error) {
rowChannel, err := stub.GetRows(tableName, []shim.Column{})
if err != nil {
return 0, fmt.Errorf("getRows operation failed. %s", err)
}
id := int64(0)
for {
select {
case row, ok := <-rowChannel:
if !ok {
rowChannel = nil
} else {
logger.Debugf("Columns: %v", row.Columns)
if val := row.Columns[0].GetInt64(); val > id {
id = val
}
}
}
if rowChannel == nil {
break
}
}
id++
logger.Debugf("Generated id %d for %s", id, tableName)
return id, nil
}