update packages
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
(define-package "emacsql-sqlite" "20230225.2205" "EmacSQL back-end for SQLite"
|
||||
'((emacs "25.1")
|
||||
(emacsql "20230220"))
|
||||
:commit "b436adf09ebe058c28e0f473bed90ccd7084f6aa" :authors
|
||||
'(("Christopher Wellons" . "wellons@nullprogram.com"))
|
||||
(define-package "emacsql-sqlite" "20250223.1743" "Code used by multiple SQLite back-ends" 'nil :commit "e4f1dcae91f91c5fa6dc1b0097a6c524e98fdf2b" :authors
|
||||
'(("Jonas Bernoulli" . "emacs.emacsql@jonas.bernoulli.dev"))
|
||||
:maintainers
|
||||
'(("Jonas Bernoulli" . "jonas@bernoul.li"))
|
||||
'(("Jonas Bernoulli" . "emacs.emacsql@jonas.bernoulli.dev"))
|
||||
:maintainer
|
||||
'("Jonas Bernoulli" . "jonas@bernoul.li")
|
||||
:url "https://github.com/magit/emacsql")
|
||||
'("Jonas Bernoulli" . "emacs.emacsql@jonas.bernoulli.dev"))
|
||||
;; Local Variables:
|
||||
;; no-byte-compile: t
|
||||
;; End:
|
||||
|
||||
@@ -1,182 +1,295 @@
|
||||
;;; emacsql-sqlite.el --- EmacSQL back-end for SQLite -*- lexical-binding:t -*-
|
||||
;;; emacsql-sqlite.el --- Code used by multiple SQLite back-ends -*- lexical-binding:t -*-
|
||||
|
||||
;; This is free and unencumbered software released into the public domain.
|
||||
|
||||
;; Author: Christopher Wellons <wellons@nullprogram.com>
|
||||
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
|
||||
;; Homepage: https://github.com/magit/emacsql
|
||||
;; Author: Jonas Bernoulli <emacs.emacsql@jonas.bernoulli.dev>
|
||||
;; Maintainer: Jonas Bernoulli <emacs.emacsql@jonas.bernoulli.dev>
|
||||
|
||||
;; Package-Version: 3.1.1.50-git
|
||||
;; Package-Requires: ((emacs "25.1") (emacsql "20230220"))
|
||||
;; SPDX-License-Identifier: Unlicense
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This library provides the original EmacSQL back-end for SQLite,
|
||||
;; which uses a custom binary for communicating with a SQLite database.
|
||||
|
||||
;; During package installation an attempt is made to compile the binary.
|
||||
;; This library contains code that is used by multiple SQLite back-ends.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'emacsql)
|
||||
(require 'emacsql-sqlite-common)
|
||||
|
||||
(emacsql-register-reserved emacsql-sqlite-reserved)
|
||||
;;; Base class
|
||||
|
||||
;;; SQLite connection
|
||||
(defclass emacsql--sqlite-base (emacsql-connection)
|
||||
((file :initarg :file
|
||||
:initform nil
|
||||
:type (or null string)
|
||||
:documentation "Database file name.")
|
||||
(types :allocation :class
|
||||
:reader emacsql-types
|
||||
:initform '((integer "INTEGER")
|
||||
(float "REAL")
|
||||
(object "TEXT")
|
||||
(nil nil))))
|
||||
:abstract t)
|
||||
|
||||
(defvar emacsql-sqlite-data-root
|
||||
(file-name-directory (or load-file-name buffer-file-name))
|
||||
"Directory where EmacSQL is installed.")
|
||||
;;; Constants
|
||||
|
||||
(defvar emacsql-sqlite-executable-path
|
||||
(if (memq system-type '(windows-nt cygwin ms-dos))
|
||||
"sqlite/emacsql-sqlite.exe"
|
||||
"sqlite/emacsql-sqlite")
|
||||
"Relative path to emacsql executable.")
|
||||
(defconst emacsql-sqlite-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.
|
||||
Also see http://www.sqlite.org/lang_keywords.html.")
|
||||
|
||||
(defvar emacsql-sqlite-executable
|
||||
(expand-file-name emacsql-sqlite-executable-path
|
||||
(if (or (file-writable-p emacsql-sqlite-data-root)
|
||||
(file-exists-p (expand-file-name
|
||||
emacsql-sqlite-executable-path
|
||||
emacsql-sqlite-data-root)))
|
||||
emacsql-sqlite-data-root
|
||||
(expand-file-name
|
||||
(concat "emacsql/" emacsql-version)
|
||||
user-emacs-directory)))
|
||||
"Path to the EmacSQL backend (this is not the sqlite3 shell).")
|
||||
(defconst emacsql-sqlite-error-codes
|
||||
'((1 SQLITE_ERROR emacsql-error "SQL logic error")
|
||||
(2 SQLITE_INTERNAL emacsql-internal nil)
|
||||
(3 SQLITE_PERM emacsql-access "access permission denied")
|
||||
(4 SQLITE_ABORT emacsql-error "query aborted")
|
||||
(5 SQLITE_BUSY emacsql-locked "database is locked")
|
||||
(6 SQLITE_LOCKED emacsql-locked "database table is locked")
|
||||
(7 SQLITE_NOMEM emacsql-memory "out of memory")
|
||||
(8 SQLITE_READONLY emacsql-access "attempt to write a readonly database")
|
||||
(9 SQLITE_INTERRUPT emacsql-error "interrupted")
|
||||
(10 SQLITE_IOERR emacsql-access "disk I/O error")
|
||||
(11 SQLITE_CORRUPT emacsql-corruption "database disk image is malformed")
|
||||
(12 SQLITE_NOTFOUND emacsql-error "unknown operation")
|
||||
(13 SQLITE_FULL emacsql-access "database or disk is full")
|
||||
(14 SQLITE_CANTOPEN emacsql-access "unable to open database file")
|
||||
(15 SQLITE_PROTOCOL emacsql-access "locking protocol")
|
||||
(16 SQLITE_EMPTY emacsql-corruption nil)
|
||||
(17 SQLITE_SCHEMA emacsql-error "database schema has changed")
|
||||
(18 SQLITE_TOOBIG emacsql-error "string or blob too big")
|
||||
(19 SQLITE_CONSTRAINT emacsql-constraint "constraint failed")
|
||||
(20 SQLITE_MISMATCH emacsql-error "datatype mismatch")
|
||||
(21 SQLITE_MISUSE emacsql-error "bad parameter or other API misuse")
|
||||
(22 SQLITE_NOLFS emacsql-error "large file support is disabled")
|
||||
(23 SQLITE_AUTH emacsql-access "authorization denied")
|
||||
(24 SQLITE_FORMAT emacsql-corruption nil)
|
||||
(25 SQLITE_RANGE emacsql-error "column index out of range")
|
||||
(26 SQLITE_NOTADB emacsql-corruption "file is not a database")
|
||||
(27 SQLITE_NOTICE emacsql-warning "notification message")
|
||||
(28 SQLITE_WARNING emacsql-warning "warning message"))
|
||||
"Alist mapping SQLite error codes to EmacSQL conditions.
|
||||
Elements have the form (ERRCODE SYMBOLIC-NAME EMACSQL-ERROR
|
||||
ERRSTR). Also see https://www.sqlite.org/rescode.html.")
|
||||
|
||||
(defvar emacsql-sqlite-c-compilers '("cc" "gcc" "clang")
|
||||
"List of names to try when searching for a C compiler.
|
||||
;;; Variables
|
||||
|
||||
Each is queried using `executable-find', so full paths are
|
||||
allowed. Only the first compiler which is successfully found will
|
||||
used.")
|
||||
(defvar emacsql-include-header nil
|
||||
"Whether to include names of columns as an additional row.
|
||||
Never enable this globally, only let-bind it around calls to `emacsql'.
|
||||
Currently only supported by `emacsql-sqlite-builtin-connection' and
|
||||
`emacsql-sqlite-module-connection'.")
|
||||
|
||||
(defclass emacsql-sqlite-connection
|
||||
(emacsql--sqlite-base emacsql-protocol-mixin) ()
|
||||
"A connection to a SQLite database.")
|
||||
(defvar emacsql-sqlite-busy-timeout 20
|
||||
"Seconds to wait when trying to access a table blocked by another process.
|
||||
See https://www.sqlite.org/c3ref/busy_timeout.html.")
|
||||
|
||||
(cl-defmethod initialize-instance :after
|
||||
((connection emacsql-sqlite-connection) &rest _rest)
|
||||
(emacsql-sqlite-ensure-binary)
|
||||
(let* ((process-connection-type nil) ; use a pipe
|
||||
;; See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=60872#11.
|
||||
(coding-system-for-write 'utf-8)
|
||||
(coding-system-for-read 'utf-8)
|
||||
(file (slot-value connection 'file))
|
||||
(buffer (generate-new-buffer " *emacsql-sqlite*"))
|
||||
(fullfile (if file (expand-file-name file) ":memory:"))
|
||||
(process (start-process
|
||||
"emacsql-sqlite" buffer emacsql-sqlite-executable fullfile)))
|
||||
(oset connection handle process)
|
||||
(set-process-sentinel process
|
||||
(lambda (proc _) (kill-buffer (process-buffer proc))))
|
||||
(when (memq (process-status process) '(exit signal))
|
||||
(error "%s has failed immediately" emacsql-sqlite-executable))
|
||||
(emacsql-wait connection)
|
||||
(emacsql connection [:pragma (= busy-timeout $s1)]
|
||||
(/ (* emacsql-global-timeout 1000) 2))
|
||||
(emacsql-register connection)))
|
||||
;;; Utilities
|
||||
|
||||
(cl-defun emacsql-sqlite (file &key debug)
|
||||
"Open a connected to database stored in FILE.
|
||||
If FILE is nil use an in-memory database.
|
||||
(defun emacsql-sqlite-connection (variable file &optional setup use-module)
|
||||
"Return the connection stored in VARIABLE to the database in FILE.
|
||||
|
||||
:debug LOG -- When non-nil, log all SQLite commands to a log
|
||||
buffer. This is for debugging purposes."
|
||||
(let ((connection (make-instance 'emacsql-sqlite-connection :file file)))
|
||||
(set-process-query-on-exit-flag (oref connection handle) nil)
|
||||
If the value of VARIABLE is a live database connection, return that.
|
||||
|
||||
Otherwise open a new connection to the database in FILE and store the
|
||||
connection in VARIABLE, before returning it. If FILE is nil, use an
|
||||
in-memory database. Always enable support for foreign key constrains.
|
||||
If optional SETUP is non-nil, it must be a function, which takes the
|
||||
connection as only argument. This function can be used to initialize
|
||||
tables, for example.
|
||||
|
||||
If optional USE-MODULE is non-nil, then use the external module even
|
||||
when Emacs was built with SQLite support. This is intended for testing
|
||||
purposes."
|
||||
(or (let ((connection (symbol-value variable)))
|
||||
(and connection (emacsql-live-p connection) connection))
|
||||
(set variable (emacsql-sqlite-open file nil setup use-module))))
|
||||
|
||||
(defun emacsql-sqlite-open (file &optional debug setup use-module)
|
||||
"Open a connection to the database stored in FILE using an SQLite back-end.
|
||||
|
||||
Automatically use the best available back-end, as returned by
|
||||
`emacsql-sqlite-default-connection'.
|
||||
|
||||
If FILE is nil, use an in-memory database. If optional DEBUG is
|
||||
non-nil, log all SQLite commands to a log buffer, for debugging
|
||||
purposes. Always enable support for foreign key constrains.
|
||||
|
||||
If optional SETUP is non-nil, it must be a function, which takes the
|
||||
connection as only argument. This function can be used to initialize
|
||||
tables, for example.
|
||||
|
||||
If optional USE-MODULE is non-nil, then use the external module even
|
||||
when Emacs was built with SQLite support. This is intended for testing
|
||||
purposes."
|
||||
(when file
|
||||
(make-directory (file-name-directory file) t))
|
||||
(let* ((class (emacsql-sqlite-default-connection use-module))
|
||||
(connection (make-instance class :file file)))
|
||||
(when debug
|
||||
(emacsql-enable-debugging connection))
|
||||
(emacsql connection [:pragma (= foreign-keys on)])
|
||||
(when setup
|
||||
(funcall setup connection))
|
||||
connection))
|
||||
|
||||
(cl-defmethod emacsql-close ((connection emacsql-sqlite-connection))
|
||||
"Gracefully exits the SQLite subprocess."
|
||||
(let ((process (oref connection handle)))
|
||||
(when (process-live-p process)
|
||||
(process-send-eof process))))
|
||||
(defun emacsql-sqlite-default-connection (&optional use-module)
|
||||
"Determine and return the best SQLite connection class.
|
||||
|
||||
(cl-defmethod emacsql-send-message ((connection emacsql-sqlite-connection) message)
|
||||
(let ((process (oref connection handle)))
|
||||
(process-send-string process (format "%d " (string-bytes message)))
|
||||
(process-send-string process message)
|
||||
(process-send-string process "\n")))
|
||||
Signal an error if none of the connection classes can be used.
|
||||
|
||||
(cl-defmethod emacsql-handle ((_ emacsql-sqlite-connection) errcode errmsg)
|
||||
"Get condition for ERRCODE and ERRMSG provided from SQLite."
|
||||
(pcase-let ((`(,_ ,_ ,signal ,errstr)
|
||||
(assq errcode emacsql-sqlite-error-codes)))
|
||||
(signal (or signal 'emacsql-error)
|
||||
(list errmsg errcode nil errstr))))
|
||||
If optional USE-MODULE is non-nil, then use the external module even
|
||||
when Emacs was built with SQLite support. This is intended for testing
|
||||
purposes."
|
||||
(or (and (not use-module)
|
||||
(fboundp 'sqlite-available-p)
|
||||
(sqlite-available-p)
|
||||
(require 'emacsql-sqlite-builtin)
|
||||
'emacsql-sqlite-builtin-connection)
|
||||
(and (boundp 'module-file-suffix)
|
||||
module-file-suffix
|
||||
(condition-case nil
|
||||
;; Failure modes:
|
||||
;; 1. `libsqlite' shared library isn't available.
|
||||
;; 2. User chooses to not compile `libsqlite'.
|
||||
;; 3. `libsqlite' compilation fails.
|
||||
(and (require 'sqlite3)
|
||||
(require 'emacsql-sqlite-module)
|
||||
'emacsql-sqlite-module-connection)
|
||||
(error
|
||||
(display-warning 'emacsql "\
|
||||
Since your Emacs does not come with
|
||||
built-in SQLite support [1], but does support C modules, we can
|
||||
use an EmacSQL backend that relies on the third-party `sqlite3'
|
||||
package [2].
|
||||
|
||||
;;; SQLite compilation
|
||||
Please install the `sqlite3' Elisp package using your preferred
|
||||
Emacs package manager, and install the SQLite shared library
|
||||
using your distribution's package manager. That package should
|
||||
be named something like `libsqlite3' [3] and NOT just `sqlite3'.
|
||||
|
||||
(defun emacsql-sqlite-compile-switches ()
|
||||
"Return the compilation switches from the Makefile under sqlite/."
|
||||
(let ((makefile (expand-file-name "sqlite/Makefile" emacsql-sqlite-data-root))
|
||||
(case-fold-search nil))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents makefile)
|
||||
(goto-char (point-min))
|
||||
(cl-loop while (re-search-forward "-D[A-Z0-9_=]+" nil :no-error)
|
||||
collect (match-string 0)))))
|
||||
The legacy backend, which uses a custom SQLite executable, has
|
||||
been remove, so we can no longer fall back to that.
|
||||
|
||||
(defun emacsql-sqlite-compile (&optional o-level async error)
|
||||
"Compile the SQLite back-end for EmacSQL, returning non-nil on success.
|
||||
If called with non-nil ASYNC, the return value is meaningless.
|
||||
If called with non-nil ERROR, signal an error on failure."
|
||||
(let* ((cc (cl-loop for option in emacsql-sqlite-c-compilers
|
||||
for path = (executable-find option)
|
||||
if path return it))
|
||||
(src (expand-file-name "sqlite" emacsql-sqlite-data-root))
|
||||
(files (mapcar (lambda (f) (expand-file-name f src))
|
||||
'("sqlite3.c" "emacsql.c")))
|
||||
(cflags (list (format "-I%s" src) (format "-O%d" (or o-level 2))))
|
||||
(ldlibs (cl-case system-type
|
||||
(windows-nt (list))
|
||||
(berkeley-unix (list "-lm"))
|
||||
(otherwise (list "-lm" "-ldl"))))
|
||||
(options (emacsql-sqlite-compile-switches))
|
||||
(output (list "-o" emacsql-sqlite-executable))
|
||||
(arguments (nconc cflags options files ldlibs output)))
|
||||
[1]: Supported since Emacs 29.1, provided it was not disabled
|
||||
with `--without-sqlite3'.
|
||||
[2]: https://github.com/pekingduck/emacs-sqlite3-api
|
||||
[3]: On Debian https://packages.debian.org/buster/libsqlite3-0")
|
||||
;; The buffer displaying the warning might immediately
|
||||
;; be replaced by another buffer, before the user gets
|
||||
;; a chance to see it. We cannot have that.
|
||||
(let (fn)
|
||||
(setq fn (lambda ()
|
||||
(remove-hook 'post-command-hook fn)
|
||||
(pop-to-buffer (get-buffer "*Warnings*"))))
|
||||
(add-hook 'post-command-hook fn))
|
||||
nil)))
|
||||
(error "EmacSQL could not find or compile a back-end")))
|
||||
|
||||
(defun emacsql-sqlite-set-busy-timeout (connection)
|
||||
(when emacsql-sqlite-busy-timeout
|
||||
(emacsql connection [:pragma (= busy-timeout $s1)]
|
||||
(* emacsql-sqlite-busy-timeout 1000))))
|
||||
|
||||
(defun emacsql-sqlite-read-column (string)
|
||||
(let ((value nil)
|
||||
(beg 0)
|
||||
(end (length string)))
|
||||
(while (< beg end)
|
||||
(let ((v (read-from-string string beg)))
|
||||
(push (car v) value)
|
||||
(setq beg (cdr v))))
|
||||
(nreverse value)))
|
||||
|
||||
(defun emacsql-sqlite-list-tables (connection)
|
||||
"Return a list of symbols identifying tables in CONNECTION.
|
||||
Tables whose names begin with \"sqlite_\", are not included
|
||||
in the returned value."
|
||||
(mapcar #'car
|
||||
(emacsql connection
|
||||
[:select name
|
||||
;; The new name is `sqlite-schema', but this name
|
||||
;; is supported by old and new SQLite versions.
|
||||
;; See https://www.sqlite.org/schematab.html.
|
||||
:from sqlite-master
|
||||
:where (and (= type 'table)
|
||||
(not-like name "sqlite_%"))
|
||||
:order-by [(asc name)]])))
|
||||
|
||||
(defun emacsql-sqlite-dump-database (connection &optional versionp)
|
||||
"Dump the database specified by CONNECTION to a file.
|
||||
|
||||
The dump file is placed in the same directory as the database
|
||||
file and its name derives from the name of the database file.
|
||||
The suffix is replaced with \".sql\" and if optional VERSIONP is
|
||||
non-nil, then the database version (the `user_version' pragma)
|
||||
and a timestamp are appended to the file name.
|
||||
|
||||
Dumping is done using the official `sqlite3' binary. If that is
|
||||
not available and VERSIONP is non-nil, then the database file is
|
||||
copied instead."
|
||||
(let* ((version (caar (emacsql connection [:pragma user-version])))
|
||||
(db (oref connection file))
|
||||
(db (if (symbolp db) (symbol-value db) db))
|
||||
(name (file-name-nondirectory db))
|
||||
(output (concat (file-name-sans-extension db)
|
||||
(and versionp
|
||||
(concat (format "-v%s" version)
|
||||
(format-time-string "-%Y%m%d-%H%M")))
|
||||
".sql")))
|
||||
(cond
|
||||
((not cc)
|
||||
(funcall (if error #'error #'message)
|
||||
"Could not find C compiler, skipping SQLite build")
|
||||
nil)
|
||||
(t
|
||||
(message "Compiling EmacSQL SQLite binary...")
|
||||
(mkdir (file-name-directory emacsql-sqlite-executable) t)
|
||||
(let ((log (get-buffer-create byte-compile-log-buffer)))
|
||||
(with-current-buffer log
|
||||
(let ((inhibit-read-only t))
|
||||
(insert (mapconcat #'identity (cons cc arguments) " ") "\n")
|
||||
(let ((pos (point))
|
||||
(ret (apply #'call-process cc nil (if async 0 t) t
|
||||
arguments)))
|
||||
(cond
|
||||
((zerop ret)
|
||||
(message "Compiling EmacSQL SQLite binary...done")
|
||||
t)
|
||||
((and error (not async))
|
||||
(error "Cannot compile EmacSQL SQLite binary: %S"
|
||||
(replace-regexp-in-string
|
||||
"\n" " "
|
||||
(buffer-substring-no-properties
|
||||
pos (point-max))))))))))))))
|
||||
((locate-file "sqlite3" exec-path)
|
||||
(when (and (file-exists-p output) versionp)
|
||||
(error "Cannot dump database; %s already exists" output))
|
||||
(with-temp-file output
|
||||
(message "Dumping %s database to %s..." name output)
|
||||
(unless (zerop (save-excursion
|
||||
(call-process "sqlite3" nil t nil db ".dump")))
|
||||
(error "Failed to dump %s" db))
|
||||
(when version
|
||||
(insert (format "PRAGMA user_version=%s;\n" version)))
|
||||
;; The output contains "PRAGMA foreign_keys=OFF;".
|
||||
;; Change that to avoid alarming attentive users.
|
||||
(when (re-search-forward "^PRAGMA foreign_keys=\\(OFF\\);" 1000 t)
|
||||
(replace-match "ON" t t nil 1))
|
||||
(message "Dumping %s database to %s...done" name output)))
|
||||
(versionp
|
||||
(setq output (concat (file-name-sans-extension output) ".db"))
|
||||
(message "Cannot dump database because sqlite3 binary cannot be found")
|
||||
(when (and (file-exists-p output) versionp)
|
||||
(error "Cannot copy database; %s already exists" output))
|
||||
(message "Copying %s database to %s..." name output)
|
||||
(copy-file db output)
|
||||
(message "Copying %s database to %s...done" name output))
|
||||
((error "Cannot dump database; sqlite3 binary isn't available")))))
|
||||
|
||||
;;; Ensure the SQLite binary is available
|
||||
(defun emacsql-sqlite-restore-database (db dump)
|
||||
"Restore database DB from DUMP.
|
||||
|
||||
(defun emacsql-sqlite-ensure-binary ()
|
||||
"Ensure the EmacSQL SQLite binary is available, signaling an error if not."
|
||||
(unless (file-exists-p emacsql-sqlite-executable)
|
||||
;; Try compiling at the last minute.
|
||||
(condition-case err
|
||||
(emacsql-sqlite-compile 2 nil t)
|
||||
(error (error "No EmacSQL SQLite binary available: %s" (cdr err))))))
|
||||
DUMP is a file containing SQL statements. DB can be the file
|
||||
in which the database is to be stored, or it can be a database
|
||||
connection. In the latter case the current database is first
|
||||
dumped to a new file and the connection is closed. Then the
|
||||
database is restored from DUMP. No connection to the new
|
||||
database is created."
|
||||
(unless (stringp db)
|
||||
(emacsql-sqlite-dump-database db t)
|
||||
(emacsql-close (prog1 db (setq db (oref db file)))))
|
||||
(with-temp-buffer
|
||||
(unless (zerop (call-process "sqlite3" nil t nil db
|
||||
(format ".read %s" dump)))
|
||||
(error "Failed to read %s: %s" dump (buffer-string)))))
|
||||
|
||||
(provide 'emacsql-sqlite)
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
-include ../.config.mk
|
||||
|
||||
.POSIX:
|
||||
LDLIBS = -ldl -lm
|
||||
CFLAGS = -O2 -Wall -Wextra -Wno-implicit-fallthrough \
|
||||
-DSQLITE_THREADSAFE=0 \
|
||||
-DSQLITE_DEFAULT_FOREIGN_KEYS=1 \
|
||||
-DSQLITE_ENABLE_FTS5 \
|
||||
-DSQLITE_ENABLE_FTS4 \
|
||||
-DSQLITE_ENABLE_FTS3_PARENTHESIS \
|
||||
-DSQLITE_ENABLE_RTREE \
|
||||
-DSQLITE_ENABLE_JSON1 \
|
||||
-DSQLITE_SOUNDEX
|
||||
|
||||
emacsql-sqlite: emacsql.c sqlite3.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ emacsql.c sqlite3.c $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f emacsql-sqlite
|
||||
@@ -1,183 +0,0 @@
|
||||
/* This is free and unencumbered software released into the public domain. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "sqlite3.h"
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
char* escape(const char *message) {
|
||||
int i, count = 0, length_orig = strlen(message);
|
||||
for (i = 0; i < length_orig; i++) {
|
||||
if (strchr("\"\\", message[i])) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
char *copy = malloc(length_orig + count + 1);
|
||||
char *p = copy;
|
||||
while (*message) {
|
||||
if (strchr("\"\\", *message)) {
|
||||
*p = '\\';
|
||||
p++;
|
||||
}
|
||||
*p = *message;
|
||||
message++;
|
||||
p++;
|
||||
}
|
||||
*p = '\0';
|
||||
return copy;
|
||||
}
|
||||
|
||||
void send_error(int code, const char *message) {
|
||||
char *escaped = escape(message);
|
||||
printf("error %d \"%s\"\n", code, escaped);
|
||||
free(escaped);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char *buffer;
|
||||
size_t size;
|
||||
} buffer;
|
||||
|
||||
buffer* buffer_create() {
|
||||
buffer *buffer = malloc(sizeof(*buffer));
|
||||
buffer->size = 4096;
|
||||
buffer->buffer = malloc(buffer->size * sizeof(char));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int buffer_grow(buffer *buffer) {
|
||||
unsigned factor = 2;
|
||||
char *newbuffer = realloc(buffer->buffer, buffer->size * factor);
|
||||
if (newbuffer == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
buffer->buffer = newbuffer;
|
||||
buffer->size *= factor;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int buffer_read(buffer *buffer, size_t count) {
|
||||
while (buffer->size < count + 1) {
|
||||
if (buffer_grow(buffer) == FALSE) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
size_t in = fread((void *) buffer->buffer, 1, count, stdin);
|
||||
buffer->buffer[count] = '\0';
|
||||
return in == count;
|
||||
}
|
||||
|
||||
void buffer_free(buffer *buffer) {
|
||||
free(buffer->buffer);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
char *file = NULL;
|
||||
if (argc != 2) {
|
||||
fprintf(stderr,
|
||||
"error: require exactly one argument, the DB filename\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
file = argv[1];
|
||||
}
|
||||
|
||||
/* On Windows stderr is not always unbuffered. */
|
||||
#if defined(_WIN32) || defined(WIN32) || defined(__MINGW32__)
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
#endif
|
||||
|
||||
sqlite3* db = NULL;
|
||||
if (sqlite3_initialize() != SQLITE_OK) {
|
||||
fprintf(stderr, "error: failed to initialize sqlite\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
||||
if (sqlite3_open_v2(file, &db, flags, NULL) != SQLITE_OK) {
|
||||
fprintf(stderr, "error: failed to open %s\n", file);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
buffer *input = buffer_create();
|
||||
while (TRUE) {
|
||||
printf("#\n");
|
||||
fflush(stdout);
|
||||
|
||||
/* Gather input from Emacs. */
|
||||
unsigned length;
|
||||
int result = scanf("%u ", &length);
|
||||
if (result == EOF) {
|
||||
break;
|
||||
} else if (result != 1) {
|
||||
send_error(SQLITE_ERROR, "middleware parsing error");
|
||||
break; /* stream out of sync: quit program */
|
||||
}
|
||||
if (!buffer_read(input, length)) {
|
||||
send_error(SQLITE_NOMEM, "middleware out of memory");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Parse SQL statement. */
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
result = sqlite3_prepare_v2(db, input->buffer, length, &stmt, NULL);
|
||||
if (result != SQLITE_OK) {
|
||||
send_error(sqlite3_errcode(db), sqlite3_errmsg(db));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Print out rows. */
|
||||
int first = TRUE, ncolumns = sqlite3_column_count(stmt);
|
||||
printf("(");
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
if (first) {
|
||||
printf("(");
|
||||
first = FALSE;
|
||||
} else {
|
||||
printf("\n (");
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < ncolumns; i++) {
|
||||
if (i > 0) {
|
||||
printf(" ");
|
||||
}
|
||||
int type = sqlite3_column_type(stmt, i);
|
||||
switch (type) {
|
||||
case SQLITE_INTEGER:
|
||||
printf("%lld", sqlite3_column_int64(stmt, i));
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
printf("%f", sqlite3_column_double(stmt, i));
|
||||
break;
|
||||
case SQLITE_NULL:
|
||||
printf("nil");
|
||||
break;
|
||||
case SQLITE_TEXT:
|
||||
fwrite(sqlite3_column_text(stmt, i), 1,
|
||||
sqlite3_column_bytes(stmt, i), stdout);
|
||||
break;
|
||||
case SQLITE_BLOB:
|
||||
printf("nil");
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf(")");
|
||||
}
|
||||
printf(")\n");
|
||||
if (sqlite3_finalize(stmt) != SQLITE_OK) {
|
||||
/* Despite any error code, the statement is still freed.
|
||||
* http://stackoverflow.com/a/8391872
|
||||
*/
|
||||
send_error(sqlite3_errcode(db), sqlite3_errmsg(db));
|
||||
} else {
|
||||
printf("success\n");
|
||||
}
|
||||
}
|
||||
buffer_free(input);
|
||||
|
||||
sqlite3_close(db);
|
||||
sqlite3_shutdown();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user