/
cellExpr.go
358 lines (300 loc) · 9.83 KB
/
cellExpr.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
346
347
348
349
350
351
352
353
354
355
356
357
358
// Copyright (c) 2016 OpenM++
// This code is licensed under the MIT license (see LICENSE.txt for details)
package db
import (
"errors"
"fmt"
"strconv"
)
// CellExpr is value of output table expression.
type CellExpr struct {
cellIdValue // dimensions and value
ExprId int // output table expression id
}
// CellCodeExpr is value of output table expression.
// Dimension(s) items are enum codes, not enum ids.
type CellCodeExpr struct {
cellCodeValue // dimensions as enum codes and value
ExprId int // output table expression id
}
// CsvFileName return file name of csv file to store output table expression rows
func (CellExpr) CsvFileName(modelDef *ModelMeta, name string, isIdCsv bool) (string, error) {
// validate parameters
if modelDef == nil {
return "", errors.New("invalid (empty) model metadata, look like model not found")
}
if name == "" {
return "", errors.New("invalid (empty) output table name")
}
// find output table by name
k, ok := modelDef.OutTableByName(name)
if !ok {
return "", errors.New("output table not found: " + name)
}
if isIdCsv {
return modelDef.Table[k].Name + ".id.csv", nil
}
return modelDef.Table[k].Name + ".csv", nil
}
// CsvHeader retrun first line for csv file: column names.
// It is like: expr_name,dim0,dim1,expr_value
// or if isIdHeader is true: expr_id,dim0,dim1,expr_value
func (CellExpr) CsvHeader(modelDef *ModelMeta, name string, isIdHeader bool, valueName string) ([]string, error) {
// validate parameters
if modelDef == nil {
return nil, errors.New("invalid (empty) model metadata, look like model not found")
}
if name == "" {
return nil, errors.New("invalid (empty) output table name")
}
// find output table by name
k, ok := modelDef.OutTableByName(name)
if !ok {
return nil, errors.New("output table not found: " + name)
}
table := &modelDef.Table[k]
// make first line columns
h := make([]string, table.Rank+2)
if isIdHeader {
h[0] = "expr_id"
} else {
h[0] = "expr_name"
}
for k := range table.Dim {
h[k+1] = table.Dim[k].Name
}
h[table.Rank+1] = "expr_value"
return h, nil
}
// CsvToIdRow return converter from output table cell (expr_id, dimensions, value) to csv row []string.
//
// Converter simply does Sprint() for each dimension item id, expression id and value.
// Converter will retrun error if len(row) not equal to number of fields in csv record.
// Double format string is used if parameter type is float, double, long double
func (CellExpr) CsvToIdRow(
modelDef *ModelMeta, name string, doubleFmt string, valueName string,
) (
func(interface{}, []string) error, error) {
cvt := func(src interface{}, row []string) error {
cell, ok := src.(CellExpr)
if !ok {
return errors.New("invalid type, expected: output table expression cell (internal error)")
}
n := len(cell.DimIds)
if len(row) != n+2 {
return errors.New("invalid size of csv row buffer, expected: " + strconv.Itoa(n+2))
}
row[0] = fmt.Sprint(cell.ExprId)
for k, e := range cell.DimIds {
row[k+1] = fmt.Sprint(e)
}
// use "null" string for db NULL values and format for model float types
if cell.IsNull {
row[n+1] = "null"
} else {
if doubleFmt != "" {
row[n+1] = fmt.Sprintf(doubleFmt, cell.Value)
} else {
row[n+1] = fmt.Sprint(cell.Value)
}
}
return nil
}
return cvt, nil
}
// CsvToRow return converter from output table cell (expr_id, dimensions, value)
// to csv row []string (expr_name, dimensions, value).
//
// Converter will retrun error if len(row) not equal to number of fields in csv record.
// Double format string is used if parameter type is float, double, long double.
// If dimension type is enum based then csv row is enum code and cell.DimIds is enum id.
func (CellExpr) CsvToRow(
modelDef *ModelMeta, name string, doubleFmt string, valueName string,
) (
func(interface{}, []string) error, error) {
// validate parameters
if modelDef == nil {
return nil, errors.New("invalid (empty) model metadata, look like model not found")
}
if name == "" {
return nil, errors.New("invalid (empty) output table name")
}
// find output table by name
k, ok := modelDef.OutTableByName(name)
if !ok {
return nil, errors.New("output table not found: " + name)
}
table := &modelDef.Table[k]
// for each dimension create converter from item id to code
fd := make([]func(itemId int) (string, error), table.Rank)
for k := 0; k < table.Rank; k++ {
f, err := cvtItemIdToCode(name+"."+table.Dim[k].Name, table.Dim[k].typeOf, table.Dim[k].IsTotal)
if err != nil {
return nil, err
}
fd[k] = f
}
cvt := func(src interface{}, row []string) error {
cell, ok := src.(CellExpr)
if !ok {
return errors.New("invalid type, expected: output table expression cell (internal error)")
}
n := len(cell.DimIds)
if len(row) != n+2 {
return errors.New("invalid size of csv row buffer, expected: " + strconv.Itoa(n+2))
}
row[0] = table.Expr[cell.ExprId].Name
// convert dimension item id to code
for k, e := range cell.DimIds {
v, err := fd[k](e)
if err != nil {
return err
}
row[k+1] = v
}
// use "null" string for db NULL values and format for model float types
if cell.IsNull {
row[n+1] = "null"
} else {
if doubleFmt != "" {
row[n+1] = fmt.Sprintf(doubleFmt, cell.Value)
} else {
row[n+1] = fmt.Sprint(cell.Value)
}
}
return nil
}
return cvt, nil
}
// CsvToCell return closure to convert csv row []string to output table expression cell (dimensions and value).
//
// It does retrun error if len(row) not equal to number of fields in cell db-record.
// If dimension type is enum based then csv row is enum code and cell.DimIds is enum id.
func (CellExpr) CsvToCell(
modelDef *ModelMeta, name string, subCount int, valueName string,
) (
func(row []string) (interface{}, error), error) {
// validate parameters
if modelDef == nil {
return nil, errors.New("invalid (empty) model metadata, look like model not found")
}
if name == "" {
return nil, errors.New("invalid (empty) output table name")
}
// find output table by name
k, ok := modelDef.OutTableByName(name)
if !ok {
return nil, errors.New("output table not found: " + name)
}
table := &modelDef.Table[k]
// for each dimension create converter from item code to id
fd := make([]func(src string) (int, error), table.Rank)
for k := 0; k < table.Rank; k++ {
f, err := cvtItemCodeToId(name+"."+table.Dim[k].Name, table.Dim[k].typeOf, table.Dim[k].IsTotal)
if err != nil {
return nil, err
}
fd[k] = f
}
// do conversion
cvt := func(row []string) (interface{}, error) {
// make conversion buffer and check input csv row size
cell := CellExpr{cellIdValue: cellIdValue{DimIds: make([]int, table.Rank)}}
n := len(cell.DimIds)
if len(row) != n+2 {
return nil, errors.New("invalid size of csv row, expected: " + strconv.Itoa(n+2))
}
// expression id by name
cell.ExprId = -1
for k := range table.Expr {
if row[0] == table.Expr[k].Name {
cell.ExprId = k
break
}
}
if cell.ExprId < 0 {
return nil, errors.New("invalid expression name: " + row[0] + " output table: " + name)
}
// convert dimensions: enum code to enum id or integer value for simple type dimension
for k := range cell.DimIds {
i, err := fd[k](row[k+1])
if err != nil {
return nil, err
}
cell.DimIds[k] = i
}
// value conversion
cell.IsNull = row[n+1] == "" || row[n+1] == "null"
if cell.IsNull {
cell.Value = 0.0
} else {
v, err := strconv.ParseFloat(row[n+1], 64)
if err != nil {
return nil, err
}
cell.Value = v
}
return cell, nil
}
return cvt, nil
}
// IdToCodeCell return converter from output table cell of ids: (expr_id, dimensions enum ids, value)
// to cell of codes: (expr_id, dimensions as enum codes, value).
//
// If dimension type is enum based then dimensions enum ids can be converted to enum code.
// If dimension type is simple (bool or int) then dimension value converted to string.
func (CellExpr) IdToCodeCell(
modelDef *ModelMeta, name string,
) (
func(interface{}) (interface{}, error), error) {
// validate parameters
if modelDef == nil {
return nil, errors.New("invalid (empty) model metadata, look like model not found")
}
if name == "" {
return nil, errors.New("invalid (empty) output table name")
}
// find output table by name
k, ok := modelDef.OutTableByName(name)
if !ok {
return nil, errors.New("output table not found: " + name)
}
table := &modelDef.Table[k]
// for each dimension create converter from item id to code
fd := make([]func(itemId int) (string, error), table.Rank)
for k := 0; k < table.Rank; k++ {
f, err := cvtItemIdToCode(name+"."+table.Dim[k].Name, table.Dim[k].typeOf, table.Dim[k].IsTotal)
if err != nil {
return nil, err
}
fd[k] = f
}
// create cell converter
cvt := func(src interface{}) (interface{}, error) {
srcCell, ok := src.(CellExpr)
if !ok {
return nil, errors.New("invalid type, expected: output table expression cell (internal error)")
}
if len(srcCell.DimIds) != table.Rank {
return nil, errors.New("invalid cell rank: " + strconv.Itoa(len(srcCell.DimIds)) + ", expected: " + strconv.Itoa(table.Rank))
}
dstCell := CellCodeExpr{
cellCodeValue: cellCodeValue{
Dims: make([]string, table.Rank),
IsNull: srcCell.IsNull,
Value: srcCell.Value,
},
ExprId: srcCell.ExprId,
}
// convert dimension item id to code
for k := range srcCell.DimIds {
v, err := fd[k](srcCell.DimIds[k])
if err != nil {
return nil, err
}
dstCell.Dims[k] = v
}
return dstCell, nil // converted OK
}
return cvt, nil
}