Skip to content

Commit

Permalink
add a way to declare database tables and query them
Browse files Browse the repository at this point in the history
+ move the bonny utils there
  • Loading branch information
euhmeuh committed Aug 11, 2019
1 parent 11633a4 commit 9b19029
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 2 deletions.
164 changes: 164 additions & 0 deletions web-galaxy-lib/web-galaxy/db.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#lang racket/base

(require
(for-syntax
racket/base
syntax/parse
web-galaxy/utils)
racket/contract
racket/function
racket/list
racket/match
racket/provide-syntax
racket/string
web-galaxy/utils
db)

(provide
current-db-path
create-db
connect-db
define-table
table-out)

(define current-db-path (make-parameter "database.sqlite"))

(define (create-db . creators)
(define db (sqlite3-connect #:database (current-db-path) #:mode 'create))
(for ([creator creators])
(creator db))
(disconnect db))

(define (connect-db)
(when (not (file-exists? (current-db-path)))
(error 'make-db-connection "Database file could not be found: ~a" (current-db-path)))
(virtual-connection
(connection-pool
(lambda ()
(sqlite3-connect #:database (current-db-path))))))

(define (table-id table)
(define found (and table (list? table) (assq 'id table)))
(and found (cdr found)))

(define-for-syntax (make-db-funcs name)
(list
(format-prefix "db-create-" name)
(format-prefix "db-save-" name)
(format-prefix "db-read-" name)
(format-prefix "db-list-" name)
))

(define-syntax (define-table stx)
(define-syntax-class type
(pattern (~datum text) #:with str #'"TEXT")
(pattern (~datum integer) #:with str #'"INTEGER")
(pattern (~datum datetime) #:with str #'"DATETIME")
(pattern (~datum date) #:with str #'"DATE")
(pattern (~datum blob) #:with str #'"BLOB"))
(syntax-parse stx
[(_ <name>:id (<column>:id <type>:type) ...)
#:with (db-create db-save db-read db-list) (make-db-funcs #'<name>)
#'(begin
(define (db-create db)
(query-exec db (make-create-query (symbol->string '<name>)
(list (list (symbol->string '<column>)
<type>.str) ...))))

(define/contract (db-save db table)
(-> connection?
(listof (or/c (cons/c 'id any/c)
(cons/c '<column> any/c) ...))
void?)
(define-values (query args)
(if (table-id table)
(make-update-query (symbol->string '<name>) table)
(make-insert-query (symbol->string '<name>) table)))
(apply query-exec db query args))

(define (db-read db . conditions)
(define-values (query args)
(make-select-query (symbol->string '<name>)
'(id <column> ...)
conditions))
(row->table '(id <column> ...)
(apply query-maybe-row db query args)))

(define (db-list db . conditions)
(define-values (query args)
(make-select-query (symbol->string '<name>)
'(id <column> ...)
conditions))
(map (curry row->table '(id <column> ...))
(apply query-rows db query args)))

)]))

(define (row->table fields row)
(map cons fields (vector->list row)))

(define (make-create-query name fields)
(format "CREATE TABLE ~a (id INTEGER PRIMARY KEY, ~a)"
name
(string-join
(map (curryr string-join " ") fields)
", ")))

(define (make-insert-query name table)
(values
(format "INSERT INTO ~a (~a) VALUES (~a)"
name
(string-join (map (compose symbol->string car) table) ", ")
(string-join (for/list ([i (in-range 1 (add1 (length table)))])
(format "?~a" i))
", "))
(map cdr table)))

(define (make-update-query name table)
(define id (table-id table))
(define fields (remove (cons 'id id) table))
(values
(format "UPDATE ~a SET ~a WHERE id=?1"
name
(string-join (for/list ([field fields]
[i (in-naturals 2)])
(format "~a=?~a" (car field) i))
", "))
(cons id (map cdr fields))))

(define (make-select-query name fields conditions)
(define order-by (map (lambda (condition)
(match condition
[(list 'order-by
(? (apply symbols fields) name)
(? (symbols 'asc 'desc) dir))
(list name dir)]
[(list 'order-by
(? (apply symbols fields) name))
(list name)]))
conditions))
(values
(string-join
(list (format "SELECT * from ~a" name)
(make-order-by-section order-by)))
'()))

(define (make-order-by-section orders)
(if (not (empty? orders))
(string-append
"ORDER BY "
(string-join
(map (lambda (order)
(cond/string
[_ (symbol->string (first order))]
[(not (empty? (cdr order)))
(symbol->string (second order))]))
orders)
", "))
""))

(define-provide-syntax (table-out stx)
(syntax-parse stx
[(_ <name>:id)
#:with (db-create db-save db-read db-list) (make-db-funcs #'<name>)
#'(for-meta 0 db-create db-save db-read db-list)]))
66 changes: 64 additions & 2 deletions web-galaxy-lib/web-galaxy/utils.rkt
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#lang racket/base

(require
(for-syntax
racket/base
syntax/parse)
racket/contract/base
racket/path
racket/string
racket/syntax
syntax/stx)

Expand All @@ -13,8 +18,9 @@
[format-prefix (-> string? syntax? syntax?)]
[format-suffix (-> syntax? string? syntax?)])

(require
racket/path)
(provide
cond/list
cond/string)

(define (filename path)
(path->string
Expand All @@ -30,3 +36,59 @@

(define (format-suffix stx str)
(format-id stx (string-append "~a" str) stx))

(begin-for-syntax
(define-syntax-class maybe-cond
#:literals (_)
(pattern (_ value:expr)
#:with condition #'#t)
(pattern (condition:expr value:expr))))

(define-syntax (cond/list stx)
(syntax-parse stx
[(cond/list mc:maybe-cond ...)
#'(let* ([result null]
[result (if mc.condition
(cons mc.value result)
result)] ...)
(reverse result))]))

(define-syntax (cond/string stx)
(define-splicing-syntax-class maybe-sep
(pattern (~seq #:separator sep:str))
(pattern (~seq) #:with sep #'" "))
(syntax-parse stx
[(_ mc:maybe-cond ... ms:maybe-sep join-options ...)
#'(string-join (cond/list mc ...) ms.sep join-options ...)]))

(module+ test
(require
rackunit)

(define beer 'la-chouffe)
(define (fresh? beer) #t)
(define (open-bottle beer) 'opened-beer)
(define time 'afternoon)

(define things-i-like
(cond/list
[_ 'carpaccio]
[_ 'pasta]
[(eq? time 'morning) 'croissant]
[(eq? time 'afternoon) 'brioche]
[#f 'salsifi]
[(fresh? beer) (open-bottle beer)]))

(check-equal? things-i-like
'(carpaccio pasta brioche opened-beer))

(check-equal? (cond/string
[_ "I really"]
[_ "love"]
[#f "hate"]
[_ "food"]
#:separator " * "
#:after-last "!")
"I really * love * food!")

)
49 changes: 49 additions & 0 deletions web-galaxy-test/tests/web-galaxy/test-db.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#lang racket/base

(require
rackunit
web-galaxy/db)

(current-db-path "test-database.sqlite")

(define ponies
(hasheq
'rarity '((name . "Rarity") (fur . "White") (mane . "Purple") (favorite . 1))
'twilight '((name . "Twilight Sparkle") (fur . "Mauve") (mane . "Violet") (favorite . 0))
'rainbowdash '((name . "Rainbow Dash") (fur . "Blue") (mane . "Rainbow") (favorite . 0))
'fluttershy '((name . "Fluttershy") (fur . "Pink") (mane . "Soft Yellow") (favorite . 0))
'pinkiepie '((name . "Pinkie Pie") (fur . "Pink") (mane . "Pink") (favorite . 0))
'applejack '((name . "Applejack") (fur . "Hay") (mane . "Orange") (favorite . 1))
))

(define-table pony
(name text)
(fur text)
(mane text)
(favorite integer))

(when (file-exists? (current-db-path))
(delete-file (current-db-path)))

(create-db db-create-pony)
(define db (connect-db))

(check-equal? (db-list-pony db) '())

(db-save-pony db (hash-ref ponies 'rarity))
(check-equal? (db-list-pony db)
'(((id . 1)
(name . "Rarity")
(fur . "White")
(mane . "Purple")
(favorite . 1))))

(db-save-pony db '((id . 1) (name . "Evil Rarity") (fur . "Night")))
(check-equal? (db-list-pony db)
'(((id . 1)
(name . "Evil Rarity")
(fur . "Night")
(mane . "Purple")
(favorite . 1))))

(delete-file (current-db-path))

0 comments on commit 9b19029

Please sign in to comment.