-
Notifications
You must be signed in to change notification settings - Fork 23
/
emacsql-sqlite3.el
254 lines (205 loc) · 9.92 KB
/
emacsql-sqlite3.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
;;; emacsql-sqlite3.el --- Yet another EmacSQL backend for SQLite -*- lexical-binding: t -*-
;; Copyright (C) 2019 Zhu Zihao
;; Author: Zhu Zihao <[email protected]>
;; URL: https://github.com/cireu/emacsql-sqlite3
;; Version: 1.0.2
;; Package-Requires: ((emacs "26.1") (emacsql "3.0.0"))
;; Keywords: extensions
;; This file is NOT part of GNU Emacs.
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; For a full copy of the GNU General Public License
;; see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; [[https://melpa.org/#/emacsql-sqlite3][file:https://melpa.org/packages/emacsql-sqlite3-badge.svg]]
;; [[https://travis-ci.org/cireu/emacsql-sqlite3][file:https://travis-ci.org/cireu/emacsql-sqlite3.svg?branch=master]]
;; * Introduction
;; This is yet another [[https://github.com/skeeto/emacsql][EmacSQL]] backend for SQLite, which use official =sqlite3=
;; executable to access SQL database.
;; * Installation
;; =emacsql-sqlite3= is available on melpa.
;; * Usage
;; For Emacs users, please notice that this package won't provide any feature for
;; convenience. *If you don't sure you need this, you probably don't need this.*
;; For package developers, this package can be a replacement of [[https://github.com/skeeto/emacsql][emacsql-sqlite]] and
;; it doesn't require user to have a C compiler, but please read following
;; precautions.
;; - You need to install =sqlite3= official CLI tool, 3.8.2 version or above were
;; tested, =emacsql-sqlite3= may won't work if you using lower version.
;; - =sqlite3= CLI tool will load =~/.sqliterc= if presented, =emacsql-sqlite3=
;; will get undefined behaviour if any error occurred during the load progress.
;; It's better to keep this file empty.
;; - This package should be compatible with =emacsql-sqlite3= for most cases. But
;; foreign key support was disabled by default. To enable this feature, use
;; ~(emacsql <db> [:pragma (= foreign_keys ON)])~
;; The only entry point to a EmacSQL interface is =emacsql-sqlite3=, for more
;; information, please check [[https://github.com/skeeto/emacsql/blob/master/README.md][EmacSQL's README]].
;; * About Closql
;; [[https://github.com/emacscollective/closql][closql]] is using =emacsql-sqlite= as backend, you can use following code to force
;; closql use =emacsql-sqlite3=.
;; #+BEGIN_SRC emacs-lisp :results none
;; (with-eval-after-load 'closql
;; (defclass closql-database (emacsql-sqlite3-connection)
;; ((object-class :allocation :class))))
;; #+END_SRC
;; * Known issue
;; The tests don't pass under Emacs 25.1 for unknown reason, so we don't support
;; Emacs 25.1 currently like [[https://github.com/skeeto/emacsql][emacsql-sqlite]]. But any PR to improve this are
;; welcomed.
;;; Code:
(require 'cl-lib)
(require 'eieio)
(require 'emacsql)
(defconst emacsql-sqlite3--cmd-end-mark "#"
"The string should be printed when a command was executed.")
(defconst emacsql-sqlite3-reserved
(emacsql-register-reserved
'(ABORT ACTION ADD AFTER ALL ALTER ANALYZE AND AS ASC ATTACH
AUTOINCREMENT BEFORE BEGIN BETWEEN BY CASCADE CASE CAST CHECK
COLLATE COLUMN COMMIT CONFLICT CONSTRAINT CREATE CROSS
CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP DATABASE DEFAULT
DEFERRABLE DEFERRED DELETE DESC DETACH DISTINCT DROP EACH ELSE END
ESCAPE EXCEPT EXCLUSIVE EXISTS EXPLAIN FAIL FOR FOREIGN FROM FULL
GLOB GROUP HAVING IF IGNORE IMMEDIATE IN INDEX INDEXED INITIALLY
INNER INSERT INSTEAD INTERSECT INTO IS ISNULL JOIN KEY LEFT LIKE
LIMIT MATCH NATURAL NO NOT NOTNULL NULL OF OFFSET ON OR ORDER
OUTER PLAN PRAGMA PRIMARY QUERY RAISE RECURSIVE REFERENCES REGEXP
REINDEX RELEASE RENAME REPLACE RESTRICT RIGHT ROLLBACK ROW
SAVEPOINT SELECT SET TABLE TEMP TEMPORARY THEN TO TRANSACTION
TRIGGER UNION UNIQUE UPDATE USING VACUUM VALUES VIEW VIRTUAL WHEN
WHERE WITH WITHOUT))
"List of all of SQLite's reserved words.
https://www.sqlite.org/lang_keywords.html")
(defgroup emacsql-sqlite3 ()
"EmacsSQL, sqlite3 backend."
:group 'comm)
(defcustom emacsql-sqlite3-executable (executable-find "sqlite3")
"The path to sqlite3 executable should be used."
:group 'emacsql-sqlite3
:type 'file)
(defcustom emacsql-sqlite3-init-file null-device
"The path to the init file.
The init file can contain a mix of SQL statements and meta-commands.
When non-nil, it is passed to the init flag when starting the sqlite3 process."
:group 'emacsql-sqlite3
:type 'file)
(defclass emacsql-sqlite3-connection (emacsql-connection)
((file :initarg :file
:type (or null string)
:documentation "Database file name.")
(types :allocation :class
:reader emacsql-types
:initform '((integer "INTEGER")
(float "REAL")
(object "TEXT")
(nil nil))))
"A connection to a SQLite database, using official `sqlite3' executable.")
(defun emacsql-sqlite3--proc-sentinel (proc _change)
"Called each time when PROC status changed."
(unless (process-live-p proc)
(let ((buf (process-buffer proc)))
(when (buffer-live-p buf)
(kill-buffer buf)))))
(defun emacsql-sqlite3-run-dot-command (conn command &rest args)
"Format a dot-command with COMMAND and ARGS, then send it to CONN.
Sign: (-> `emacsql-sqlite3-connection' (U Sym Str) &rest (Listof Str) Nil)
COMMAND can be a symbol/keyword/string, which will be converted to string
if a keyword was presented, heading colon will be removed.
ARGS is list of strings which will be appended to command sequentially,
each arg will be quoted first."
(let* ((proc (emacsql-process conn))
(cmd-name (format ".%s" (if (keywordp command)
(substring (symbol-name command) 1)
command))))
(process-send-string proc (concat cmd-name " "
(mapconcat #'prin1-to-string args " ")
"\n"))
nil))
;;; Mandatory API
;; Some class don't call superclass's constructor!
(cl-defmethod initialize-instance :after
((conn emacsql-sqlite3-connection) _slots)
(cl-assert emacsql-sqlite3-executable nil
"Cannot find executable \"sqlite3\"!")
(let* ((file (oref conn file))
(fullfile (if file (list (expand-file-name file))))
(proc (make-process
:name "emacsql-sqlite3"
:command `(,emacsql-sqlite3-executable
"--batch"
;; Use space as separator,
;; which is convenient for `read'.
"--list" "--separator" " "
;; Obviously
"--nullvalue" "nil"
;; Don't return column headings
"--noheader"
;; pass init file if set
,@(when emacsql-sqlite3-init-file
`("--init" ,emacsql-sqlite3-init-file))
,@fullfile)
:buffer (generate-new-buffer " *emacsql sqlite*")
:noquery t
:connection-type 'pipe
:coding 'utf-8-auto
:sentinel #'emacsql-sqlite3--proc-sentinel)))
(setf (emacsql-process conn) proc)
(emacsql conn [:pragma (= busy-timeout $s1)]
(/ (* emacsql-global-timeout 1000) 2))
(emacsql-register conn)))
(cl-defmethod emacsql-waiting-p ((conn emacsql-sqlite3-connection))
(with-current-buffer (emacsql-buffer conn)
(save-excursion
(goto-char (point-max))
(re-search-backward (rx-to-string `(and ,emacsql-sqlite3--cmd-end-mark
"\n"
point)) nil t))))
(cl-defmethod emacsql-send-message ((conn emacsql-sqlite3-connection) message)
(let ((proc (emacsql-process conn)))
(process-send-string proc message)
(process-send-string proc "\n")
;; HACK: Print a fake prompt "#" after each command.
;; this is because some command don't echo when success.
;; We need a prompt to refer a command was executed or not.
(emacsql-sqlite3-run-dot-command conn :print "#")
nil))
(cl-defmethod emacsql-parse ((conn emacsql-sqlite3-connection))
(with-current-buffer (emacsql-buffer conn)
(goto-char (point-min))
;; https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1006666
;; SQLite 3.38 prints "Parse error" instead of "Error:"
(if (looking-at (rx (or "Error:" "Parse error ") (group (1+ any)) eol))
(signal 'emacsql-error (list (match-string 1)))
(cl-macrolet ((sexps-in-line! ()
`(cl-loop until (looking-at "\n")
collect (read (current-buffer)))))
(cl-loop
until (looking-at
(concat (regexp-quote emacsql-sqlite3--cmd-end-mark) "\n"))
collect (sexps-in-line!)
do (forward-char))))))
(cl-defmethod emacsql-close ((conn emacsql-sqlite3-connection))
(let ((process (emacsql-process conn)))
(when (process-live-p process)
(process-send-eof process))))
;;; Main entry
(cl-defun emacsql-sqlite3 (file &key debug)
"Open a connected to database stored in FILE.
If FILE is nil use an in-memory database.
If DEBUG is non-nil, log all SQLite commands to a log
buffer. This is for debugging purposes."
(let ((connection (make-instance 'emacsql-sqlite3-connection :file file)))
(when debug
(emacsql-enable-debugging connection))
connection))
(provide 'emacsql-sqlite3)
;; Local Variables:
;; coding: utf-8
;; End:
;;; emacsql-sqlite3.el ends here