Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

296 Zeilen
9.9KB

  1. #|
  2. src/db.lisp
  3. Database / connection skeleton generated by Caveman.
  4. Additional changes:
  5. WITH-DATAFLY-CONNECTION :: Same as WITH-CONNECTION, but we use
  6. DATAFLY:*CONNECTION* instead of MITO.CORE:*CONNECTION*.
  7. WITH-DATAFLY-TRANSACTION is the same.
  8. TODO: I would like to be able to specify :MITO or :DATAFLY.
  9. TODO Test this stuff
  10. DONE Caveman has a db module and dbi manager middleware - Looks
  11. useless. We already have CONNECT-CACHED in db.lisp.
  12. |#
  13. (in-package :cl-user)
  14. (defpackage #:cl-deck-builder2.db
  15. (:use #:cl)
  16. (:import-from #:cl-deck-builder2.config
  17. #:config)
  18. (:import-from #:cl-deck-builder2.toolkit
  19. #:grouped
  20. #:relative-pathname)
  21. (:import-from #:cl-dbi
  22. #:connect-cached)
  23. (:export #:connection-settings
  24. #:db
  25. #:with-connection
  26. #:with-transaction
  27. #:with-datafly-connection
  28. #:with-datafly-transaction
  29. #:with-includes
  30. #:do-grouped-insert
  31. #:count-dao
  32. #:create-dao
  33. #:delete-by-values
  34. #:delete-from
  35. #:delete-dao
  36. #:create-table
  37. #:drop-table
  38. #:recreate-table
  39. #:find-dao
  40. #:find-or-make-instance
  41. #:find-or-create-instance
  42. #:insert-dao
  43. #:max-dao
  44. #:retrieve-by-sql
  45. #:retrieve-dao
  46. #:save-dao
  47. #:select-dao
  48. #:update-dao)
  49. (:documentation "The database package.
  50. This is more than just the default generated by Caveman2 skeleton.
  51. It provides convenience functions for accessing database information and objects."))
  52. (in-package #:cl-deck-builder2.db)
  53. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  54. (defun connection-settings (&optional (db :maindb))
  55. "Query the Caveman configuration for these database settings. DB is the name of the configuration query, e.g. :MAINDB."
  56. (cdr (assoc db (config :databases))))
  57. (defun db (&optional (db :maindb))
  58. "Get a handle to an open database, possibly cached with CONNECT-CACHED."
  59. (apply #'connect-cached (connection-settings db)))
  60. (defmacro with-connection (conn &body body)
  61. "Wrap BODY using LEXICAL-LET to bind MITO.CORE:*CONNECTION* to CONN."
  62. ;; TODO
  63. ;; `(if (and (listp ,conn)
  64. ;; (eq (type-of ,(car conn)) 'cl-dbi::dbi-connection))
  65. ;; (let ((,(car conn) ,(cdr conn)))
  66. ;; ,@body))
  67. `(let ((mito.core:*connection* ,conn))
  68. ,@body))
  69. (defmacro with-transaction (&body body)
  70. "Wrap BODY with CL-DBI:WITH-TRANSACTION."
  71. `(cl-dbi:with-transaction mito.core:*connection*
  72. ,@body))
  73. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  74. ;; What's a better way to do this? DEFGENERIC?
  75. (defmacro with-datafly-connection (conn &body body)
  76. "Wrap BODY using LEXICAL-LET to bind DATAFLY:*CONNECTION* to CONN."
  77. `(let ((datafly:*connection* ,conn))
  78. ;; (datafly:*trace-sql* t))
  79. ,@body))
  80. (defmacro with-datafly-transaction (&body body)
  81. "Wrap BODY with CL-DBI:WITH-TRANSACTION. Datafly variant."
  82. `(cl-dbi:with-transaction datafly:*connection*
  83. ,@body))
  84. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  85. ;; I didn't realize that this implicitly only allows SELECT-DAO. I
  86. ;; guess in the MITO documentation he only allows SELECT-DAO to have
  87. ;; MITO:INCLUDES machinery. Where is that captured? For now, I assume
  88. ;; you know this, and that WITH-INCLUDES is only compatible with
  89. ;; SELECT-DAO anyway.
  90. (defmacro with-includes (class includes &body body)
  91. `(mito:select-dao ,class
  92. ,includes
  93. ,@body))
  94. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  95. ;; What happened here? It used to work now it's returning NIL. A LOOP
  96. ;; is nicer than a DOLIST+SETF but...
  97. (defun do-grouped-insert (obj &key (n 1000) (fn #'mito:insert-dao))
  98. "Batch INSERT-DAO to the database. Display a progress bar."
  99. (let ((pos 0)
  100. (width 50)
  101. (grouped (grouped obj n)))
  102. (with-connection (db)
  103. (loop for group in grouped
  104. collect
  105. (prog1
  106. (with-transaction
  107. (mapcan fn group))
  108. (format t ".")
  109. (if (>= pos (1- width))
  110. (progn
  111. (setf pos 0)
  112. (format t "~%"))
  113. (incf pos))
  114. (finish-output))))))
  115. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  116. ;; TODO FIND-DAO-OR-MAKE-INSTANCE?
  117. ;; TODO Maybe merge FIND-OR-MAKE-INSTANCE and FIND-OR-CREATE-INSTANCE
  118. (defun find-or-make-instance (class &rest args)
  119. "Wrap MITO:FIND-DAO and if no result is returned, issue MAKE-INSTANCE for CLASS."
  120. (with-connection (db)
  121. (with-transaction
  122. (or (apply #'mito:find-dao class args)
  123. (apply #'make-instance class args)))))
  124. (defun find-or-create-instance (class &rest args)
  125. "Wrap MITO:FIND-DAO and if no result is returned, issue CREATE-DAO for CLASS."
  126. (with-connection (db)
  127. (with-transaction
  128. (or (apply #'mito:find-dao class args)
  129. (apply #'mito:create-dao class args)))))
  130. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  131. ;; DAOs
  132. (defmacro select-dao (class &body body)
  133. "Select an object of CLASS from the database by wrapping MITO:SELECT-DAO in WITH-CONNECTION etc.
  134. Note that SELECT-DAO is a DEFMACRO form.
  135. You may additionally specify BODY parameters that are SXQL statements."
  136. `(with-connection (db)
  137. (with-transaction
  138. (mito:select-dao ,class ,@body))))
  139. (defun delete-from (class &rest clauses)
  140. "Delete entries for CLASS by wrapping SXQL:DELETE-FROM.
  141. You may additionally specify CLAUSES parameters that are SXQL statements."
  142. (with-connection (db)
  143. (with-transaction
  144. (mito:execute-sql
  145. (apply #'sxql:make-statement :delete-from
  146. (sxql:make-sql-symbol
  147. (mito.class:table-name (find-class class)))
  148. clauses)))))
  149. (defun count-dao (class &rest fields-and-values)
  150. "Count the number of entries matching this CLASS using FIELDS-AND-VALUES.
  151. Simply wrap MITO:COUNT-DAO in WITH-CONNECTION etc."
  152. (with-connection (db)
  153. (with-transaction
  154. (apply #'mito:count-dao class fields-and-values))))
  155. (defun create-dao (class &rest initargs)
  156. "Create an entry for this CLASS by wrapping MITO:CREATE-DAO in WITH-CONNECTION etc."
  157. (with-connection (db)
  158. (with-transaction
  159. (apply #'mito:create-dao class initargs))))
  160. (defun max-dao (class &rest fields-and-values &key (field :id))
  161. "Select the aggregate MAX from the SQL database and return the result, if any.
  162. You may additionally specify the FIELD to find the MAX by."
  163. (setf class (mito.util::ensure-class class))
  164. (let ((sql (sxql:select ((:as (:ifnull (:max field) 0) :max))
  165. (sxql:from (sxql:make-sql-symbol
  166. (mito.class:table-name class))))))
  167. (when fields-and-values
  168. (sxql:add-child sql (mito.dao::where-and fields-and-values class)))
  169. (getf (first
  170. (retrieve-by-sql sql))
  171. :max)))
  172. (defun delete-by-values (class &rest fields-and-values)
  173. "Delete an entry for this CLASS by wrapping MITO:DELETE-BY-VALUES.
  174. You may specify additional FIELDS-AND-VALUES to narrow your search."
  175. (with-connection (db)
  176. (with-transaction
  177. (apply #'mito:delete-by-values class fields-and-values))))
  178. (defun delete-dao (obj)
  179. (with-connection (db)
  180. (with-transaction
  181. (mito:delete-dao obj))))
  182. (defun find-dao (class &rest fields-and-values)
  183. "Find an entry for this CLASS, if it exists, by wrapping MITO:FIND-DAO.
  184. You may specify additional FIELDS-AND-VALUES to narrow your search."
  185. (with-connection (db)
  186. (with-transaction
  187. (apply #'mito:find-dao class fields-and-values))))
  188. (defun insert-dao (obj)
  189. "Insert an existing object OBJ into the database by wrapping MITO:INSERT-DAO in WITH-CONNECTION etc."
  190. (with-connection (db)
  191. (with-transaction
  192. (mito:insert-dao obj))))
  193. (defun save-dao (obj)
  194. "Save an existing object OBJ into the database by wrapping MITO:SAVE-DAO in WITH-CONNECTION etc."
  195. (with-connection (db)
  196. (with-transaction
  197. (mito:save-dao obj))))
  198. (defun update-dao (obj)
  199. "Update an existing object OBJ into the database by wrapping MITO:UPDATE-DAO in WITH-CONNECTION etc."
  200. (with-connection (db)
  201. (with-transaction
  202. (mito:update-dao obj))))
  203. (defun retrieve-by-sql (sql &key binds)
  204. "Retrieve a query from the database by wrapping MITO:RETRIEVE-BY-SQL in WITH-CONNECTION etc.
  205. I don't know what BINDS does. I think it has something to do with passing parameters to queries."
  206. (with-connection (db)
  207. (with-transaction
  208. (mito:retrieve-by-sql sql :binds binds))))
  209. (defun retrieve-dao (class &rest fields-and-values)
  210. "Retrieve a row for this CLASS, if it exists, by wrapping MITO:RETRIEVE-DAO.
  211. You may specify additional FIELDS-AND-VALUES to narrow your search."
  212. (with-connection (db)
  213. (with-transaction
  214. (apply #'mito:retrieve-dao class fields-and-values))))
  215. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  216. ;; Tables
  217. (defun create-table (&rest classes)
  218. "Create tables for CLASSES by wrapping MITO:ENSURE-TABLE-EXISTS.
  219. CLASSES may be a list of classes to create corresponding tables for."
  220. (with-connection (db)
  221. (with-transaction
  222. (mapcar (alexandria:compose #'mito:ensure-table-exists #'find-class)
  223. classes))))
  224. (defun drop-table (&rest classes)
  225. "Drop tables for CLASSES by wrapping SXQL:DROP-TABLE.
  226. CLASSES may be a list of classes to drop corresponding tables for."
  227. (with-connection (db)
  228. (with-transaction
  229. (mapcar (lambda (class)
  230. (mito:execute-sql
  231. (sxql:drop-table
  232. (sxql:make-sql-symbol
  233. (mito.class:table-name (find-class class)))
  234. :if-exists t)))
  235. classes))))
  236. (defun recreate-table (&rest classes)
  237. "DROP-TABLE then CREATE-TABLE for CLASSES."
  238. (mapcar #'drop-table classes)
  239. (mapcar #'create-table classes))