-
Notifications
You must be signed in to change notification settings - Fork 0
/
library.go
205 lines (182 loc) · 6.73 KB
/
library.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
// A Golang JSON document-file based database allowing complex SELECT queries and multiple aggregations.
package jdocdb
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"strings"
"sync"
"github.com/rodolfoap/gx"
)
var mutex sync.Mutex
// Structure used for unmarshaling each register. The Id value is generated and
// kept while recording, because it duplicates the file name.
type Register struct {
Id string
Data interface{}
}
// Inserts one registry into a table using its ID, prefix is a set of dir/subdirectories
func Insert[T interface{}](id string, doc T, prefix ...string) {
defer mutex.Unlock()
mutex.Lock()
reg := Register{Id: id, Data: doc}
table := buildPath(getType(doc), prefix...)
err := os.MkdirAll(table, 0755)
gx.Fatal(err)
jsonPath := filepath.Join(table, id+".json")
jsonBytes, err := json.MarshalIndent(reg, "", "\t")
gx.Error(err)
jsonBytes = append(jsonBytes, byte('\n'))
err = os.WriteFile(jsonPath, jsonBytes, 0644)
gx.Fatal(err)
gx.Tracef("JDocDB INSERT: %v", jsonPath)
}
// Selects one registry from a table using its ID, prefix is a set of dir/subdirectories.
// This just reads a single file, unmarshals it and returns a (generic) structure.
func Select[T interface{}](id string, doc T, prefix ...string) T {
defer mutex.Unlock()
mutex.Lock()
reg := Register{Id: id, Data: &doc}
table := buildPath(getType(doc), prefix...)
jsonPath := filepath.Join(table, id+".json")
jsonBytes, err := os.ReadFile(jsonPath)
if err != nil {
gx.Tracef("Data file %v not found.", jsonPath)
return doc
}
err = json.Unmarshal(jsonBytes, ®)
gx.Error(err)
gx.Tracef("JDocDB SELECT: %v", jsonPath)
return doc
}
// Select all IDs of a table, prefix is a set of dir/subdirectories.
// Internally, just finding, not unmarshaling, .json files in a directory,
// since IDs are just filename prefixes.
func SelectIds[T interface{}](doc T, prefix ...string) []string {
defer mutex.Unlock()
mutex.Lock()
idList := []string{}
table := buildPath(getType(doc), prefix...)
fileList, err := os.ReadDir(table)
gx.Error(err)
for _, f := range fileList {
if strings.HasSuffix(f.Name(), ".json") {
idList = append(idList, strings.TrimSuffix(f.Name(), ".json"))
}
}
gx.Trace("JDocDB SELECT_IDS: ", table, idList)
return idList
}
// Selects all rows from a table, prefix is a set of dir/subdirectories.
// Combines SelectIds(), which provides the list of files, and Select(), which
// unmarshals each file into the structure provided as a generic type.
func SelectAll[T interface{}](doc T, prefix ...string) map[string]T {
docs := map[string]T{}
for _, id := range SelectIds(doc, prefix...) {
docs[id] = Select(id, doc, prefix...)
}
gx.Tracef("JDocDB SELECT_ALL: %v/%v", strings.Join(prefix, "/"), keys(docs))
return docs
}
// Selects all rows that meet some conditions, prefix is a set of dir/subdirectories.
// This applies the condition over each unmarshaled struct, providing the matching result in a map.
func SelectWhere[T interface{}](doc T, cond func(T) bool, prefix ...string) map[string]T {
docs := map[string]T{}
for key, val := range SelectAll(doc, prefix...) {
if cond(val) {
docs[key] = val
}
}
gx.Trace("JDocDB SELECT_WHERE: ", docs)
return docs
}
// Selects all rows that meet the conditions provided in the passing function, prefix
// is a set of dir/subdirectories.
func SelectIdWhere[T interface{}](doc T, cond func(T) bool, prefix ...string) []string {
keys := keys(SelectWhere(doc, cond, prefix...))
gx.Trace("JDocDB SELECT_ID_WHERE: ", keys)
return keys
}
// Selects all rows that meet some conditions, prefix is a set of dir/subdirectories.
func SelectWhereAggreg[T interface{}, A interface{}](doc T, cond func(T) bool, aggregator *A, aggregate func(string, T), prefix ...string) map[string]T {
_docs := SelectWhere(doc, cond, prefix...)
for _key, _val := range _docs {
aggregate(_key, _val)
}
gx.Trace("JDocDB SELECT_WHERE_AGGREG: ", _docs, *aggregator)
return _docs
}
// Equivalent to SelectWhereAggreg() except without WHERE clause.
func SelectAggreg[T interface{}, A interface{}](doc T, aggregator *A, aggregate func(string, T), prefix ...string) map[string]T {
_docs := SelectAll(doc, prefix...)
for _key, _val := range _docs {
aggregate(_key, _val)
}
gx.Trace("JDocDB SELECT_AGGREG: ", _docs, *aggregator)
return _docs
}
// Equivalent to SelectWhereAggreg() except that it returns just a count
func CountWhereAggreg[T interface{}, A interface{}](doc T, cond func(T) bool, aggregator *A, aggregate func(string, T), prefix ...string) int {
_docs, _count := SelectWhere(doc, cond, prefix...), 0
for key, val := range _docs {
_count += 1
aggregate(key, val)
}
gx.Trace("JDocDB COUNT_WHERE_AGGREG: ", _docs, *aggregator)
return _count
}
// Equivalent to SelectWhereAggreg() except that it returns just a count
func CountWhere[T interface{}](doc T, cond func(T) bool, prefix ...string) int {
docs := SelectWhere(doc, cond, prefix...)
gx.Trace("JDocDB COUNT_WHERE: ", len(docs))
return len(docs)
}
// Equivalent to CountWhereAggreg() except without WHERE conditionals
func CountAggreg[T interface{}, A interface{}](doc T, aggregator *A, aggregate func(string, T), prefix ...string) int {
_docs, _count := SelectAll(doc, prefix...), 0
for key, val := range _docs {
_count += 1
aggregate(key, val)
}
gx.Trace("JDocDB COUNT_AGGREG: ", _docs, *aggregator)
return _count
}
// Simple count of all registers
func Count[T interface{}](doc T, prefix ...string) int {
docs := SelectAll(doc, prefix...)
gx.Trace("JDocDB COUNT: ", len(docs))
return len(docs)
}
// Simple sum of all registers
func Sum[T interface{}](doc T, fieldName string, prefix ...string) int {
_docs, _sum := SelectAll(doc, prefix...), 0
var data []int
for _, doc := range _docs {
data = append(data, int(reflect.Indirect(reflect.ValueOf(doc)).FieldByName(fieldName).Int()))
}
gx.Trace("JDocDB SUM: ", _sum, data)
// Getting an array would simplify a lot any future aggregate calculation.
return sum(data)
}
// Simple sum of all registers fulfilling a WHERE condition
func SumWhere[T interface{}](doc T, fieldName string, cond func(T) bool, prefix ...string) int {
_docs, _sum := SelectWhere(doc, cond, prefix...), 0
var data []int
for _, doc := range _docs {
data = append(data, int(reflect.Indirect(reflect.ValueOf(doc)).FieldByName(fieldName).Int()))
}
gx.Trace("JDocDB SUM: ", _sum, data)
// Getting an array would simplify a lot any future aggregate calculation.
return sum(data)
}
// Deletes one registry from a table using its ID, prefix is a set of dir/subdirectories
func Delete[T interface{}](id string, doc T, prefix ...string) {
defer mutex.Unlock()
mutex.Lock()
table := buildPath(getType(doc), prefix...)
jsonPath := filepath.Join(table, id+".json")
err := os.Remove(jsonPath)
gx.Fatal(err)
gx.Tracef("JDocDB DELETE: %v", jsonPath)
}