-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgnow.go
585 lines (519 loc) · 12.3 KB
/
gnow.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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
// gnow is a simple nowdb client.
//
// A typical workflow is as follows:
//
// func myquery(srv string, port string, usr string, pwd string) {
// c, err := Connect(srv, port, usr, pwd)
// if err != nil {
// // error handling
// }
// defer c.Close()
// err = c.Use("mydb")
// if err != nil {
// // error handling
// }
// res, err = c.Execute("select count(*) from mytable")
// if err != nil {
// // error handling
// }
// cur, err := res.Open()
// if err != nil {
// // error handling
// }
// defer cur.Close()
// for row, cerr := cur.Fetch(); cerr != nil; row, cerr = cur.Fetch() {
// fmt.Printf("Count: %d\n", row.UInt(0))
// }
// }
package gnow
// #include <nowdb/nowclient.h>
// #include <stdlib.h>
import "C"
import(
"fmt"
"os"
"time"
"unsafe"
)
var global_is_initialised bool
func init() {
global_is_initialised = (C.nowdb_client_init() != 0)
}
// Leave shall be called immediately before program exit.
// It frees resources allocated by the C library on initialisation.
func Leave() {
if global_is_initialised {
C.nowdb_client_close()
}
}
const (
// success status code
OK = 0
_eof = 8
)
const (
status = 0x21
report = 0x22
row = 0x23
cursor = 0x24
)
// Return Type indicators
const (
// Invalid or unknown return type
InvalidT = -1
// Status return type (ok/not ok)
StatusT = 1
// Cursor return type
CursorT = 2
)
// Data type indicators
const (
// NULL
NOTHING = 0
// Text type
TEXT = 1
// Time type
DATE = 2
// Time type
TIME = 3
// Float type
FLOAT= 4
// Int type
INT = 5
// Uint type
UINT = 6
// Bool type
BOOL = 9
)
// EOF error
var EOF = eof()
// NULL error
var NULL = null()
// Generic Error Type
type ClientError struct {
what string
}
func newClientError(s string) (e ClientError) {
e.what = s
return
}
func (e ClientError) Error() string {
return e.what
}
func eof() ClientError{
return newClientError("end-of-file")
}
// Type error
type TypeError struct {
what string
}
func newTypeError(s string) (e TypeError) {
e.what = s
return
}
func (e TypeError) Error() string {
return e.what
}
func null() TypeError{
return newTypeError("NULL")
}
// Error type for server-side errors
type ServerError struct {
what string
}
func newServerError(s string) (e ServerError) {
e.what = s
return
}
func (e ServerError) Error() string {
return e.what
}
const npersec = 1000000000
// Now2Go converts a nowdb time value
// to a go time.Time object.
func Now2Go(n int64) time.Time {
s := n / npersec
ns := n - s * npersec
return time.Unix(s,ns).UTC()
}
// Go2Now converts a go time.Time object
// to a nowdb time value
func Go2Now(t time.Time) int64 {
return t.UnixNano()
}
// Connection type
type Connection struct {
cc C.nowdb_con_t
}
// Connect creates a connection to the database server.
// It expects the server name and port and
// a user name and password.
// It returns a new Connection object or
// error on failure.
func Connect(server string, port string, usr string, pwd string) (*Connection, error) {
var cc C.nowdb_con_t
if !global_is_initialised {
return nil, newClientError("Client is not initialised")
}
rc := C.nowdb_connect(&cc, C.CString(server), C.CString(port), nil, nil, 0)
if rc != OK {
fmt.Fprintf(os.Stderr, "cannot connect: %d\n", rc)
m := fmt.Sprintf("%d", rc) // explain!
return nil, newServerError(m)
}
c := new(Connection)
c.cc = cc
return c, nil
}
// Close closes the connection.
func (c *Connection) Close() error {
if c.cc == nil {
return nil
}
rc := C.nowdb_connection_close(c.cc)
if rc != OK {
C.nowdb_connection_destroy(c.cc)
c.cc = nil
fmt.Fprintf(os.Stderr, "cannot connect: %d\n", rc)
m := fmt.Sprintf("%d", rc) // explain!
return newServerError(m)
}
c.cc = nil
return nil
}
// Result is a polymorphic type to
// abstract data results.
// It is either a Status (ok or not ok)
// or a Cursor.
type Result struct {
cs C.nowdb_result_t
t int
}
// Tell type returns the result type indicator
// of this result object.
func (r *Result) TellType() int {
t := C.nowdb_result_type(r.cs)
switch(t) {
case status: fallthrough
case report: return StatusT
case row: fallthrough
case cursor: return CursorT
default: return -1
}
}
// OK returns true if the status is ok
// and false otherwise.
func (r *Result) OK() bool {
return (C.nowdb_result_errcode(r.cs) == OK)
}
// Error returns an error reflecting the status
// of the result.
func (r *Result) Error() string {
if C.nowdb_result_details(r.cs) == nil {
return fmt.Sprintf("%d", int(C.nowdb_result_errcode(r.cs)))
}
return fmt.Sprintf("%d: %s", int(C.nowdb_result_errcode(r.cs)),
C.GoString(C.nowdb_result_details(r.cs)))
}
// Errcode returns the numerical error code related to this result
func (r *Result) Errcode() int {
return int(C.nowdb_result_errcode(r.cs))
}
// transform a result into a server error
func r2err(r C.nowdb_result_t) ServerError {
return newServerError(fmt.Sprintf("%d: %s",
int(C.nowdb_result_errcode(r)),
C.GoString(C.nowdb_result_details(r))))
}
// Execute sends a SQL statement to the database.
// It retuns a result or an error.
// That means: Result is always ok;
// if there was an error result will be nil.
func (c *Connection) Execute(stmt string) (*Result, error) {
var cr C.nowdb_result_t
rc := C.nowdb_exec_statement(c.cc, C.CString(stmt), &cr)
if rc != OK || cr == nil {
m := fmt.Sprintf("%d", rc) // explain!
return nil, newServerError(m)
}
r := new(Result)
r.cs = cr
r.t = int(C.nowdb_result_type(cr))
if int(C.nowdb_result_status(cr)) != OK {
err := r2err(cr)
r.Destroy()
return nil, err
}
return r, nil
}
// Use defines the database to be used
// in all subsequent statements.
// Use must be called before any other statement.
func (c *Connection) Use(db string) error {
stmt := fmt.Sprintf("use %s", db)
r, err := c.Execute(stmt)
if err != nil {
return err
}
if r != nil {
r.Destroy()
}
return nil
}
// Destroy releases all resources
// allocated by the C library for
// this result. I must be called
// to avoid memory leaks in the
// C library.
// If the result is a cursor
// and the cursor was opened
// and closed, it is not necessary
// to call Destroy on the result
// (but it does not harm to call
// Destroy addionally).
func (r *Result) Destroy() {
if r.cs == nil {
return
}
C.nowdb_result_destroy(r.cs)
r.cs = nil
}
// Cursor is an iterator over
// a resultset. It is created
// from an existing result.
// It inherits all resources
// from that result.
// Closing a cursor releases
// all resources (server- and
// client-side) assigned to it.
type Cursor struct {
cc C.nowdb_cursor_t
row C.nowdb_row_t
first bool
}
func cur2res(c C.nowdb_cursor_t) C.nowdb_result_t {
return C.nowdb_result_t(unsafe.Pointer(c))
}
// Open creates a cursor from a result.
// The cursor inherits all
// resources from the result.
func (r *Result) Open() (*Cursor, error) {
if r.t != cursor && r.t != row {
return nil, newClientError("not a cursor")
}
c := new(Cursor)
c.first = true
if r.t == cursor {
rc := C.nowdb_cursor_open(r.cs, &c.cc)
if rc != OK {
return nil, newClientError(fmt.Sprintf("%d", rc))
}
c.row = C.nowdb_cursor_row(c.cc)
} else {
c.row = C.nowdb_row_t(unsafe.Pointer(r.cs))
}
r.cs = nil
return c, nil
}
// One results one row from the result and discards all other rows.
// Conceptually, it opens a cursor, fetches once, closes the cursor
// and returns the row. The main use case for this method
// is results that return only one row (e.g. select count(*)).
func (r *Result) One() (*Row, error) {
cur, err := r.Open()
if err != nil {
return nil, err
}
defer cur.Close()
row, err := cur.Fetch()
if err != nil {
return nil, err
}
return row, nil
}
// Close releases all resources assigned to the cursor
// and the result form which it was opened.
// A cursor shall be closed to avoid memory leaks
// in the C library and resources in the server
// (which, otherwise, would be pending until the end
// of the session).
// When the cursor was closed, it is not necessary
// to destroy the corresponding result
// (but there is also no harm in destroying
// the result additionally).
func (c *Cursor) Close() {
if c.row != nil {
if c.cc == nil {
C.nowdb_result_destroy(C.nowdb_result_t(unsafe.Pointer(c.row)))
}
c.row = nil
}
if c.cc != nil {
rc := C.nowdb_cursor_close(c.cc)
if rc != OK {
C.nowdb_result_destroy(C.nowdb_result_t(unsafe.Pointer(c.cc)))
}
c.cc = nil
}
}
// The Row type represents one row in a resultset.
type Row struct {
cr C.nowdb_row_t
}
func makeRow(c *Cursor) (*Row, error) {
r := new(Row)
r.cr = c.row
return r, nil
}
// Fetch returns one row of the result set or error
// (but never both).
func (c *Cursor) Fetch() (*Row, error) {
if c.row != nil {
if c.first {
c.first = false
return makeRow(c)
}
rc := C.nowdb_row_next(c.row)
if rc != OK {
if c.cc == nil {
C.nowdb_result_destroy(C.nowdb_result_t(unsafe.Pointer(c.row)))
}
c.row = nil
} else {
return makeRow(c)
}
}
if c.cc != nil {
rc := C.nowdb_cursor_fetch(c.cc)
if rc == OK {
rc = C.nowdb_result_errcode(C.nowdb_result_t(unsafe.Pointer(c.cc)))
}
if rc != OK {
if rc == _eof {
return nil, EOF
}
return nil, r2err(cur2res(c.cc))
}
c.row = C.nowdb_cursor_row(c.cc)
return makeRow(c)
}
return nil, EOF
}
// Count returns the number of fields in the row.
func (r *Row) Count() int {
if r.cr == nil {
return 0
}
return int(C.nowdb_row_count(r.cr))
}
// Field returns the type indicator and
// the content of the field with index idx
// starting to count from 0.
// If idx is out of range, NOTHING is returned.
func (r *Row) Field(idx int) (int, interface{}) {
var t C.int
if r.cr == nil {
return NOTHING, nil
}
v := C.nowdb_row_field(r.cr, C.int(idx), &t)
switch(t) {
case NOTHING: return NOTHING, nil
case TEXT: return int(t), C.GoString((*C.char)(v))
case DATE: fallthrough
case TIME: fallthrough
case INT: return int(t), *(*int64)(v)
case UINT: return int(t), *(*uint64)(v)
case FLOAT: return int(t), *(*float64)(v)
case BOOL:
x := *(*C.char)(v)
if x == 0 {
return int(t), false
} else {
return int(t), true
}
default: return NOTHING, nil
}
}
// String returns the field with index 'idx'
// as string. If that field is not a string,
// a type error is returned.
// If idx is out of range, NOTHING is returned.
func (r *Row) String(idx int) (string, error) {
t, v := r.Field(idx)
if t == NOTHING {
return "", NULL
}
if t != TEXT {
return "", newTypeError("not a string")
}
return v.(string), nil
}
func (r *Row) intValue(idx int, isTime bool) (int64, error) {
t, v := r.Field(idx)
if t == NOTHING {
return 0, NULL
}
if isTime && t != TIME && t != DATE && t != INT {
return 0, newTypeError("not a time value")
}
if !isTime && t != INT {
return 0, newTypeError("not an int value")
}
return v.(int64), nil
}
// Time returns the field with index 'idx'
// as time value. If that field is not a time value,
// a type error is returned.
// If idx is out of range, NOTHING is returned.
func (r *Row) Time(idx int) (int64, error) {
return r.intValue(idx, true)
}
// Int returns the field with index 'idx'
// as int value. If that field is not an int,
// a type error is returned.
// If idx is out of range, NOTHING is returned.
func (r *Row) Int(idx int) (int64, error) {
return r.intValue(idx, false)
}
// UInt returns the field with index 'idx'
// as uint value. If that field is not a uint,
// a type error is returned.
// If idx is out of range, NOTHING is returned.
func (r *Row) UInt(idx int) (uint64, error) {
t, v := r.Field(idx)
if t == NOTHING {
return 0, NULL
}
if t != UINT {
return 0, newTypeError("not an uint value")
}
return v.(uint64), nil
}
// Float returns the field with index 'idx'
// as float value. If that field is not a float,
// a type error is returned.
// If idx is out of range, NOTHING is returned.
func (r *Row) Float(idx int) (float64, error) {
t, v := r.Field(idx)
if t == NOTHING {
return 0, NULL
}
if t != FLOAT {
return 0, newTypeError("not a float value")
}
return v.(float64), nil
}
// Bool returns the field with index 'idx'
// as bool value. If that field is not a bool,
// a type error is returned.
// If idx is out of range, NOTHING is returned.
func (r *Row) Bool(idx int) (bool, error) {
t, v := r.Field(idx)
if t == NOTHING {
return false, NULL
}
if t != BOOL {
return false, newTypeError("not a bool value")
}
return v.(bool), nil
}