db.lisp 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. (in-package :cl-user)
  2. (defpackage chatikbot.db
  3. (:use :cl)
  4. (:import-from :chatikbot.utils
  5. :dekeyify)
  6. (:export :db-transaction
  7. :db-execute
  8. :db-select
  9. :db-single
  10. :db-init
  11. :load-settings
  12. :set-setting
  13. :lists-set-entry
  14. :lists-get))
  15. (in-package :chatikbot.db)
  16. (defvar *db-name* "db.sqlite" "SQLite database name")
  17. (defun db-path ()
  18. (merge-pathnames *db-name*
  19. (asdf:component-pathname
  20. (asdf:find-system '#:chatikbot))))
  21. (defvar *current-db* nil "Currently opened database")
  22. (defvar *db-lock* (bordeaux-threads:make-recursive-lock
  23. "sqlite connection lock") "Database connection lock")
  24. (defmacro with-db ((db) &body body)
  25. `(bordeaux-threads:with-recursive-lock-held (*db-lock*)
  26. (unless *current-db*
  27. (let ((path (db-path)))
  28. (log:info "Connecting to ~A" path)
  29. (setf *current-db* (sqlite:connect path))
  30. (sqlite:execute-non-query *current-db* "PRAGMA foreign_keys = ON")))
  31. (let ((,db *current-db*))
  32. (declare (ignorable ,db))
  33. (handler-case (progn ,@body)
  34. (sqlite:sqlite-error (e)
  35. (log:error (sqlite:sqlite-error-code e)
  36. (sqlite:sqlite-error-message e))
  37. (ignore-errors (sqlite:disconnect *current-db*))
  38. (setf *current-db* nil)
  39. (error e))))))
  40. (defmacro db-transaction (&body body)
  41. (let ((db (gensym "DB-")))
  42. `(with-db (,db)
  43. (sqlite:with-transaction ,db ,@body))))
  44. (defun db-execute (sql &rest parameters)
  45. (with-db (db)
  46. (apply #'sqlite:execute-non-query db sql parameters)
  47. (sqlite:last-insert-rowid db)))
  48. (defun db-select (sql &rest parameters)
  49. (with-db (db)
  50. (apply #'sqlite:execute-to-list db sql parameters)))
  51. (defun db-single (sql &rest parameters)
  52. (with-db (db)
  53. (apply #'sqlite:execute-single db sql parameters)))
  54. (defun db-init ()
  55. (with-db (db)
  56. (db-execute "create table if not exists settings (var, val)")
  57. (db-execute "create unique index if not exists settings_var_unique on settings (var)")
  58. (db-execute "create table if not exists lists (list, entry)")
  59. (db-execute "create unique index if not exists lists_unique on lists (list, entry)")))
  60. (defun load-settings ()
  61. (let ((*package* (find-package :chatikbot)))
  62. (loop for (var val) in (db-select "select var, val from settings")
  63. do (handler-case (setf (symbol-value (read-from-string var))
  64. (read-from-string val))
  65. (error (e) (log:error "~A" e))))))
  66. (defun set-setting (symbol value)
  67. (handler-case
  68. (let ((*package* (find-package :chatikbot)))
  69. (db-execute "replace into settings (var, val) values (?, ?)"
  70. (write-to-string symbol)
  71. (write-to-string value))
  72. (setf (symbol-value symbol) value))
  73. (error (e) (log:error e))))
  74. (defun lists-set-entry (list entry &optional (add t))
  75. (with-db (db)
  76. (handler-case
  77. (if add
  78. (db-execute "insert into lists (list, entry) values (?, ?)"
  79. (dekeyify list) entry)
  80. (db-execute "delete from lists where list = ? and entry = ?"
  81. (dekeyify list) entry))
  82. (sqlite:sqlite-constraint-error ()
  83. nil))))
  84. (defun lists-get (list)
  85. (with-db (db)
  86. (mapcar #'car (db-select "select entry from lists where list = ?" (dekeyify list)))))