-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdatabase.go
515 lines (436 loc) · 18.9 KB
/
database.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
package cblcgo
/*
#cgo LDFLAGS: -L. -lCouchbaseLiteC
#include <stdlib.h>
#include <stdio.h>
#include "include/CouchbaseLite.h"
void databaseListenerBridge(void *, CBLDatabase*, unsigned, char **);
void notificationBridge(void *);
void gatewayDatabaseChangeGoCallback(void *context, const CBLDatabase* db _cbl_nonnull, unsigned numDocs, const char **docIDs _cbl_nonnull) {
databaseListenerBridge(context, (CBLDatabase*)db, numDocs, (char**)docIDs);
}
void notificationReadyCallback(void *context, CBLDatabase* db _cbl_nonnull) {
notificationBridge(context);
}
char * getDocIDFromArray(char **docIds, unsigned index) {
return docIds[index];
}
void Set_Null(void * ptr) {
ptr = NULL;
}
*/
import "C"
import "unsafe"
import "context"
import "fmt"
type EncryptionAlgorithm uint32
type DatabaseFlags uint32
const (
EncryptionNone EncryptionAlgorithm = iota
EncryptionAES256
)
/** Flags for how to open a database. */
const (
Database_Create DatabaseFlags = 1 << iota ///< Create the file if it doesn't exist
Database_ReadOnly ///< Open file read-only
Database_NoUpgrade ///< Disable upgrading an older-version database
)
type Database struct {
db *C.CBLDatabase
config *C.CBLDatabaseConfiguration
name string
}
type ListenerToken struct {
key string
token *C.CBLListenerToken
callbackType string
}
var dbCallbacks map[string]DatabaseChangeListener = make(map[string]DatabaseChangeListener)
var docCallbacks map[string]DocumentChangeListener = make(map[string]DocumentChangeListener)
var queryCallbacks map[string]QueryChangeListener = make(map[string]QueryChangeListener)
var pushFilterCallbacks map[string]ReplicationFilter = make(map[string]ReplicationFilter)
var pullFilterCallbacks map[string]ReplicationFilter = make(map[string]ReplicationFilter)
var replicatedDocCallbacks map[string]ReplicatedDocumentListener = make(map[string]ReplicatedDocumentListener)
var replicatorCallbacks map[string]ReplicatorChangeListener = make(map[string]ReplicatorChangeListener)
var notificationCallback NotificationsReadyCallback
var conflictResolverCallbacks map[string]ConflictResolver = make(map[string]ConflictResolver)
var uuid string = "UUID"
var callback string = "CALLBACK"
var pushCallback string = "PUSHCALLBACK"
var pullCallback string = "PULLCALLBACK"
var conflictResolver string = "CONFLICTRESOLVER"
/** Encryption key specified in a \ref CBLDatabaseConfiguration. */
type EncryptionKey struct {
Algorithm EncryptionAlgorithm ///< Encryption algorithm
Bytes []byte ///< Raw key data
}
/** Database configuration options. */
type DatabaseConfiguration struct {
Directory string ///< The parent directory of the database
Flags DatabaseFlags ///< Options for opening the database
EncryptionKey EncryptionKey; ///< The database's encryption key (if any)
}
/** \name Database file operations
@{
These functions operate on database files without opening them.
*/
/** Returns true if a database with the given name exists in the given directory.
@param name The database name (without the ".cblite2" extension.)
@param inDirectory The directory containing the database. If "", `name` must be an
absolute or relative path to the database. */
//bool CBL_DatabaseExists(const char* _cbl_nonnull name, const char *inDirectory) CBLAPI;
func DatabaseExists(name, inDirectory string) bool {
c_name := C.CString(name)
var c_inDirectory *C.char
if len(inDirectory) > 0 {
c_inDirectory = C.CString(inDirectory)
defer C.free(unsafe.Pointer(c_inDirectory))
} else {
C.Set_Null(unsafe.Pointer(c_inDirectory))
}
result := C.CBL_DatabaseExists(c_name, c_inDirectory)
C.free(unsafe.Pointer(c_name))
return bool(result)
}
/** Copies a database file to a new location, and assigns it a new internal UUID to distinguish
it from the original database when replicating.
@param fromPath The full filesystem path to the original database (including extension).
@param toName The new database name (without the ".cblite2" extension.)
@param config The database configuration (directory and encryption option.) */
// bool CBL_CopyDatabase(const char* _cbl_nonnull fromPath,
// const char* _cbl_nonnull toName,
// const CBLDatabaseConfiguration* config,
// CBLError*) CBLAPI;
func CopyDatabase(fromPath, toName string, config *DatabaseConfiguration) bool {
c_fromPath := C.CString(fromPath)
c_toName := C.CString(toName)
c_dir := C.CString(config.Directory)
// need to check length and return false if diff or less than 32
var key_data [32]C.uint8_t
for i:=0; i < len(config.EncryptionKey.Bytes); i++ {
key_data[i] = C.uint8_t(config.EncryptionKey.Bytes[i])
}
encryption_key := C.CBLEncryptionKey{C.uint32_t(config.EncryptionKey.Algorithm), key_data}
c_config := (*C.CBLDatabaseConfiguration)(C.malloc(C.sizeof_CBLDatabaseConfiguration))
c_config.directory = c_dir
c_config.flags = C.uint32_t(config.Flags)
c_config.encryptionKey = encryption_key
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
result := C.CBL_CopyDatabase(c_fromPath, c_toName, c_config, err)
C.free(unsafe.Pointer(c_fromPath))
C.free(unsafe.Pointer(c_toName))
C.free(unsafe.Pointer(c_dir))
C.free(unsafe.Pointer(err))
return bool(result)
}
/** Deletes a database file. If the database file is open, an error is returned.
@param name The database name (without the ".cblite2" extension.)
@param inDirectory The directory containing the database. If NULL, `name` must be an
absolute or relative path to the database.
@param outError On return, will be set to the error that occurred, or a 0 code if no error.
@return True if the database was deleted, false if it doesn't exist or deletion failed.
(You can tell the last two cases apart by looking at \ref outError.)*/
// bool CBL_DeleteDatabase(const char _cbl_nonnull *name,
// const char *inDirectory,
// CBLError *outError) CBLAPI;
func DeleteDatabase(name, inDirectory string) bool {
c_name := C.CString(name)
c_inDirectory := C.CString(inDirectory)
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
result := C.CBL_DeleteDatabase(c_name, c_inDirectory, err)
C.free(unsafe.Pointer(c_name))
C.free(unsafe.Pointer(c_inDirectory))
C.free(unsafe.Pointer(err))
return bool(result)
}
/** \name Database lifecycle
@{
Opening, closing, and managing open databases.
*/
/** Opens a database, or creates it if it doesn't exist yet, returning a new \ref CBLDatabase
instance.
It's OK to open the same database file multiple times. Each \ref CBLDatabase instance is
independent of the others (and must be separately closed and released.)
@param name The database name (without the ".cblite2" extension.)
@param config The database configuration (directory and encryption option.)
@param error On failure, the error will be written here.
@return The new database object, or NULL on failure. */
// _cbl_warn_unused
// CBLDatabase* CBLDatabase_Open(const char *name _cbl_nonnull,
// const CBLDatabaseConfiguration* config,
// CBLError* error) CBLAPI;
func Open(name string, config *DatabaseConfiguration) (*Database, error) {
c_name := C.CString(name)
defer C.free(unsafe.Pointer(c_name))
// Convert to C array
var key_data [32]C.uint8_t
for i:=0; i < len(config.EncryptionKey.Bytes); i++ {
key_data[i] = C.uint8_t(config.EncryptionKey.Bytes[i])
}
// Create C key
c_key := C.CBLEncryptionKey{C.uint32_t(config.EncryptionKey.Algorithm), key_data}
// Create C config
c_config := (*C.CBLDatabaseConfiguration)(C.malloc(C.sizeof_CBLDatabaseConfiguration))
c_dir := C.CString(config.Directory)
defer C.free(unsafe.Pointer(c_dir))
c_config.directory = c_dir
c_config.flags = C.uint32_t(config.Flags)
c_config.encryptionKey = c_key
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
defer C.free(unsafe.Pointer(err))
// Open Database
c_db := C.CBLDatabase_Open(c_name, c_config, err)
if (*err).code == 0 {
database := Database{}
database.db = c_db
database.config = c_config
database.name = name
return &database, nil
}
ErrCBLInternalError = fmt.Errorf("CBL: Problem Opening Database. Domain: %d Code: %d", (*err).domain, (*err).code)
return nil, ErrCBLInternalError
}
/** Closes an open database. */
// bool CBLDatabase_Close(CBLDatabase*, CBLError*) CBLAPI;
func (db *Database) Close() bool {
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
defer C.free(unsafe.Pointer(err))
C.free(unsafe.Pointer(db.config))
result := C.CBLDatabase_Close(db.db, err)
return bool(result)
}
/** Closes and deletes a database. If there are any other connections to the database,
an error is returned. */
// bool CBLDatabase_Delete(CBLDatabase* _cbl_nonnull, CBLError*) CBLAPI;
func (db *Database) Delete() bool {
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
defer C.free(unsafe.Pointer(err))
result := C.CBLDatabase_Delete(db.db, err)
if (*err).code == 0 {
return bool(result)
}
return false
}
/** Compacts a database file. */
// bool CBLDatabase_Compact(CBLDatabase* _cbl_nonnull, CBLError*) CBLAPI;
func (db *Database) Compact() bool {
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
defer C.free(unsafe.Pointer(err))
result := C.CBLDatabase_Compact(db.db, err)
if (*err).code == 0 {
return bool(result)
}
return false
}
/** Begins a batch operation, similar to a transaction. You **must** later call \ref
CBLDatabase_EndBatch to end (commit) the batch.
@note Multiple writes are much faster when grouped inside a single batch.
@note Changes will not be visible to other CBLDatabase instances on the same database until
the batch operation ends.
@note Batch operations can nest. Changes are not committed until the outer batch ends. */
// bool CBLDatabase_BeginBatch(CBLDatabase* _cbl_nonnull, CBLError*) CBLAPI;
func (db *Database) BeginBatch() bool {
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
defer C.free(unsafe.Pointer(err))
result := C.CBLDatabase_BeginBatch(db.db, err)
if (*err).code == 0 {
return bool(result)
}
return false
}
/** Ends a batch operation. This **must** be called after \ref CBLDatabase_BeginBatch. */
// bool CBLDatabase_EndBatch(CBLDatabase* _cbl_nonnull, CBLError*) CBLAPI;
func (db *Database) EndBatch() bool {
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
defer C.free(unsafe.Pointer(err))
result := C.CBLDatabase_EndBatch(db.db, err)
if (*err).code == 0 {
return bool(result)
}
return false
}
/** Returns the nearest future time at which a document in this database will expire,
or 0 if no documents will expire. */
// CBLTimestamp CBLDatabase_NextDocExpiration(CBLDatabase* _cbl_nonnull) CBLAPI;
// int64_t
func (db *Database) NextDocExpiration() int64 {
timestamp := C.CBLDatabase_NextDocExpiration(db.db)
return int64(timestamp)
}
/** Purges all documents whose expiration time has passed.
@param db The database to purge
@param error On failure, the error will be written here.
@return The number of documents purged, or -1 on error. */
// int64_t CBLDatabase_PurgeExpiredDocuments(CBLDatabase* db _cbl_nonnull,
// CBLError* error) CBLAPI;
func (db *Database) PurgeExpiredDocuments() int64 {
err := (*C.CBLError)(C.malloc(C.sizeof_CBLError))
defer C.free(unsafe.Pointer(err))
result := C.CBLDatabase_PurgeExpiredDocuments(db.db, err)
if (*err).code == 0 {
return int64(result)
}
return -1
}
/** @} */
/** \name Database accessors
@{
Getting information about a database.
*/
/** Returns the database's name. */
// const char* CBLDatabase_Name(const CBLDatabase* _cbl_nonnull) CBLAPI _cbl_returns_nonnull;
func (db *Database) DatabaseName() string {
c_name := C.CBLDatabase_Name(db.db)
name := C.GoString(c_name)
return name
}
/** Returns the database's full filesystem path. */
// const char* CBLDatabase_Path(const CBLDatabase* _cbl_nonnull) CBLAPI _cbl_returns_nonnull;
func (db *Database) Path() string {
c_path := C.CBLDatabase_Path(db.db)
path := C.GoString(c_path)
return path
}
/** Returns the number of documents in the database. */
// uint64_t CBLDatabase_Count(const CBLDatabase* _cbl_nonnull) CBLAPI;
func (db *Database) Count() uint64 {
c_count := C.CBLDatabase_Count(db.db)
return uint64(c_count)
}
/** Returns the database's configuration, as given when it was opened.
@note The encryption key is not filled in, for security reasons. */
// const CBLDatabaseConfiguration CBLDatabase_Config(const CBLDatabase* _cbl_nonnull) CBLAPI;
func (db *Database) DatabaseConfig() *DatabaseConfiguration {
c_config := C.CBLDatabase_Config(db.db)
config := DatabaseConfiguration{}
key := EncryptionKey{}
dir := C.GoString(c_config.directory)
flags := DatabaseFlags(c_config.flags)
key.Algorithm = EncryptionAlgorithm(c_config.encryptionKey.algorithm)
key.Bytes = make([]byte, 0)
// setup go config
config.Directory = dir
config.EncryptionKey = key
config.Flags = flags
return &config
}
/** \name Database listeners
@{
A database change listener lets you detect changes made to all documents in a database.
(If you only want to observe specific documents, use a \ref CBLDocumentChangeListener instead.)
@note If there are multiple \ref CBLDatabase instances on the same database file, each one's
listeners will be notified of changes made by other database instances.
@warning Changes made to the database file by other processes will _not_ be notified. */
/** A database change listener callback, invoked after one or more documents are changed on disk.
@warning By default, this listener may be called on arbitrary threads. If your code isn't
prepared for that, you may want to use \ref CBLDatabase_BufferNotifications
so that listeners will be called in a safe context.
@param context An arbitrary value given when the callback was registered.
@param db The database that changed.
@param numDocs The number of documents that changed (size of the `docIDs` array)
@param docIDs The IDs of the documents that changed, as a C array of `numDocs` C strings. */
// typedef void (*CBLDatabaseChangeListener)(void *context,
// const CBLDatabase* db _cbl_nonnull,
// unsigned numDocs,
// const char **docIDs _cbl_nonnull);
type DatabaseChangeListener func(ctx context.Context, db *Database, docIDs []string)
/** Registers a database change listener callback. It will be called after one or more
documents are changed on disk.
@param db The database to observe.
@param listener The callback to be invoked.
@param context An opaque value that will be passed to the callback.
@return A token to be passed to \ref CBLListener_Remove when it's time to remove the
listener.*/
// _cbl_warn_unused
// CBLListenerToken* CBLDatabase_AddChangeListener(const CBLDatabase* db _cbl_nonnull,
// CBLDatabaseChangeListener listener _cbl_nonnull,
// void *context) CBLAPI;
func (db *Database) AddDatabaseChangeListener(listener DatabaseChangeListener, ctx context.Context, ctxKeys []string) (*ListenerToken, error) {
if v := ctx.Value(uuid); v != nil {
key, ok := v.(string)
if ok {
dbCallbacks[key] = listener
mutableDictContext := storeContextInMutableDict(ctx, ctxKeys)
token := C.CBLDatabase_AddChangeListener(db.db, (C.CBLDatabaseChangeListener)(C.gatewayDatabaseChangeGoCallback),
unsafe.Pointer(mutableDictContext))
listener_token := ListenerToken{key,token,"DatabaseChangeListener"}
return &listener_token, nil
}
}
ErrCBLInternalError = fmt.Errorf("CBL: No UUID present in context.")
return nil, ErrCBLInternalError
}
/** @} */
/** @} */ // end of outer \defgroup
/** \defgroup listeners Listeners
@{ */
/** \name Scheduling notifications
@{
Applications may want control over when Couchbase Lite notifications (listener callbacks)
happen. They may want them called on a specific thread, or at certain times during an event
loop. This behavior may vary by database, if for instance each database is associated with a
separate thread.
The API calls here enable this. When notifications are "buffered" for a database, calls to
listeners will be deferred until the application explicitly allows them. Instead, a single
callback will be issued when the first notification becomes available; this gives the app a
chance to schedule a time when the notifications should be sent and callbacks called.
*/
/** Callback indicating that the database (or an object belonging to it) is ready to call one
or more listeners. You should call \ref CBLDatabase_SendNotifications at your earliest
convenience, in the context (thread, dispatch queue, etc.) you want them to run.
@note This callback is called _only once_ until the next time \ref CBLDatabase_SendNotifications
is called. If you don't respond by (sooner or later) calling that function,
you will not be informed that any listeners are ready.
@warning This can be called from arbitrary threads. It should do as little work as
possible, just scheduling a future call to \ref CBLDatabase_SendNotifications. */
// typedef void (*CBLNotificationsReadyCallback)(void *context,
// CBLDatabase* db _cbl_nonnull);
type NotificationsReadyCallback func (ctx context.Context, db *Database)
/** Switches the database to buffered-notification mode. Notifications for objects belonging
to this database (documents, queries, replicators, and of course the database) will not be
called immediately; your \ref CBLNotificationsReadyCallback will be called instead.
@param db The database whose notifications are to be buffered.
@param callback The function to be called when a notification is available.
@param context An arbitrary value that will be passed to the callback. */
// void CBLDatabase_BufferNotifications(CBLDatabase *db _cbl_nonnull,
// CBLNotificationsReadyCallback callback _cbl_nonnull,
// void *context) CBLAPI;
func (db *Database) DatabaseBufferNotifications(callback NotificationsReadyCallback, ctx context.Context, ctxKeys []string) {
notificationCallback = callback
mutableDictContext := storeContextInMutableDict(ctx, ctxKeys)
C.CBLDatabase_BufferNotifications(db.db, (C.CBLNotificationsReadyCallback)(C.notificationReadyCallback),
unsafe.Pointer(mutableDictContext))
}
/** Immediately issues all pending notifications for this database, by calling their listener
callbacks. */
// void CBLDatabase_SendNotifications(CBLDatabase *db _cbl_nonnull) CBLAPI;
func (db *Database) SendNotifications() {
C.CBLDatabase_SendNotifications(db.db)
}
/*
Removes a listener callback, given the token that was returned when it was added.
*/
func (db *Database) RemoveListener(token *ListenerToken) {
switch token.callbackType {
case "DatabaseChangeListener":
delete(dbCallbacks, token.key)
break;
case "DocumentChangeListener":
delete(docCallbacks,token.key)
break;
case "QueryChangeListener":
delete(queryCallbacks, token.key)
break;
case "ReplicatedDocumentListener":
delete(replicatedDocCallbacks, token.key)
break;
case "ReplicatorChangeListener":
delete(replicatorCallbacks, token.key)
break;
}
C.CBLListener_Remove(token.token)
token.callbackType = ""
token.key = ""
}
/** @} */
/** @} */ // end of outer \defgroup