Commit 2ea67927f5e97161f0dfc7d0b7c2c33aff370d2a
1 parent
6620591d86
Exists in
master
and in
1 other branch
added support for metadata update
Showing
2 changed files
with
214 additions
and
84 deletions
Show diff stats
http_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | "net/http" | 5 | "net/http" |
6 | ) | 6 | ) |
7 | 7 | ||
8 | const ( | 8 | const ( |
9 | templateHttpErr500_EN = "An internal server error has occurred." | 9 | templateHttpErr500_EN = "An internal server error has occurred." |
10 | templateHttpErr500_RS = "Došlo je do greške na serveru." | 10 | templateHttpErr500_RS = "Došlo je do greške na serveru." |
11 | templateHttpErr400_EN = "Bad request: invalid request body." | 11 | templateHttpErr400_EN = "Bad request." |
12 | templateHttpErr400_RS = "Neispravan zahtev." | 12 | templateHttpErr400_RS = "Neispravan zahtev." |
13 | templateHttpErr404_EN = "Resource not found." | 13 | templateHttpErr404_EN = "Resource not found." |
14 | templateHttpErr404_RS = "Objekat nije pronadjen." | 14 | templateHttpErr404_RS = "Objekat nije pronadjen." |
15 | templateHttpErr401_EN = "Unauthorized request." | 15 | templateHttpErr401_EN = "Unauthorized request." |
16 | templateHttpErr401_RS = "Neautorizovan zahtev." | 16 | templateHttpErr401_RS = "Neautorizovan zahtev." |
17 | ) | 17 | ) |
18 | 18 | ||
19 | type httpError struct { | 19 | type httpError struct { |
20 | Error []HttpErrorDesc `json:"error"` | 20 | Error []HttpErrorDesc `json:"error"` |
21 | Request string `json:"request"` | 21 | Request string `json:"request"` |
22 | } | 22 | } |
23 | 23 | ||
24 | type HttpErrorDesc struct { | 24 | type HttpErrorDesc struct { |
25 | Lang string `json:"lang"` | 25 | Lang string `json:"lang"` |
26 | Desc string `json:"description"` | 26 | Desc string `json:"description"` |
27 | } | 27 | } |
28 | 28 | ||
29 | // DeliverPayload encodes payload as JSON to w. | 29 | // DeliverPayload encodes payload as JSON to w. |
30 | func DeliverPayload(w http.ResponseWriter, payload Payload) { | 30 | func DeliverPayload(w http.ResponseWriter, payload Payload) { |
31 | // Don't write status OK in the headers here. Leave it up for the caller. | 31 | // Don't write status OK in the headers here. Leave it up for the caller. |
32 | // E.g. Status 201. | 32 | // E.g. Status 201. |
33 | json.NewEncoder(w).Encode(payload) | 33 | json.NewEncoder(w).Encode(payload) |
34 | payload.Data = nil | 34 | payload.Data = nil |
35 | } | 35 | } |
36 | 36 | ||
37 | // ErrorResponse writes HTTP error to w. | 37 | // ErrorResponse writes HTTP error to w. |
38 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { | 38 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { |
39 | err := httpError{desc, r.Method + " " + r.RequestURI} | 39 | err := httpError{desc, r.Method + " " + r.RequestURI} |
40 | w.WriteHeader(code) | 40 | w.WriteHeader(code) |
41 | json.NewEncoder(w).Encode(err) | 41 | json.NewEncoder(w).Encode(err) |
42 | } | 42 | } |
43 | 43 | ||
44 | // NotFoundResponse writes HTTP error 404 to w. | 44 | // NotFoundResponse writes HTTP error 404 to w. |
45 | func NotFoundResponse(w http.ResponseWriter, req *http.Request) { | 45 | func NotFoundResponse(w http.ResponseWriter, req *http.Request) { |
46 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ | 46 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ |
47 | {"en", templateHttpErr404_EN}, | 47 | {"en", templateHttpErr404_EN}, |
48 | {"rs", templateHttpErr404_RS}, | 48 | {"rs", templateHttpErr404_RS}, |
49 | }) | 49 | }) |
50 | } | 50 | } |
51 | 51 | ||
52 | // BadRequestResponse writes HTTP error 400 to w. | 52 | // BadRequestResponse writes HTTP error 400 to w. |
53 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { | 53 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { |
54 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ | 54 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ |
55 | {"en", templateHttpErr400_EN}, | 55 | {"en", templateHttpErr400_EN}, |
56 | {"rs", templateHttpErr400_RS}, | 56 | {"rs", templateHttpErr400_RS}, |
57 | }) | 57 | }) |
58 | } | 58 | } |
59 | 59 | ||
60 | // InternalSeverErrorResponse writes HTTP error 500 to w. | 60 | // InternalSeverErrorResponse writes HTTP error 500 to w. |
61 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { | 61 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { |
62 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ | 62 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ |
63 | {"en", templateHttpErr500_EN}, | 63 | {"en", templateHttpErr500_EN}, |
64 | {"rs", templateHttpErr500_RS}, | 64 | {"rs", templateHttpErr500_RS}, |
65 | }) | 65 | }) |
66 | } | 66 | } |
67 | 67 | ||
68 | // UnauthorizedError writes HTTP error 401 to w. | 68 | // UnauthorizedError writes HTTP error 401 to w. |
69 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { | 69 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { |
70 | w.Header().Set("WWW-Authenticate", "Bearer") | 70 | w.Header().Set("WWW-Authenticate", "Bearer") |
71 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ | 71 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ |
72 | {"en", templateHttpErr401_EN}, | 72 | {"en", templateHttpErr401_EN}, |
73 | {"rs", templateHttpErr401_RS}, | 73 | {"rs", templateHttpErr401_RS}, |
74 | }) | 74 | }) |
75 | } | 75 | } |
76 | 76 | ||
77 | // NotFoundHandler writes HTTP error 404 to w. | 77 | // NotFoundHandler writes HTTP error 404 to w. |
78 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { | 78 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { |
79 | SetDefaultHeaders(w) | 79 | SetDefaultHeaders(w) |
80 | if req.Method == "OPTIONS" { | 80 | if req.Method == "OPTIONS" { |
81 | return | 81 | return |
82 | } | 82 | } |
83 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ | 83 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ |
84 | {"en", "Not found."}, | 84 | {"en", "Not found."}, |
85 | {"rs", "Traženi resurs ne postoji."}, | 85 | {"rs", "Traženi resurs ne postoji."}, |
86 | }) | 86 | }) |
87 | } | 87 | } |
88 | 88 | ||
89 | // SetDefaultHeaders set's default headers for an HTTP response. | 89 | // SetDefaultHeaders set's default headers for an HTTP response. |
90 | func SetDefaultHeaders(w http.ResponseWriter) { | 90 | func SetDefaultHeaders(w http.ResponseWriter) { |
91 | w.Header().Set("Access-Control-Allow-Origin", "*") | 91 | w.Header().Set("Access-Control-Allow-Origin", "*") |
92 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | 92 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") |
93 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, | 93 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, |
94 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) | 94 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) |
95 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | 95 | w.Header().Set("Content-Type", "application/json; charset=utf-8") |
96 | } | 96 | } |
97 | 97 |
json_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | "errors" | 5 | "errors" |
6 | "fmt" | 6 | "fmt" |
7 | "io" | 7 | "io" |
8 | "net/http" | 8 | "net/http" |
9 | "os" | ||
9 | "sync" | 10 | "sync" |
11 | "time" | ||
10 | 12 | ||
11 | "gopkg.in/rana/ora.v4" | 13 | "gopkg.in/rana/ora.v4" |
12 | ) | 14 | ) |
13 | 15 | ||
14 | var mu = &sync.Mutex{} | 16 | var ( |
15 | var payloads []Payload | 17 | mu = &sync.Mutex{} |
18 | metadata map[string]Payload | ||
19 | |||
20 | metadataDB *ora.Ses | ||
21 | activeProject string | ||
22 | |||
23 | inited bool | ||
24 | ) | ||
16 | 25 | ||
17 | type LangMap map[string]map[string]string | 26 | type LangMap map[string]map[string]string |
18 | 27 | ||
19 | type Field struct { | 28 | type Field struct { |
20 | Parameter string `json:"param"` | 29 | Parameter string `json:"param"` |
21 | Type string `json:"type"` | 30 | Type string `json:"type"` |
22 | Visible bool `json:"visible"` | 31 | Visible bool `json:"visible"` |
23 | Editable bool `json:"editable"` | 32 | Editable bool `json:"editable"` |
24 | } | 33 | } |
25 | 34 | ||
26 | type CorrelationField struct { | 35 | type CorrelationField struct { |
27 | Result string `json:"result"` | 36 | Result string `json:"result"` |
28 | Elements []string `json:"elements"` | 37 | Elements []string `json:"elements"` |
29 | Type string `json:"type"` | 38 | Type string `json:"type"` |
30 | } | 39 | } |
31 | 40 | ||
32 | type Translation struct { | 41 | type Translation struct { |
33 | Language string `json:"language"` | 42 | Language string `json:"language"` |
34 | FieldsLabels map[string]string `json:"fieldsLabels"` | 43 | FieldsLabels map[string]string `json:"fieldsLabels"` |
35 | } | 44 | } |
36 | 45 | ||
37 | type Payload struct { | 46 | type Payload struct { |
38 | Type string `json:"type"` | ||
39 | Method string `json:"method"` | 47 | Method string `json:"method"` |
40 | Params map[string]string `json:"params"` | 48 | Params map[string]string `json:"params"` |
41 | Lang []Translation `json:"lang"` | 49 | Lang []Translation `json:"lang"` |
42 | Fields []Field `json:"fields"` | 50 | Fields []Field `json:"fields"` |
43 | Correlations []CorrelationField `json:"correlationFields"` | 51 | Correlations []CorrelationField `json:"correlationFields"` |
44 | IdField string `json:"idField"` | 52 | IdField string `json:"idField"` |
45 | 53 | ||
46 | // Data holds JSON payload. It can't be used for itteration. | 54 | // Data holds JSON payload. It can't be used for itteration. |
47 | Data interface{} `json:"data"` | 55 | Data interface{} `json:"data"` |
48 | } | 56 | } |
49 | 57 | ||
50 | // InitPayloadsMetaData loads all payloads in the payloads variable. | 58 | // LoadPayloadsdetaData loads all payloads' information into 'metadata' variable. |
51 | // Returns an error if it fails. | 59 | func LoadPayloadsMetadata(db *ora.Ses, project string, hotloading bool, hlPeriod int) error { |
52 | func InitPayloadsMetaData(db *ora.Ses, project string) error { | 60 | metadataDB = db |
53 | payloads = make([]Payload, 0) | 61 | activeProject = project |
54 | 62 | ||
55 | jsonbuf, err := fetchJSON(db, project) | 63 | mu.Lock() |
64 | defer mu.Unlock() | ||
65 | err := initMetadata(project) | ||
56 | if err != nil { | 66 | if err != nil { |
57 | return err | 67 | return err |
58 | } | 68 | } |
69 | if hotloading { | ||
70 | go hotload(hlPeriod) | ||
71 | } | ||
72 | inited = true | ||
73 | |||
74 | return nil | ||
75 | } | ||
76 | |||
77 | func UpdateMetadataModels(md map[string][]byte) (upd, add int, err error) { | ||
78 | if !inited { | ||
79 | return 0, 0, errors.New("webutil: metadata not initialized but update was tried.") | ||
80 | } | ||
81 | |||
82 | forUpdate := make([]string, 0) | ||
83 | forCreate := make([]string, 0) | ||
84 | |||
85 | for k, _ := range md { | ||
86 | if _, exists := metadata[k]; exists { | ||
87 | forUpdate = append(forUpdate, k) | ||
88 | } else { | ||
89 | forCreate = append(forCreate, k) | ||
90 | } | ||
91 | } | ||
92 | |||
93 | for _, k := range forUpdate { | ||
94 | fmt.Printf("for update: %s\n", k) | ||
95 | _, err := metadataDB.PrepAndExe(`update entities set | ||
96 | entity_model = :1 | ||
97 | where entity_type = :2`, | ||
98 | string(md[k]), | ||
99 | k) | ||
100 | |||
101 | if err != nil { | ||
102 | fmt.Printf("webutility: update metadata: prep and exe: %v\n", err) | ||
103 | continue | ||
104 | } | ||
105 | upd++ | ||
106 | } | ||
107 | |||
108 | for _, k := range forCreate { | ||
109 | fmt.Printf("for add: %s\n", k) | ||
110 | /* | ||
111 | _, err := metadataDB.PrepAndExe(`insert into entities | ||
112 | (projekat, metadata, entity_type, entity_model) | ||
113 | values(:1, :2, :3, :4)`, | ||
114 | activeProject, "", k, string(md[k])) | ||
115 | |||
116 | if err != nil { | ||
117 | fmt.Printf("webutility: add metadata: prep and exe: %v\n", err) | ||
118 | continue | ||
119 | } | ||
120 | */ | ||
121 | add++ | ||
122 | } | ||
123 | |||
124 | return upd, add, nil | ||
125 | } | ||
126 | |||
127 | func GetMetadataForAllEntities() map[string]Payload { | ||
128 | return metadata | ||
129 | } | ||
130 | |||
131 | func GetMetadataForEntityType(t string) Payload { | ||
132 | return metadata[t] | ||
133 | } | ||
59 | 134 | ||
135 | func UpdateMetadata(entityType string, p *Payload) error { | ||
136 | md, err := json.Marshal(p) | ||
137 | if err != nil { | ||
138 | return err | ||
139 | } | ||
140 | fmt.Printf("md: %s %s\n", entityType, string(md)) | ||
60 | mu.Lock() | 141 | mu.Lock() |
61 | defer mu.Unlock() | 142 | defer mu.Unlock() |
62 | json.Unmarshal(jsonbuf, &payloads) | 143 | _, err = metadataDB.PrepAndExe(`update entities set |
63 | if len(payloads) == 0 { | 144 | metadata = :1 |
64 | return errors.New("tables config is corrupt") | 145 | where projekat = :2 |
146 | and entity_type = :3`, | ||
147 | string(md), | ||
148 | activeProject, | ||
149 | entityType) | ||
150 | if err != nil { | ||
151 | return err | ||
65 | } | 152 | } |
66 | return nil | 153 | return nil |
67 | } | 154 | } |
68 | 155 | ||
69 | // DecodeJSON decodes JSON data from r to v. | 156 | // DecodeJSON decodes JSON data from r to v. |
70 | // Returns an error if it fails. | 157 | // Returns an error if it fails. |
71 | func DecodeJSON(r io.Reader, v interface{}) error { | 158 | func DecodeJSON(r io.Reader, v interface{}) error { |
72 | return json.NewDecoder(r).Decode(v) | 159 | return json.NewDecoder(r).Decode(v) |
73 | } | 160 | } |
74 | 161 | ||
75 | // NewPayload returs a payload sceleton for provided table. | 162 | // NewPayload returs a payload sceleton for entity described with etype. |
76 | func NewPayload(r *http.Request, table string) Payload { | 163 | func NewPayload(r *http.Request, etype string) Payload { |
77 | var pload Payload | 164 | pload := metadata[etype] |
78 | |||
79 | pload.Method = r.Method + " " + r.RequestURI | 165 | pload.Method = r.Method + " " + r.RequestURI |
80 | pload.Type = table | ||
81 | if table != "" { | ||
82 | pload.Params = make(map[string]string, 0) | ||
83 | pload.Lang = translations(table) | ||
84 | pload.Fields = fields(table) | ||
85 | pload.IdField = id(table) | ||
86 | pload.Correlations = correlations(table) | ||
87 | } | ||
88 | return pload | 166 | return pload |
89 | } | 167 | } |
90 | 168 | ||
91 | // translations returns a slice of translations for a payload/table of ptype type. | 169 | func initMetadata(project string) error { |
92 | func translations(ptype string) []Translation { | 170 | metadataDB.SetCfg(metadataDB.Cfg().SetClob(ora.S)) |
93 | var translations []Translation | 171 | stmt, err := metadataDB.Prep(`select |
94 | 172 | entity_type, | |
95 | for _, pload := range payloads { | 173 | metadata |
96 | if pload.Type == ptype { | 174 | from entities |
97 | for _, t := range pload.Lang { | 175 | where projekat = `+fmt.Sprintf("'%s'", project), |
98 | translations = append(translations, Translation{ | 176 | ora.S, |
99 | Language: t.Language, | 177 | ora.S) |
100 | FieldsLabels: t.FieldsLabels, | 178 | |
101 | }) | 179 | defer stmt.Close() |
102 | } | 180 | if err != nil { |
103 | } | 181 | return err |
104 | } | 182 | } |
105 | 183 | ||
106 | return translations | 184 | rset, err := stmt.Qry() |
107 | } | 185 | if err != nil { |
186 | return err | ||
187 | } | ||
108 | 188 | ||
109 | // fields returns a slice of fields for a payload/table of ptype type. | 189 | count := 0 |
110 | func fields(ptype string) []Field { | 190 | success := 0 |
111 | var fields []Field | 191 | metadata = make(map[string]Payload) |
192 | for rset.Next() { | ||
193 | name := rset.Row[0].(string) | ||
194 | load := []byte(rset.Row[1].(string)) | ||
112 | 195 | ||
113 | for _, pload := range payloads { | 196 | p := Payload{} |
114 | if pload.Type == ptype { | 197 | err := json.Unmarshal(load, &p) |
115 | for _, f := range pload.Fields { | 198 | if err != nil { |
116 | fields = append(fields, f) | 199 | fmt.Printf("couldn't init: '%s' metadata\n", name) |
117 | } | 200 | } else { |
201 | success++ | ||
202 | metadata[name] = p | ||
118 | } | 203 | } |
204 | count++ | ||
119 | } | 205 | } |
206 | fmt.Printf("webutility: successfully loaded %d/%d (%.1f%%) entities\n", | ||
207 | success, count, float32(success)/float32(count)*100.0) | ||
120 | 208 | ||
121 | return fields | 209 | return nil |
122 | } | 210 | } |
123 | 211 | ||
124 | // id returns the name of ID field of a payload/table of ptype type. | 212 | func hotload(n int) { |
125 | func id(ptype string) string { | 213 | entityScan := make(map[string]int64) |
126 | for _, pload := range payloads { | 214 | firstCheck := true |
127 | if pload.Type == ptype { | 215 | for { |
128 | return pload.IdField | 216 | time.Sleep(time.Duration(n) * time.Second) |
217 | stmt, err := metadataDB.Prep(`select | ||
218 | ora_rowscn, | ||
219 | entity_type | ||
220 | from entities where projekat = `+fmt.Sprintf("'%s'", activeProject), | ||
221 | ora.I64, | ||
222 | ora.S) | ||
223 | if err != nil { | ||
224 | fmt.Fprintf(os.Stderr, "hotload failed: %v\n", err) | ||
225 | time.Sleep(time.Duration(n) * time.Second) | ||
226 | continue | ||
129 | } | 227 | } |
130 | } | ||
131 | return "" | ||
132 | } | ||
133 | 228 | ||
134 | // correlations returns a slice of correlation fields for a payload/table of ptype type. | 229 | rset, err := stmt.Qry() |
135 | func correlations(ptype string) []CorrelationField { | 230 | if err != nil { |
136 | var corr []CorrelationField | 231 | fmt.Fprintf(os.Stderr, "hotload failed: %v\n", err) |
232 | time.Sleep(time.Duration(n) * time.Second) | ||
233 | continue | ||
234 | } | ||
137 | 235 | ||
138 | for _, pload := range payloads { | 236 | var toRefresh []string |
139 | if pload.Type == ptype { | 237 | for rset.Next() { |
140 | for _, c := range pload.Correlations { | 238 | scanID := rset.Row[0].(int64) |
141 | corr = append(corr, c) | 239 | entity := rset.Row[1].(string) |
240 | oldID, ok := entityScan[entity] | ||
241 | if !ok || oldID != scanID { | ||
242 | entityScan[entity] = scanID | ||
243 | toRefresh = append(toRefresh, entity) | ||
142 | } | 244 | } |
143 | } | 245 | } |
144 | } | 246 | stmt.Close() |
145 | 247 | ||
146 | return corr | 248 | if rset.Err() != nil { |
147 | } | 249 | fmt.Fprintf(os.Stderr, "hotload rset error: %v\n", rset.Err()) |
250 | time.Sleep(time.Duration(n) * time.Second) | ||
251 | continue | ||
252 | } | ||
148 | 253 | ||
149 | // fetchJSON returns a byte slice of JSON configuration file from TABLES_CONFIG table. | 254 | if len(toRefresh) > 0 && !firstCheck { |
150 | // Returns an error if it fails. | 255 | mu.Lock() |
151 | func fetchJSON(db *ora.Ses, project string) ([]byte, error) { | 256 | refreshMetadata(toRefresh) |
152 | db.SetCfg(db.Cfg().SetClob(ora.S)) | 257 | mu.Unlock() |
153 | stmt, err := db.Prep(`SELECT JSON_NCLOB FROM TABLES_CONFIG WHERE PROJEKAT = `+fmt.Sprintf("'%s'", project), ora.S) | 258 | } |
154 | defer stmt.Close() | 259 | if firstCheck { |
155 | if err != nil { | 260 | firstCheck = false |
156 | return nil, err | 261 | } |
157 | } | 262 | } |
263 | } | ||
158 | 264 | ||
159 | rset, err := stmt.Qry() | 265 | func refreshMetadata(entities []string) { |
160 | if err != nil { | 266 | for _, e := range entities { |
161 | return nil, err | 267 | //fmt.Printf("refreshing %s\n", e) |
162 | } | 268 | stmt, err := metadataDB.Prep(`select |
269 | metadata | ||
270 | from entities | ||
271 | where projekat = `+fmt.Sprintf("'%s'", activeProject)+ | ||
272 | ` and entity_type = `+fmt.Sprintf("'%s'", e), | ||
273 | ora.S) | ||
163 | 274 | ||
164 | var data string | 275 | if err != nil { |
165 | if rset.Next() { | 276 | fmt.Printf("webutility: refresh: prep: %v\n", err) |
166 | data = rset.Row[0].(string) | 277 | stmt.Close() |
167 | } | 278 | continue |
279 | } | ||
168 | 280 | ||
169 | //fmt.Println(data) | 281 | rset, err := stmt.Qry() |
170 | return []byte(data), nil | 282 | if err != nil { |
283 | fmt.Printf("webutility: refresh: query: %v\n", err) | ||
284 | stmt.Close() | ||
285 | continue | ||
286 | } | ||
287 | |||
288 | for rset.Next() { | ||
289 | load := []byte(rset.Row[0].(string)) |