;; -*- mode: Scheme; -*- ;; ;; This file is part of BerkeleyDB for CHICKEN ;; Copyright (c) 2011-2013 by Thomas Chust. All rights reserved. ;; ;; Permission is hereby granted, free of charge, to any person ;; obtaining a copy of this software and associated documentation ;; files (the Software), to deal in the Software without restriction, ;; including without limitation the rights to use, copy, modify, ;; merge, publish, distribute, sublicense, and/or sell copies of the ;; Software, and to permit persons to whom the Software is furnished ;; to do so, subject to the following conditions: ;; ;; The above copyright notice and this permission notice shall be ;; included in all copies or substantial portions of the Software. ;; ;; THE SOFTWARE IS PROVIDED ASIS, WITHOUT WARRANTY OF ANY KIND, ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS ;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ;; SOFTWARE. (require-library srfi-1 srfi-4 srfi-18 srfi-69 lolevel data-structures ports extras) (module berkeley-db (database-environment? current-database-environment open-database-environment close-database-environment with-transaction copy-database rename-database delete-database database? open-database close-database database-type database-serializer database-deserializer database-associate database-ref database-set! database-exists? database-delete! database-fold database-walk) (import scheme chicken foreign srfi-1 srfi-4 srfi-18 srfi-69 lolevel data-structures ports extras) (foreign-declare "#include " "#include ") (define-syntax define-foreign-tagged-type (syntax-rules () ((define-foreign-tagged-type (id nonnull-id target-type) predicate tag) (begin (define tag (gensym 'id)) (define predicate (cut tagged-pointer? <> tag)) (define-foreign-type id (c-pointer target-type) (lambda (v) (if v (ensure predicate v) v)) (lambda (v) (and v (tag-pointer v tag)))) (define-foreign-type nonnull-id (nonnull-c-pointer target-type) (lambda (v) (ensure predicate v) v) (lambda (v) (tag-pointer v tag))))))) (define-syntax define-foreign-enum-argconvert (syntax-rules () ((define-foreign-enum-argconvert (id base-type) (key value) ...) (define (id v #!optional (seed 0)) (case v ((key) (fxior (foreign-value value base-type) seed)) ... (else #f)))))) (define-syntax define-foreign-enum-retconvert (syntax-rules () ((define-foreign-enum-retconvert (id base-type) (key value) ...) (define (id v) (select v (((foreign-value value base-type)) 'key) ... (else #f)))))) (define-syntax define-foreign-enum-type (syntax-rules () ((define-foreign-enum-type (id base-type) argconvert retconvert (key value) ...) (begin (define-foreign-enum-argconvert (argconvert base-type) (key value) ...) (define-foreign-enum-retconvert (retconvert base-type) (key value) ...) (define-foreign-type id base-type (lambda (v) (or (argconvert v) (abort (make-composite-condition (make-property-condition 'exn 'location 'id 'message "unknown enumeration value" 'arguments (list v)) (make-property-condition 'type))))) retconvert))))) (define-syntax let-keys+flags (syntax-rules () ((let-keys+flags loc args-expr ((key key-var key-default) ...) ((flag-argconvert flags-var) ...) expr ...) (let ((key-var key-default) ... (flags-var 0) ...) (let next ((args args-expr) (pos 0)) (when (pair? args) (let ((v (car args))) (cond ((flag-argconvert v flags-var) => (lambda (v) (set! flags-var v) (next (cdr args) pos))) ... (else (let-values (((k v next-args next-pos) (if (keyword? v) (values v (if (pair? (cdr args)) (cadr args) (error 'loc "illegal arguments" args)) (cddr args) pos) (values pos v (cdr args) (fx+ pos 1))))) (case k ((key) (set! key-var v) (next next-args next-pos)) ... (else (error 'loc "illegal arguments" args))))))))) expr ...)))) (define (check-error loc s) (unless (zero? s) (abort (make-composite-condition (make-property-condition 'exn 'location loc 'message ((foreign-lambda c-string "db_strerror" int) s)) (make-property-condition 'db 'code s))))) (define access-error? (letrec ((condition-code (condition-property-accessor 'db 'code #f)) (not-found (foreign-value "DB_NOTFOUND" int)) (access-error? (lambda (exn) (eqv? (condition-code exn) not-found)))) access-error?)) (define (blob/string-size v) (cond ((blob? v) (blob-size v)) ((string? v) (string-length v)))) ;; Database environments (define-foreign-tagged-type (database-environment nonnull-database-environment "DB_ENV") database-environment? tag:database-environment) (define current-database-environment (make-parameter #f)) (define-foreign-enum-argconvert (database-environment-open-flags int) (#:locking "DB_INIT_LOCK") (#:logging "DB_INIT_LOG") (#:memory-pool "DB_INIT_MPOOL") (#:replication "DB_INIT_REP") (#:transactions "DB_INIT_TXN") (#:recover "DB_RECOVER") (#:recover/fatal "DB_RECOVER_FATAL") (#:use-environment "DB_USE_ENVIRON") (#:use-environment/root "DB_USE_ENVIRON_ROOT") (#:create "DB_CREATE") (#:lockdown "DB_LOCKDOWN") (#:failure-check "DB_FAILCHK") (#:private "DB_PRIVATE")) (define-foreign-enum-argconvert (database-environment-open-extra-flags int) (#:auto-commit "DB_AUTO_COMMIT") (#:direct-i/o "DB_DIRECT_DB") (#:synchronous-i/o "DB_DSYNC_DB") (#:multiversion "DB_MULTIVERSION") (#:no-mmap "DB_NOMMAP") (#:overwrite "DB_OVERWRITE") (#:initialize-region "DB_REGION_INIT") (#:timeout->not-granted "DB_TIME_NOTGRANTED") (#:asynchronous "DB_TXN_NOSYNC") (#:no-wait "DB_TXN_NOWAIT") (#:snapshot "DB_TXN_SNAPSHOT") (#:asynchronous-write "DB_TXN_WRITE_NOSYNC")) (define-foreign-enum-argconvert (database-encryption-flags int) (#:encrypt/aes "DB_ENCRYPT_AES")) (define (open-database-environment home . args) (let-keys+flags open-database-environment args ((#:mode mode 0) (#:password password #f)) ((database-environment-open-flags flags) (database-environment-open-extra-flags extra-flags) (database-encryption-flags encryption-flags)) (let-location ((env database-environment #f)) (check-error 'open-database-environment ((foreign-lambda int "db_env_create" (nonnull-c-pointer database-environment) int) (location env) 0)) (condition-case (begin (check-error 'open-database-environment ((foreign-lambda* int ((nonnull-database-environment env)) "C_return(env->set_alloc(env, &malloc, &realloc, &free));") env)) (check-error 'open-database-environment ((foreign-lambda* int ((nonnull-database-environment env) (int flags)) "C_return(env->set_flags(env, flags, 1));") env extra-flags)) (unless (zero? encryption-flags) (check-error 'open-database-environment ((foreign-lambda* int ((nonnull-database-environment env) (nonnull-c-string password) (int flags)) "C_return(env->set_encrypt(env, password, flags));") env password encryption-flags))) (check-error 'open-database-environment ((foreign-lambda* int ((nonnull-database-environment env) (nonnull-c-string home) (int flags) (int mode)) "C_return(env->open(env, home, flags, mode));") env home flags mode))) (exn (exn db) (close-database-environment env) (abort exn))) (current-database-environment env)))) (cond-expand (enable-forcesync (define-foreign-enum-argconvert (database-environment-close-flags int) (#:synchronous "DB_FORCESYNC"))) (else (define-foreign-enum-argconvert (database-environment-close-flags int)))) (define (close-database-environment . args) (let-keys+flags close-database-environment args ((0 env (current-database-environment))) ((database-environment-close-flags flags)) (when (equal? (current-database-environment) env) (current-database-environment #f)) (check-error 'close-database-environment ((foreign-lambda* int ((nonnull-database-environment env) (int flags)) "C_return(env->close(env, flags));") env flags)))) (define-foreign-tagged-type (transaction nonnull-transaction "DB_TXN") transaction? tag:transaction) (define current-transaction (make-parameter #f)) (cond-expand (enable-bulk (define-foreign-enum-argconvert (transaction-flags int) (#:read-committed "DB_READ_COMMITTED") (#:read-uncommitted "DB_READ_UNCOMMITTED") (#:bulk "DB_TXN_BULK") (#:asynchronous "DB_TXN_NOSYNC") (#:no-wait "DB_TXN_NOWAIT") (#:snapshot "DB_TXN_SNAPSHOT") (#:synchronous "DB_TXN_SYNC") (#:wait "DB_TXN_WAIT") (#:asynchronous-write "DB_TXN_WRITE_NOSYNC"))) (else (define-foreign-enum-argconvert (transaction-flags int) (#:read-committed "DB_READ_COMMITTED") (#:read-uncommitted "DB_READ_UNCOMMITTED") (#:asynchronous "DB_TXN_NOSYNC") (#:no-wait "DB_TXN_NOWAIT") (#:snapshot "DB_TXN_SNAPSHOT") (#:synchronous "DB_TXN_SYNC") (#:wait "DB_TXN_WAIT") (#:asynchronous-write "DB_TXN_WRITE_NOSYNC")))) (define (with-transaction thunk . args) (let-keys+flags with-transaction args () ((transaction-flags flags)) (let-location ((txn transaction #f)) (let ((success? #f)) (dynamic-wind (lambda () (cond (txn => (cut error 'with-transaction "cannot re-enter transaction" <>)))) (lambda () (check-error 'with-transaction ((foreign-lambda* int ((nonnull-database-environment env) (transaction parent) ((nonnull-c-pointer transaction) txn) (int flags)) "C_return(env->txn_begin(env, parent, txn, flags));") (current-database-environment) (current-transaction) (location txn) flags)) (parameterize ((current-transaction txn)) (receive results (thunk) (set! success? #t) (apply values results)))) (lambda () (cond (txn => (lambda (txn) (check-error 'with-transaction (if success? ((foreign-lambda* int ((nonnull-transaction txn) (int flags)) "C_return(txn->commit(txn, flags));") txn 0) ((foreign-lambda* int ((nonnull-transaction txn)) "C_return(txn->abort(txn));") txn)))))))))))) ;; Databases (define-foreign-enum-argconvert (database-file-operation-flags int) (#:auto-commit "DB_AUTO_COMMIT")) (define (copy-database file target #!key password) (check-error 'copy-database ((foreign-lambda int "db_copy" nonnull-database-environment nonnull-c-string nonnull-c-string c-string) (current-database-environment) file target password))) (define (rename-database file target . args) (let-keys+flags rename-database args ((0 database #f)) ((database-file-operation-flags flags)) (check-error 'rename-database ((foreign-lambda* int ((nonnull-database-environment env) (transaction txn) (nonnull-c-string file) (c-string database) (nonnull-c-string target) (int flags)) "C_return(env->dbrename(env, txn, file, database, target, flags));") (current-database-environment) (current-transaction) file database target flags)))) (define (delete-database file . args) (let-keys+flags delete-database args ((0 database #f)) ((database-file-operation-flags flags)) (check-error 'delete-database ((foreign-lambda* int ((nonnull-database-environment env) (transaction txn) (nonnull-c-string file) (c-string database) (int flags)) "C_return(env->dbremove(env, txn, file, database, flags));") (current-database-environment) (current-transaction) file database flags)))) (define-foreign-tagged-type (database nonnull-database "DB") database? tag:database) (cond-expand (enable-heap (define-foreign-enum-type (database-type int) database-type->integer integer->database-type (b-tree "DB_BTREE") (hash-table "DB_HASH") (heap "DB_HEAP") (queue "DB_QUEUE") (records "DB_RECNO") (#f "DB_UNKNOWN"))) (else (define-foreign-enum-type (database-type int) database-type->integer integer->database-type (b-tree "DB_BTREE") (hash-table "DB_HASH") (queue "DB_QUEUE") (records "DB_RECNO") (#f "DB_UNKNOWN")))) (define-foreign-enum-argconvert (database-open-flags int) (#:auto-commit "DB_AUTO_COMMIT") (#:create "DB_CREATE") (#:exclusive "DB_EXCL") (#:multiversion "DB_MULTIVERSION") (#:no-mmap "DB_NOMMAP") (#:read-only "DB_RDONLY") (#:read-uncommitted "DB_READ_UNCOMMITTED") (#:free-threaded "DB_THREAD") (#:truncate "DB_TRUNCATE")) (define-foreign-enum-argconvert (database-open-extra-flags int) (#:checksum "DB_CHKSUM") (#:encrypt "DB_ENCRYPT") (#:transactions-not-durable "DB_TXN_NOT_DURABLE") (#:duplicates "DB_DUP") (#:duplicates/sorted "DB_DUPSORT") (#:no-reverse-splitting "DB_REVSPLITOFF") (#:in-order "DB_INORDER") (#:renumber "DB_RENUMBER") (#:snapshot "DB_SNAPSHOT")) (define (open-database file . args) (let-keys+flags open-database args ((0 type #f) (1 database #f) (#:mode mode 0) (#:byte-order byte-order #f) (#:page-size page-size #f) (#:heap-max-size heap-max-size #f) (#:queue-extent-size queue-extent-size #f) (#:record-size record-size #f) (#:record-delimiter record-delimiter #f) (#:record-padding record-padding #f) (#:record-source record-source #f) (#:password password #f) (#:serialize+deserialize serialize+deserialize #f)) ((database-open-flags flags) (database-open-extra-flags extra-flags) (database-encryption-flags encryption-flags)) (let-location ((db database #f)) (check-error 'open-database ((foreign-lambda int "db_create" (nonnull-c-pointer database) database-environment int) (location db) (current-database-environment) 0)) (condition-case (begin (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (int flags)) "C_return(db->set_flags(db, flags));") db extra-flags)) (when byte-order (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (unsigned-long order)) "C_return(db->set_lorder(db, order));") db byte-order))) (when page-size (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (unsigned-long size)) "C_return(db->set_pagesize(db, size));") db page-size))) (when heap-max-size (cond-expand (enable-heap (check-error 'open-database (call-with-values (cut quotient&remainder heap-max-size 1000000000) (cut (foreign-lambda* int ((nonnull-database db) (unsigned-long size_q) (unsigned-long size_r) (int flags)) "C_return(db->set_heapsize(db, size_q, size_r, flags));") db <> <> 0)))) (else (void)))) (when queue-extent-size (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (unsigned-long size)) "C_return(db->set_q_extentsize(db, size));") db queue-extent-size))) (when record-size (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (unsigned-long size)) "C_return(db->set_re_len(db, size));") db record-size))) (when record-delimiter (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (char delimiter)) "C_return(db->set_re_delim(db, delimiter));") db record-delimiter))) (when record-padding (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (char padding)) "C_return(db->set_re_pad(db, padding));") db record-padding))) (when record-source (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (nonnull-c-string file)) "C_return(db->set_re_source(db, file));") db record-source))) (unless (zero? encryption-flags) (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (nonnull-c-string password) (int flags)) "C_return(db->set_encrypt(db, password, flags));") db password encryption-flags))) (when serialize+deserialize ((foreign-lambda* void ((nonnull-database db) (scheme-object info)) "if (!db->app_private) db->app_private = CHICKEN_new_gc_root();" "CHICKEN_gc_root_set(db->app_private, info);") db serialize+deserialize)) (check-error 'open-database ((foreign-lambda* int ((nonnull-database db) (transaction txn) (nonnull-c-string file) (c-string database) (database-type type) (int flags) (int mode)) "C_return(db->open(db, txn, file, database, type, flags, mode));") db (current-transaction) file database type flags mode))) (exn (exn db) (close-database db) (abort exn))) db))) (define (database-type db) (let-location ((type database-type #f)) (check-error 'database-type ((foreign-lambda* int ((nonnull-database db) ((nonnull-c-pointer "DBTYPE") type)) "C_return(db->get_type(db, type));") db (location type))) type)) (define-foreign-enum-argconvert (database-close-flags int) (#:asynchronous "DB_NOSYNC")) (define (close-database db . args) (let-keys+flags close-database args () ((database-close-flags flags)) (check-error 'close-database ((foreign-lambda* int ((nonnull-database db) (int flags)) "if (db->app_private) {" " CHICKEN_delete_gc_root(db->app_private);" " db->app_private = NULL;" "}" "C_return(db->close(db, flags));") db flags)))) (define-foreign-enum-argconvert (secondary-key-flags int) (#:create "DB_CREATE") (#:immutable "DB_IMMUTABLE_KEY")) (define-foreign-enum-argconvert (foreign-key-flags int) (#:abort "DB_FOREIGN_ABORT") (#:cascade "DB_FOREIGN_CASCADE")) (define-values (database-serializer database-deserializer) (letrec ((serializer+deserializer (foreign-lambda* scheme-object ((nonnull-database db)) "C_return(db->app_private ? " "CHICKEN_gc_root_ref(db->app_private) : " "C_SCHEME_FALSE);")) (serializer (lambda (db) (cond ((serializer+deserializer db) => car) (else #f)))) (deserializer (lambda (db) (cond ((serializer+deserializer db) => cdr) (else #f))))) (values serializer deserializer))) (define (make-key-data db key) (case (database-type db) ((heap) (if key (ensure blob? key) (make-blob (cond-expand (enable-heap (foreign-value "DB_HEAP_RID_SZ" int)) (else 6))))) ((records queue) (u32vector->blob/shared (u32vector (or key 0)))) (else (make-value-data db key)))) (define (import-key-data db ptr size) (case (database-type db) ((heap) (let ((data (make-blob size))) (move-memory! ptr data size) data)) ((records queue) (let ((data (make-blob size))) (move-memory! ptr data size) (u32vector-ref (blob->u32vector/shared data) 0))) (else (import-value-data db ptr size)))) (define (make-value-data db value) (cond ((database-serializer db) => (lambda (serialize) (call-with-output-string (cut serialize value <>)))) (else value))) (define (import-value-data db ptr size) (let ((data (make-string size))) (move-memory! ptr data size) (cond ((database-deserializer db) => (cut call-with-input-string data <>)) (else data)))) (define-external (secondary_key (c-pointer root) (c-pointer args)) void ((foreign-lambda* void ((nonnull-c-pointer args) (bool ok)) "va_return_int((va_alist)args, ok ? 0 : DB_DONOTINDEX);") args (handle-exceptions exn (begin (print-error-message exn (current-error-port) "Callback Error") #f) (let* ((proc ((foreign-lambda scheme-object "CHICKEN_gc_root_ref" nonnull-c-pointer) root))) (let-location ((db database #f) (key-ptr c-pointer #f) (key-size unsigned-long 0) (value-ptr c-pointer #f) (value-size unsigned-long 0) (results (c-pointer "DBT") #f)) ((foreign-lambda* void ((nonnull-c-pointer args) ((nonnull-c-pointer database) db) ((nonnull-c-pointer c-pointer) key_data) ((nonnull-c-pointer unsigned-long) key_size) ((nonnull-c-pointer c-pointer) value_data) ((nonnull-c-pointer unsigned-long) value_size) ((nonnull-c-pointer (c-pointer "DBT")) results)) "va_start_int((va_alist)args);" "DB *secondary = va_arg_ptr((va_alist)args, DB *);" "*db = secondary;" "const DBT *key = va_arg_ptr((va_alist)args, const DBT *);" "*key_data = key->data; *key_size = key->size;" "const DBT *value = va_arg_ptr((va_alist)args, const DBT *);" "*value_data = value->data; *value_size = value->size;" "*results = va_arg_ptr((va_alist)args, DBT *);") args (location db) (location key-ptr) (location key-size) (location value-ptr) (location value-size) (location results)) (let* ((key (import-key-data db key-ptr key-size)) (value (import-value-data db value-ptr value-size)) (result-data (list->vector (map (cut make-key-data db <>) (receive (proc key value)))))) ((foreign-lambda* void (((nonnull-c-pointer "DBT") results) (unsigned-long result_count)) "size_t results_size = result_count * sizeof(DBT);" "if ((results->data = malloc(results_size))) {" " memset(results->data, 0, results_size);" " results->size = result_count;" " results->flags = DB_DBT_APPMALLOC;" "}") results (vector-length result-data)) (do ((i 0 (fx+ i 1))) ((>= i (vector-length result-data))) (let ((result-data (vector-ref result-data i))) ((foreign-lambda* void (((nonnull-c-pointer "DBT") results) (unsigned-long i) (nonnull-scheme-pointer result_data) (unsigned-long result_size)) "if (results->data) {" " DBT *result = &((DBT *)results)[i];" " if ((result->data = malloc(result_size))) {" " memcpy(result->data, result_data, result_size);" " result->size = result_size;" " result->flags = DB_DBT_APPMALLOC;" " }" "}") results i result-data (blob/string-size result-data)))))))))) (define secondary-key-callback (letrec ((cache (make-mutex 'secondary-key-callback)) (secondary-key-callback (lambda (proc key-type result-type) (dynamic-wind (cut mutex-lock! cache) (lambda () (and proc (let ((cache (mutex-specific cache))) (cond ((hash-table-ref/default cache proc #f) => identity) (else (let ((callback ((foreign-lambda* c-pointer ((scheme-object info)) "void *root = CHICKEN_new_gc_root();" "CHICKEN_gc_root_set(root, info);" "C_return(alloc_callback(&secondary_key, root));") proc))) (hash-table-set! cache proc callback) callback)))))) (cut mutex-unlock! cache))))) (mutex-specific-set! cache (make-hash-table equal? equal?-hash)) secondary-key-callback)) (define (database-associate db proc secondary . args) (let-keys+flags database-associate args ((0 foreign #f)) ((secondary-key-flags secondary-flags) (foreign-key-flags foreign-flags)) (check-error 'database-associate ((foreign-safe-lambda* int ((nonnull-database db) (transaction txn) (nonnull-database secondary) (c-pointer callback) (int flags)) "C_return(db->associate(db, txn, secondary, callback, flags));") db (current-transaction) secondary (secondary-key-callback proc (database-type db) (database-type secondary)) secondary-flags)) (when foreign (check-error 'database-associate ((foreign-lambda* int ((nonnull-database db) (nonnull-database secondary) (c-pointer callback) (int flags)) "C_return(db->associate_foreign(db, secondary, callback, flags));") foreign secondary #f foreign-flags))))) (define-foreign-enum-argconvert (database-ref-flags int) (#:consume "DB_CONSUME") (#:consume/wait "DB_CONSUME_WAIT") (#:ignore-lease "DB_IGNORE_LEASE") (#:read-committed "DB_READ_COMMITTED") (#:read-uncommitted "DB_READ_UNCOMMITTED") (#:read-modify-write "DB_RMW")) (define-foreign-enum-argconvert (database-set!-flags int) (#:append "DB_APPEND") (#:no-duplicate "DB_NODUPDATA") (#:no-overwrite "DB_NOOVERWRITE") (#:overwrite-duplicate "DB_OVERWRITE_DUP")) (define-values (database-ref database-set!) (letrec ((database-ref (lambda (db key . args) (let-keys+flags database-ref args ((0 default (lambda () (abort (make-composite-condition (make-property-condition 'exn 'location 'database-ref 'message "database does not contain key" 'arguments (list db key)) (make-property-condition 'access)))))) ((database-ref-flags flags)) (condition-case (let-location ((value-ptr c-pointer #f) (value-size unsigned-long 0)) (let ((key-data (make-key-data db key))) (check-error 'database-ref ((foreign-lambda* int ((nonnull-database db) (transaction txn) (nonnull-scheme-pointer key_data) (unsigned-long key_size) ((nonnull-c-pointer c-pointer) value_data) ((nonnull-c-pointer unsigned-long) value_size) (int flags)) "int s;" "DBT key, value;" "memset(&key, 0, sizeof(DBT));" "key.data = key_data; key.size = key_size;" "memset(&value, 0, sizeof(DBT));" "s = db->get(db, txn, &key, &value, flags);" "*value_data = value.data; *value_size = value.size;" "C_return(s);") db (current-transaction) key-data (blob/string-size key-data) (location value-ptr) (location value-size) flags))) (import-value-data db value-ptr value-size)) (exn (exn db) (if (access-error? exn) (if (procedure? default) (default) default) (abort exn))))))) (database-set! (lambda (db key value . args) (let-keys+flags database-set! args () ((database-set!-flags flags)) (let* ((key-data (make-key-data db key)) (key-size (blob/string-size key-data)) (value-data (make-value-data db value)) (value-size (string-length value-data))) (check-error 'database-set! ((foreign-safe-lambda* int ((nonnull-database db) (transaction txn) (nonnull-scheme-pointer key_data) (unsigned-long key_size) (nonnull-scheme-pointer value_data) (unsigned-long value_size) (int flags)) "DBT key, value;" "memset(&key, 0, sizeof(DBT));" "key.data = key_data; key.size = key.ulen = key_size;" "key.flags = DB_DBT_USERMEM;" "memset(&value, 0, sizeof(DBT));" "value.data = value_data; value.size = value_size;" "C_return(db->put(db, txn, &key, &value, flags));") db (current-transaction) key-data key-size value-data value-size flags)) (if (memq #:append args) (import-key-data db key-data key-size) (void))))))) (values (getter-with-setter database-ref database-set!) database-set!))) (define-foreign-enum-argconvert (database-exists?-flags int) (#:read-committed "DB_READ_COMMITTED") (#:read-uncommitted "DB_READ_UNCOMMITTED") (#:read-modify-write "DB_RMW")) (define (database-exists? db key . args) (let-keys+flags database-exists? args () ((database-exists?-flags flags)) (let ((key-data (make-key-data db key))) ((foreign-lambda* bool ((nonnull-database db) (transaction txn) (nonnull-scheme-pointer key_data) (unsigned-long key_size) (int flags)) "DBT key;" "memset(&key, 0, sizeof(DBT));" "key.data = key_data; key.size = key_size;" "C_return(!db->exists(db, txn, &key, flags));") db (current-transaction) key-data (blob/string-size key-data) flags)))) (define-foreign-enum-argconvert (database-delete!-flags int) (#:consume "DB_CONSUME")) (define (database-delete! db key . args) (let-keys+flags database-delete! args () ((database-delete!-flags flags)) (let ((key-data (make-key-data db key))) (check-error 'database-delete! ((foreign-safe-lambda* int ((nonnull-database db) (transaction txn) (nonnull-scheme-pointer key_data) (unsigned-long key_size) (int flags)) "DBT key;" "memset(&key, 0, sizeof(DBT));" "key.data = key_data; key.size = key_size;" "C_return(db->del(db, txn, &key, flags));") db (current-transaction) key-data (blob/string-size key-data) flags))))) (define-foreign-tagged-type (cursor nonnull-cursor "DBC") cursor? tag:cursor) (define-foreign-enum-argconvert (database-cursor-flags int) (#:read-committed "DB_READ_COMMITTED") (#:read-uncommitted "DB_READ_UNCOMMITTED") (#:snapshot "DB_TXN_SNAPSHOT")) (define-foreign-enum-argconvert (database-cursor-set-flags int) (#:range "DB_SET_RANGE")) (define (database-fold db proc seed . args) (let-keys+flags database-fold args ((0 key #f)) ((database-cursor-flags flags) (database-cursor-set-flags set-flags)) (define step-flags (foreign-value "DB_NEXT" int)) (if key (if (zero? set-flags) (set!-values (set-flags step-flags) (values (foreign-value "DB_SET" int) (foreign-value "DB_NEXT_DUP" int)))) (set! set-flags (foreign-value "DB_FIRST" int))) (let-location ((cur cursor #f)) (dynamic-wind (lambda () (cond (cur => (cut error 'database-fold "cannot re-enter cursor walk" <>)))) (lambda () (check-error 'database-fold ((foreign-lambda* int ((nonnull-database db) (transaction txn) ((nonnull-c-pointer cursor) cur) (int flags)) "C_return(db->cursor(db, txn, cur, flags));") db (current-transaction) (location cur) flags)) (let ((cur cur)) (define (cursor-step key flag) (condition-case (let-location ((key-ptr c-pointer #f) (key-size unsigned-long 0) (value-ptr c-pointer #f) (value-size unsigned-long 0)) (let ((key-data (make-key-data db key))) (check-error 'database-fold ((foreign-lambda* int ((nonnull-cursor cur) (scheme-pointer ikey_data) (unsigned-long ikey_size) ((nonnull-c-pointer c-pointer) okey_data) ((nonnull-c-pointer unsigned-long) okey_size) ((nonnull-c-pointer c-pointer) value_data) ((nonnull-c-pointer unsigned-long) value_size) (int flags)) "int s;" "DBT key, value;" "memset(&key, 0, sizeof(DBT));" "key.data = ikey_data; key.size = ikey_size;" "memset(&value, 0, sizeof(DBT));" "s = cur->get(cur, &key, &value, flags);" "*okey_data = key.data; *okey_size = key.size;" "*value_data = value.data; *value_size = value.size;" "C_return(s);") cur key-data (blob/string-size key-data) (location key-ptr) (location key-size) (location value-ptr) (location value-size) flag))) (cons (import-key-data db key-ptr key-size) (import-value-data db value-ptr value-size))) (exn (exn db) (if (access-error? exn) #f (abort exn))))) (let loop ((seed seed) (key+value (cursor-step key set-flags))) (if key+value (loop (proc (car key+value) (cdr key+value) seed) (cursor-step #f step-flags)) seed)))) (lambda () (cond (cur => (lambda (cur) (check-error 'database-fold ((foreign-lambda* int ((nonnull-cursor cur)) "C_return(cur->close(cur));") cur)))))))))) (define (database-walk db proc . args) (apply database-fold db (lambda (key value seed) (proc key value) seed) (void) args)) )