1
0

db.lisp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. (in-package #:chatikbot)
  2. (defvar *db-name* "db.sqlite" "SQLite database name")
  3. (defun db-path ()
  4. (merge-pathnames *db-name*
  5. (asdf:component-pathname
  6. (asdf:find-system '#:chatikbot))))
  7. (defmacro with-db ((db) &body body)
  8. `(sqlite:with-open-database (,db (db-path) :busy-timeout 10)
  9. (sqlite:execute-non-query ,db "PRAGMA foreign_keys = ON")
  10. ,@body))
  11. (defun db-init ()
  12. (with-db (db)
  13. ;; Finance
  14. (sqlite:execute-non-query db "create table if not exists finance (ts, usd, eur, gbp, brent)")
  15. (sqlite:execute-non-query db "create index if not exists fin_ts_ids on finance (ts)")
  16. ;; Foursquare
  17. (sqlite:execute-non-query db "create table if not exists fsq_chat_users (chat_id, user_id)")
  18. (sqlite:execute-non-query db "create index if not exists fsq_chat_users_chat_idx on fsq_chat_users (chat_id)")
  19. (sqlite:execute-non-query db "create index if not exists fsq_chat_users_user_idx on fsq_chat_users (user_id)")
  20. (sqlite:execute-non-query db "create table if not exists fsq_seen (id, created_at)")
  21. (sqlite:execute-non-query db "create index if not exists fsq_seen_idx on fsq_seen (id)")
  22. ;; RSS
  23. (sqlite:execute-non-query db "create table if not exists rss_feeds (id INTEGER PRIMARY KEY, url, title, next_fetch, period)")
  24. (sqlite:execute-non-query db "create unique index if not exists rss_feeds_url_idx on rss_feeds (url)")
  25. (sqlite:execute-non-query db "create table if not exists rss_items (id INTEGER PRIMARY KEY, feed_id REFERENCES rss_feeds, guid, link, title, published)")
  26. (sqlite:execute-non-query db "create index if not exists rss_items_idx on rss_items (feed_id, guid)")
  27. (sqlite:execute-non-query db "create table if not exists rss_chat_feeds (chat_id, feed_id REFERENCES rss_feeds)")
  28. (sqlite:execute-non-query db "create index if not exists rss_chat_feeds_chat_idx on rss_chat_feeds (chat_id)")
  29. (sqlite:execute-non-query db "create index if not exists rss_chat_feeds_feed_idx on rss_chat_feeds (feed_id)")))
  30. ;; Finance
  31. (defun db-add-finance (ts usd eur gbp brent)
  32. (with-db (db)
  33. (sqlite:execute-non-query db "insert into finance (ts, usd, eur, gbp, brent) values (?, ?, ?, ?, ?)"
  34. ts usd eur gbp brent)))
  35. (defun db-get-last-finance ()
  36. (with-db (db)
  37. (sqlite:execute-one-row-m-v db "select ts, usd, eur, gbp, brent from finance order by ts desc limit 1")))
  38. (defun %finance-alist (statement)
  39. (let ((names (sqlite:statement-column-names statement))
  40. (map '(("usd" . "USD/RUB") ("eur" . "EUR/RUB") ("gbp" . "GBP/RUB") ("brent" . "Brent"))))
  41. (cons (sqlite:statement-column-value statement 0)
  42. (loop
  43. for i from 1 below (length names)
  44. for col in (rest names)
  45. collect (cons (aget col map) (sqlite:statement-column-value statement i))))))
  46. (defun db-get-series (after-ts &optional usd eur gbp brent (avg 60))
  47. (let ((sql (format nil
  48. "select ts/~a*~a~:[~;,avg(usd) as usd~]~:[~;,avg(eur) as eur~]~:[~;,avg(gbp) as gbp~]~:[~;,avg(brent) as brent~] from finance where ts >= ? group by ts/~a order by ts"
  49. avg avg usd eur gbp brent avg)))
  50. (with-db (db)
  51. (loop
  52. with statement = (sqlite:prepare-statement db sql)
  53. initially (sqlite:bind-parameter statement 1 (local-time:timestamp-to-unix after-ts))
  54. while (sqlite:step-statement statement)
  55. collect (%finance-alist statement)
  56. finally (sqlite:finalize-statement statement)))))
  57. ;; Foursquare
  58. (defun db-fsq-get-chat-users (chat-id)
  59. (flatten (with-db (db)
  60. (sqlite:execute-to-list db "select user_id from fsq_chat_users where chat_id = ?" chat-id))))
  61. (defun db-fsq-get-user-chats (user-id)
  62. (flatten (with-db (db)
  63. (sqlite:execute-to-list db "select chat_id from fsq_chat_users where user_id = ?" user-id))))
  64. (defun db-fsq-set-chat-users (chat-id users)
  65. (with-db (db)
  66. (sqlite:with-transaction db
  67. (sqlite:execute-non-query db "delete from fsq_chat_users where chat_id = ?" chat-id)
  68. (dolist (user-id users)
  69. (sqlite:execute-non-query db "insert into fsq_chat_users (chat_id, user_id) values (?, ?)" chat-id user-id)))))
  70. (defun db-fsq-add-seen (id created-at)
  71. (with-db (db)
  72. (sqlite:execute-non-query db "insert into fsq_seen (id, created_at) values (?, ?)" id created-at)))
  73. (defun db-fsq-has-seen (id)
  74. (with-db (db)
  75. (sqlite:execute-single db "select id from fsq_seen where id = ? limit 1" id)))
  76. (defun db-fsq-last-created ()
  77. (with-db (db)
  78. (sqlite:execute-single db "select created_at from fsq_seen order by created_at desc limit 1")))
  79. ;; RSS
  80. (defun %make-feed (row)
  81. (when row
  82. (make-feed :id (nth 0 row)
  83. :url (nth 1 row)
  84. :title (nth 2 row)
  85. :next-fetch (when (nth 3 row) (local-time:unix-to-timestamp (nth 3 row)))
  86. :period (nth 4 row))))
  87. (defun db-rss-get-feed-by-url (url)
  88. (with-db (db)
  89. (%make-feed (car (sqlite:execute-to-list db "select id, url, title, next_fetch, period from rss_feeds where url = ?" url)))))
  90. (defun db-rss-add-feed (feed)
  91. (with-db (db)
  92. (sqlite:execute-non-query db "insert into rss_feeds (url, title, next_fetch, period) values (?, ?, ?, ?)"
  93. (feed-url feed)
  94. (feed-title feed)
  95. (feed-next-fetch-unix feed)
  96. (feed-period feed))
  97. (setf (feed-id feed) (sqlite:last-insert-rowid db))
  98. feed))
  99. (defun db-rss-update-feed (feed)
  100. (with-db (db)
  101. (sqlite:execute-non-query db "update rss_feeds set title = ?, next_fetch = ?, period = ? where id = ?"
  102. (feed-title feed)
  103. (feed-next-fetch-unix feed)
  104. (feed-period feed)
  105. (feed-id feed))))
  106. (defun db-rss-get-active-feeds ()
  107. (with-db (db)
  108. (mapcar #'%make-feed
  109. (sqlite:execute-to-list db "select id, url, title, next_fetch, period from rss_feeds where exists (select 1 from rss_chat_feeds where feed_id=id)"))))
  110. (defun db-rss-get-chat-feeds (chat-id)
  111. (with-db (db)
  112. (mapcar #'%make-feed
  113. (sqlite:execute-to-list db "select id, url, title, next_fetch, period from rss_feeds where id in (select feed_id from rss_chat_feeds where chat_id = ?)" chat-id))))
  114. (defun db-rss-get-feed-chats (feed)
  115. (flatten (with-db (db)
  116. (sqlite:execute-to-list db "select chat_id from rss_chat_feeds where feed_id = ?"
  117. (feed-id feed)))))
  118. (defun db-rss-set-chat-feeds (chat-id feeds)
  119. (with-db (db)
  120. (sqlite:with-transaction db
  121. (sqlite:execute-non-query db "delete from rss_chat_feeds where chat_id = ?" chat-id)
  122. (dolist (feed feeds)
  123. (sqlite:execute-non-query db "insert into rss_chat_feeds (chat_id, feed_id) values (?, ?)" chat-id (feed-id feed))))))
  124. (defun db-rss-item-exists (item)
  125. (with-db (db)
  126. (sqlite:execute-single db "select id from rss_items where feed_id = ? and guid = ? limit 1"
  127. (feed-id (feed-item-feed item))
  128. (feed-item-guid item))))
  129. (defun db-rss-add-item (item)
  130. (with-db (db)
  131. (sqlite:execute-non-query db "insert into rss_items (feed_id, guid, link, title, published) values (?, ?, ?, ?, ?)"
  132. (feed-id (feed-item-feed item))
  133. (feed-item-guid item)
  134. (feed-item-link item)
  135. (feed-item-title item)
  136. (feed-item-published-unix item))))
  137. (defun %make-feed-item (feed row)
  138. (when row
  139. (make-feed-item :feed feed
  140. :guid (nth 0 row)
  141. :link (nth 1 row)
  142. :title (nth 2 row)
  143. :published (when (nth 3 row) (local-time:unix-to-timestamp (nth 3 row))))))
  144. (defun db-rss-last-feed-items (feed &optional (limit 10))
  145. (with-db (db)
  146. (mapcar #'(lambda (row) (%make-feed-item feed row))
  147. (sqlite:execute-to-list db "select guid, link, title, published from rss_items where feed_id = ? order by published desc, id desc limit ?" (feed-id feed) limit))))