;; guile-wiredtiger - 0.2 - 2015/10/22
;; Copyright © 2014-2015 Amirouche BOUBEKKI
;; guile-wiredtiger is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 2 of the License, or
;; (at your option) or version 3.
;; guile-wiredtiger is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with guile-wiredtiger. If not, see
;;; Comment:
;;
;; Tested with wiredtiger-2.6.1
;;
(define-module (wiredtiger))
(use-modules (srfi srfi-9)) ;; records
(use-modules (srfi srfi-9 gnu)) ;; set-record-type-printer!
(use-modules (srfi srfi-26)) ;; cut
(use-modules (rnrs bytevectors))
(use-modules (ice-9 iconv)) ;; string->bytevector
(use-modules (ice-9 match))
(use-modules (ice-9 format))
(use-modules (ice-9 optargs)) ;; lambda*
(use-modules (ice-9 receive))
(use-modules (system foreign)) ;; ffi
;;;
;;; srfi-99
;;;
;;
;; macro to quickly define immutable records
;;
;;
;; Usage:
;;
;; (define-record-type field-one field-two)
;; (define zzz (make-abc 1 2))
;; (abc-field-one zzz) ;; => 1
;;
(define-syntax define-record-type*
(lambda (x)
(define (%id-name name) (string->symbol (string-drop (string-drop-right (symbol->string name) 1) 1)))
(define (id-name ctx name)
(datum->syntax ctx (%id-name (syntax->datum name))))
(define (id-append ctx . syms)
(datum->syntax ctx (apply symbol-append (map syntax->datum syms))))
(syntax-case x ()
((_ rname field ...)
(and (identifier? #'rname) (and-map identifier? #'(field ...)))
(with-syntax ((cons (id-append #'rname #'make- (id-name #'rname #'rname)))
(pred (id-append #'rname (id-name #'rname #'rname) #'?))
((getter ...) (map (lambda (f)
(id-append f (id-name #'rname #'rname) #'- f))
#'(field ...))))
#'(define-record-type rname
(cons field ...)
pred
(field getter)
...))))))
;;; ffi helpers
(define *NULL* %null-pointer)
(define *pointer* '*)
;; This is small syntax change
(define* ((dynamic-link* shared-object) func-name)
(dynamic-func func-name shared-object))
;;; foreign macro
(define-syntax-rule (foreign
;; function pointer and signature
(ret function-pointer args ...)
;; foreign-function lambda wrapper
wrapper)
(let ((foreign-function (pointer->procedure ret
function-pointer
(list args ...))))
(lambda rest
(apply wrapper (append (list foreign-function) rest)))))
;;: utils
(define (make constructor constructor-structure pointer size)
"Convert a POINTER to a structure of SIZE and into a record
using CONSTRUCTOR and where the structure is wrapped using
CONSTRUCTOR-STRUCTURE"
(let* ((pointer (make-pointer (array-ref pointer 0)))
(array (pointer->bytevector pointer size 0 'u64))
(structure (apply constructor-structure (map make-pointer (array->list array)))))
(constructor pointer structure)))
;;;
;;; wiredtiger bindings
;;;
(define wiredtiger (dynamic-link "libwiredtiger.so"))
(define wiredtiger* (dynamic-link* wiredtiger))
(define-public WT_NOTFOUND -31803)
;;
;; (wiredtiger-error-string code)
;;
(define* (%wiredtiger-string-error call)
(foreign
(*pointer* (wiredtiger* "wiredtiger_strerror") int)
(lambda (foreign-function code)
(let ((message (pointer->string (foreign-function code))))
(format #t "wiredtiger error while calling ~a: ~a" call message))
;; here we use (exit) instead of (error) which outputs a not very useful traceback
(exit -1))))
(define (wiredtiger-string-error call message)
((%wiredtiger-string-error call) message))
;;;
;;; Connection
;;;
(define-record-type* handle structure)
(set-record-type-printer!
(lambda (record port)
(format port
""
(pointer-address (connection-handle record)))))
;; record holding structure pointers
(define-record-type*
async-flush
async-new-op
close
reconfigure
get-home
configure-method
is-new
open-session
load-extension
add-data-source
add-collator
add-compressor
add-encryptor
add-extractor
get-extension-api)
(define-public connection-open
(foreign
(int (wiredtiger* "wiredtiger_open") *pointer* *pointer* *pointer* *pointer*)
(lambda (foreign-function home config)
(let* (;; init a double pointer
(pointer (u64vector 0))
(double-pointer (bytevector->pointer pointer))
;; convert arguments to c types
(%home (string->pointer home))
(%config (string->pointer config))
;; call the foreign function
;; FIXME: add support for error_handler
(code (foreign-function %home *NULL* %config double-pointer)))
(if (eq? code 0)
(make make-connection make-connection-structure pointer 15)
(let ((message (format #false "(wiredtiger-open ~s ~s)" home config)))
(wiredtiger-string-error message code)))))))
(define (%connection-close connection)
(foreign
(int (connection-structure-close (connection-structure connection)) *pointer* *pointer*)
(lambda (foreign-function config)
(let* (;; init a double pointer
(pointer (u64vector 0))
(double-pointer (bytevector->pointer pointer))
;; convert arguments to c types
(%config (string->pointer config))
;; call the foreign function
;; FIXME: add support for error_handler
(code (foreign-function (connection-handle connection) %config)))
(if (eq? code 0)
#true
(let ((message (format #false "(connection-close ~s ~s)" connection config)))
(wiredtiger-string-error message code)))))))
(define*-public (connection-close connection #:optional (config ""))
((%connection-close connection) config))
;;;
;;; Session
;;;
(define-record-type* handle structure)
(set-record-type-printer!
(lambda (record port)
(format port
""
(pointer-address (session-handle record)))))
;; record holding structure pointers
(define-record-type*
connection
%app-private%
close
reconfigure
string-error
cursor-open
create
compact
drop
log-printf
rename
salvage
truncate
upgrade
verify
transaction-begin
transaction-commit
transaction-rollback
checkpoint
snapshot
transaction-pinned-range
transaction-sync)
(define (%session-string-error session)
(foreign
(int (session-structure-string-error (session-structure session)) *pointer* int)
(lambda (foreign-function code)
(format #true
"wiredtiger session error: ~a"
(pointer->string (make-pointer (foreign-function (session-handle session) code))))
(exit -1))))
(define-public (session-string-error session code)
((%session-string-error session) code))
(define (%session-open connection)
(foreign
(int (connection-structure-open-session (connection-structure connection)) *pointer* *pointer* *pointer* *pointer*)
(lambda (foreign-function config)
(let* (;; init a double pointer
(pointer (u64vector 0))
(double-pointer (bytevector->pointer pointer))
;; convert arguments to c types
(%config (string->pointer config))
;; call the foreign function
;; FIXME: add support for error_handler
(code (foreign-function (connection-handle connection) *NULL* %config double-pointer)))
(if (eq? code 0)
(make make-session make-session-structure pointer 22)
(let ((message (format #false "(session-open ~s ~s)" connection config)))
(wiredtiger-string-error message code)))))))
(define*-public (session-open connection #:optional (config ""))
((%session-open connection) config))
(define (%session-create session)
(foreign
(int (session-structure-create (session-structure session)) *pointer* *pointer* *pointer*)
(lambda (foreign-function name config)
(let* (;; convert arguments to c types
(%name (string->pointer name))
(%config (string->pointer config))
;; call the foreign function
(code (foreign-function (session-handle session) %name %config)))
(if (not (eq? code 0))
(let ((message (format #false "(session-create ~s ~s)" name config)))
(wiredtiger-string-error message code)))))))
(define-public (session-create session name config)
((%session-create session) name config))
(define (%session-close session)
(foreign
(int (session-structure-close (session-structure session)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (session-handle session))))
(if (not (eq? code 0))
(let ((message (format #false "(session-close ~s)" session)))
(wiredtiger-string-error message code)))))))
(define-public (session-close session)
((%session-close session)))
(define (%session-transaction-begin session)
(foreign
(int (session-structure-transaction-begin (session-structure session)) *pointer* *pointer*)
(lambda (foreign-function config)
(let* ((%config (string->pointer config))
;; call the foreign function
(code (foreign-function (session-handle session) %config)))
(if (eq? code 0)
#true
(let ((message (format #false "(session-transaction-begin ~s ~s)" session config)))
(wiredtiger-string-error message code)))))))
(define*-public (session-transaction-begin session #:optional (config ""))
((%session-transaction-begin session) config))
(define (%session-transaction-commit session)
(foreign
(int (session-structure-transaction-commit (session-structure session)) *pointer* *pointer*)
(lambda (foreign-function config)
(let* ((%config (string->pointer config))
;; call the foreign function
(code (foreign-function (session-handle session) %config)))
(if (eq? code 0)
#true
(let ((message (format #false "(session-transaction-commit ~s ~s)" session config)))
(wiredtiger-string-error message code)))))))
(define*-public (session-transaction-commit session #:optional (config ""))
((%session-transaction-commit session) config))
(define (%session-transaction-rollback session)
(foreign
(int (session-structure-transaction-rollback (session-structure session)) *pointer* *pointer*)
(lambda (foreign-function name config)
(let* ((%config (string->pointer config))
;; call the foreign function
(code (foreign-function (session-handle session) %config)))
(if (eq? code 0)
#true
(let ((message (format #false "(session-transaction-rollback ~s ~s)" session config)))
(wiredtiger-string-error message code)))))))
(define*-public (session-transaction-rollback session #:optional (config ""))
((%session-transaction-rollback session) config))
;;;
;;; Cursor
;;;
(define-record-type* handle structure)
(set-record-type-printer!
(lambda (record port)
(format port
""
(pointer-address (cursor-handle record)))))
;; record holding structure pointers
(define-record-type*
session
uri
key-format
value-format
key-ref
value-ref
key-set
value-set
compare
equals
next
previous
reset
search
search-near
insert
update
remove
close
reconfigure
;; XXX: other fields are defined in the header
;; those are only useful to implement a new cursor type
;; and as such are not part the record
)
(define (cursor-key-format cursor)
(pointer->string (cursor-structure-key-format (cursor-structure cursor))))
(define (cursor-value-format cursor)
(pointer->string (cursor-structure-value-format (cursor-structure cursor))))
(define (%cursor-open session)
(foreign
(int (session-structure-cursor-open (session-structure session)) *pointer* *pointer* *pointer* *pointer* *pointer*)
(lambda (foreign-function uri config)
(let* (;; init a double pointer
(pointer (u64vector 0))
(double-pointer (bytevector->pointer pointer))
;; convert arguments to c types
(%uri (string->pointer uri))
(%config (string->pointer config))
;; call the foreign function
(code (foreign-function (session-handle session) %uri *NULL* %config double-pointer)))
(if (eq? code 0)
(make make-cursor make-cursor-structure pointer 20)
(let ((message (format #false "(cursor-open ~a ~s ~s)" session uri config)))
(wiredtiger-string-error message code)))))))
(define*-public (cursor-open session uri #:optional (config ""))
((%cursor-open session) uri config))
;;; key/value set/ref
(define (item->string bv)
(pointer->string (make-pointer (array-ref bv 0))))
(define (item->integer bv)
(array-ref bv 0))
(define *item->value* `((#\S . ,item->string)
(#\Q . ,item->integer)
(#\q . ,item->integer)
(#\r . ,item->integer)))
(define (pointers->scm formats pointers)
(let loop ((formats (string->list formats))
(pointers pointers)
(out '()))
(cond
((and (null? formats) (null? pointers)) out)
((or (null? formats) (null? pointers))
(throw 'wiredtiger "failed to ref cursor value due to format error"))
(else (loop (cdr formats)
(cdr pointers)
(append out (list ((assoc-ref *item->value* (car formats)) (car pointers)))))))))
(define-public (cursor-key-ref cursor)
(let* ((args (map (lambda (_) (u64vector 0)) (string->list (cursor-key-format cursor))))
(args* (append (list (cursor-handle cursor)) (map bytevector->pointer args)))
(signature (map (lambda (_) *pointer*) args*))
(proc (pointer->procedure int
(cursor-structure-key-ref (cursor-structure cursor))
signature)))
(apply proc args*)
(pointers->scm (cursor-key-format cursor) args)))
(define-public (cursor-value-ref cursor)
(let* ((args (map (lambda ignore (u64vector 0))
(string->list (cursor-value-format cursor))))
(args* (append (list (cursor-handle cursor))
(map bytevector->pointer args)))
(signature (map (lambda (_) *pointer*) args*))
(proc (pointer->procedure int
(cursor-structure-value-ref (cursor-structure cursor))
signature)))
(apply proc args*)
(pointers->scm (cursor-value-format cursor) args)))
;;; set procedures
(define make-string-pointer
(compose bytevector->pointer
(cut string->bytevector <> "utf-8")
(cut string-append <> "\0")))
(define *format->pointer* `((#\S . ,make-string-pointer)
(#\Q . ,make-pointer)
(#\q . ,make-pointer)
(#\r . ,make-pointer)))
(define (formats->items formats values)
(let loop ((formats (string->list formats))
(values values)
(out '()))
(cond
((and (null? formats) (null? values)) out)
((or (null? formats) (null? values))
(throw 'wiredtiger "failed to set cursor due to format error"))
(else (loop (cdr formats)
(cdr values)
(append out (list ((assoc-ref *format->pointer* (car formats)) (car values)))))))))
(define-public (cursor-key-set cursor . key)
(let* ((args (append (list (cursor-handle cursor)) (formats->items (cursor-key-format cursor) key)))
(signature (map (lambda (_) *pointer*) args))
(proc (pointer->procedure int
(cursor-structure-key-set (cursor-structure cursor))
signature)))
(apply proc args)))
(define-public (cursor-value-set cursor . value)
(let* ((args (append (list (cursor-handle cursor)) (formats->items (cursor-value-format cursor) value)))
(signature (map (lambda (_) *pointer*) args))
(proc (pointer->procedure int
(cursor-structure-value-set (cursor-structure cursor))
signature)))
(apply proc args)))
(define (%cursor-reset cursor)
(foreign
(int (cursor-structure-reset (cursor-structure cursor)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (cursor-handle cursor))))
(if (eq? code 0)
#true
(let ((message (format #false "(cursor-reset ~a)" cursor)))
(wiredtiger-string-error message code)))))))
(define-public (cursor-reset cursor)
((%cursor-reset cursor)))
(define (%cursor-next cursor)
(foreign
(int (cursor-structure-next (cursor-structure cursor)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (cursor-handle cursor))))
(if (eq? code 0)
#true
(if (eq? code WT_NOTFOUND)
#false
(let ((message (format #false "(cursor-next ~a)" cursor)))
(wiredtiger-string-error message code))))))))
(define-public (cursor-next cursor)
((%cursor-next cursor)))
(define (%cursor-previous cursor)
(foreign
(int (cursor-structure-previous (cursor-structure cursor)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (cursor-handle cursor))))
(if (eq? code 0)
#true
(let ((message (format #false "(cursor-previous ~a)" cursor)))
(wiredtiger-string-error message code)))))))
(define-public (cursor-previous cursor)
((%cursor-previous cursor)))
(define (%cursor-search cursor)
(foreign
(int (cursor-structure-search (cursor-structure cursor)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (cursor-handle cursor))))
(if (eq? code 0)
#true
#false)))))
(define-public (cursor-search cursor)
((%cursor-search cursor)))
(define (%cursor-search-near cursor)
(foreign
(int (cursor-structure-search-near (cursor-structure cursor)) *pointer* *pointer*)
(lambda (foreign-function)
(let* (;; init a integer pointer
(integer (u64vector 0))
(pointer (bytevector->pointer integer))
;; call the foreign function
(code (foreign-function (cursor-handle cursor) pointer)))
(if (eq? code 0)
(array-ref integer 0)
(if (eq? code WT_NOTFOUND)
#false
(let ((message (format #false "(cursor-search-near ~a)" cursor)))
(wiredtiger-string-error message code))))))))
(define-public (cursor-search-near cursor)
((%cursor-search-near cursor)))
(define (%cursor-insert cursor)
(foreign
(int (cursor-structure-insert (cursor-structure cursor)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (cursor-handle cursor))))
(if (eq? code 0)
#true
(let ((message (format #false "(cursor-insert ~a)" cursor)))
(wiredtiger-string-error message code)))))))
(define-public (cursor-insert cursor)
((%cursor-insert cursor)))
(define (%cursor-update cursor)
(foreign
(int (cursor-structure-update (cursor-structure cursor)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (cursor-handle cursor))))
(if (eq? code 0)
#true
(let ((message (format #false "(cursor-update ~a)" cursor)))
(wiredtiger-string-error message code)))))))
(define-public (cursor-update cursor)
((%cursor-update cursor)))
(define (%cursor-remove cursor)
(foreign
(int (cursor-structure-remove (cursor-structure cursor)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (cursor-handle cursor))))
(if (eq? code 0)
#true
(let ((message (format #false "(cursor-remove ~a)" cursor)))
(wiredtiger-string-error message code)))))))
(define-public (cursor-remove cursor)
((%cursor-remove cursor)))
(define (%cursor-close cursor)
(foreign
(int (cursor-structure-close (cursor-structure cursor)) *pointer*)
(lambda (foreign-function)
(let* (;; call the foreign function
(code (foreign-function (cursor-handle cursor))))
(if (eq? code 0)
#true
(let ((message (format #false "(cursor-close ~a)" cursor)))
(wiredtiger-string-error message code)))))))
(define-public (cursor-close cursor)
((%cursor-close cursor)))