Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

296 líneas
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))