update packages

This commit is contained in:
2025-02-26 20:16:44 +01:00
parent 59db017445
commit 45d49daef0
291 changed files with 16240 additions and 522600 deletions

View File

@@ -1,17 +1,22 @@
;;; emacsql-compile.el --- S-expression SQL compiler -*- lexical-binding:t -*-
;;; emacsql-compiler.el --- S-expression SQL compiler -*- 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
;; Maintainer: Jonas Bernoulli <emacs.emacsql@jonas.bernoulli.dev>
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; This library provides support for compiling S-expressions to SQL.
;;; Code:
(require 'cl-lib)
(eval-when-compile (require 'subr-x))
;;; Error symbols
(defmacro emacsql-deferror (symbol parents message)
@@ -20,8 +25,8 @@
(let ((conditions (cl-remove-duplicates
(append parents (list symbol 'emacsql-error 'error)))))
`(prog1 ',symbol
(setf (get ',symbol 'error-conditions) ',conditions
(get ',symbol 'error-message) ,message))))
(put ',symbol 'error-conditions ',conditions)
(put ',symbol 'error-message ,message))))
(emacsql-deferror emacsql-error () ;; parent condition for all others
"EmacSQL had an unhandled condition")
@@ -121,7 +126,7 @@
(defun emacsql-escape-vector (vector)
"Encode VECTOR into a SQL vector scalar."
(cl-typecase vector
(null (emacsql-error "Empty SQL vector expression."))
(null (emacsql-error "Empty SQL vector expression"))
(list (mapconcat #'emacsql-escape-vector vector ", "))
(vector (concat "(" (mapconcat #'emacsql-escape-scalar vector ", ") ")"))
(otherwise (emacsql-error "Invalid vector %S" vector))))
@@ -145,7 +150,7 @@
(upcase (replace-regexp-in-string "-" " " name))))
(defun emacsql--prepare-constraints (constraints)
"Compile CONSTRAINTS into a partial SQL expresson."
"Compile CONSTRAINTS into a partial SQL expression."
(mapconcat
#'identity
(cl-loop for constraint in constraints collect
@@ -210,21 +215,21 @@
A parameter is a symbol that looks like $i1, $s2, $v3, etc. The
letter refers to the type: identifier (i), scalar (s),
vector (v), raw string (r), schema (S)."
(when (symbolp thing)
(let ((name (symbol-name thing)))
(when (string-match-p "^\\$[isvrS][0-9]+$" name)
(cons (1- (read (substring name 2)))
(cl-ecase (aref name 1)
(?i :identifier)
(?s :scalar)
(?v :vector)
(?r :raw)
(?S :schema)))))))
(and (symbolp thing)
(let ((name (symbol-name thing)))
(and (string-match-p "^\\$[isvrS][0-9]+$" name)
(cons (1- (read (substring name 2)))
(cl-ecase (aref name 1)
(?i :identifier)
(?s :scalar)
(?v :vector)
(?r :raw)
(?S :schema)))))))
(defmacro emacsql-with-params (prefix &rest body)
"Evaluate BODY, collecting parameters.
Provided local functions: `param', `identifier', `scalar', `raw',
`svector', `expr', `subsql', and `combine'. BODY should return a
`svector', `expr', `subsql', and `combine'. BODY should return a
string, which will be combined with variable definitions."
(declare (indent 1))
`(let ((emacsql--vars ()))
@@ -236,7 +241,7 @@ string, which will be combined with variable definitions."
(svector (thing) (combine (emacsql--*vector thing)))
(expr (thing) (combine (emacsql--*expr thing)))
(subsql (thing)
(format "(%s)" (combine (emacsql-prepare thing)))))
(format "(%s)" (combine (emacsql-prepare thing)))))
(cons (concat ,prefix (progn ,@body)) emacsql--vars))))
(defun emacsql--!param (thing &optional kind)
@@ -244,9 +249,9 @@ string, which will be combined with variable definitions."
If optional KIND is not specified, then try to guess it.
Only use within `emacsql-with-params'!"
(cl-flet ((check (param)
(when (and kind (not (eq kind (cdr param))))
(emacsql-error
"Invalid parameter type %s, expecting %s" thing kind))))
(when (and kind (not (eq kind (cdr param))))
(emacsql-error
"Invalid parameter type %s, expecting %s" thing kind))))
(let ((param (emacsql-param thing)))
(if (null param)
(emacsql-escape-format
@@ -264,7 +269,7 @@ Only use within `emacsql-with-params'!"
(emacsql-escape-scalar thing))))
(prog1 (if (eq (cdr param) :schema) "(%s)" "%s")
(check param)
(setf emacsql--vars (nconc emacsql--vars (list param))))))))
(setq emacsql--vars (nconc emacsql--vars (list param))))))))
(defun emacsql--*vector (vector)
"Prepare VECTOR."
@@ -275,23 +280,22 @@ Only use within `emacsql-with-params'!"
(vector (format "(%s)" (mapconcat #'scalar vector ", ")))
(otherwise (emacsql-error "Invalid vector: %S" vector)))))
(defmacro emacsql--generate-op-lookup-defun (name
operator-precedence-groups)
(defmacro emacsql--generate-op-lookup-defun (name operator-precedence-groups)
"Generate function to look up predefined SQL operator metadata.
The generated function is bound to NAME and accepts two
arguments, OPERATOR-NAME and OPERATOR-ARGUMENT-COUNT.
OPERATOR-PRECEDENCE-GROUPS should be a number of lists containing
operators grouped by operator precedence (in order of precedence
from highest to lowest). A single operator is represented by a
from highest to lowest). A single operator is represented by a
list of at least two elements: operator name (symbol) and
operator arity (:unary or :binary). Optionally a custom
operator arity (:unary or :binary). Optionally a custom
expression can be included, which defines how the operator is
expanded into an SQL expression (there are two defaults, one for
:unary and one for :binary operators).
An example for OPERATOR-PRECEDENCE-GROUPS:
(((+ :unary (\"+\" :operand)) (- :unary (\"-\" :operand)))
\(((+ :unary (\"+\" :operand)) (- :unary (\"-\" :operand)))
((+ :binary) (- :binary)))"
`(defun ,name (operator-name operator-argument-count)
"Look up predefined SQL operator metadata.
@@ -349,34 +353,34 @@ See `emacsql--generate-op-lookup-defun' for details."
"Create format-string for an SQL operator.
The format-string returned is intended to be used with `format'
to create an SQL expression."
(when expr
(cl-labels ((replace-operand (x) (if (eq x :operand)
"%s"
x))
(to-format-string (e) (mapconcat #'replace-operand e "")))
(cond
((and (eq arity :unary) (eql argument-count 1))
(to-format-string expr))
((and (eq arity :binary) (>= argument-count 2))
(let ((result (reverse expr)))
(dotimes (_ (- argument-count 2))
(setf result (nconc (reverse expr) (cdr result))))
(to-format-string (nreverse result))))
(t (emacsql-error "Wrong number of operands for %s" op))))))
(and expr
(cl-labels ((replace-operand (x) (if (eq x :operand) "%s" x))
(to-format-string (e) (mapconcat #'replace-operand e "")))
(cond
((and (eq arity :unary) (eql argument-count 1))
(to-format-string expr))
((and (eq arity :binary) (>= argument-count 2))
(let ((result (reverse expr)))
(dotimes (_ (- argument-count 2))
(setq result (nconc (reverse expr) (cdr result))))
(to-format-string (nreverse result))))
(t (emacsql-error "Wrong number of operands for %s" op))))))
(defun emacsql--get-op-info (op argument-count parent-precedence-value)
"Lookup SQL operator information for generating an SQL expression.
Returns the following multiple values when an operator can be
identified: a format string (see `emacsql--expand-format-string')
and a precedence value. If PARENT-PRECEDENCE-VALUE is greater or
and a precedence value. If PARENT-PRECEDENCE-VALUE is greater or
equal to the identified operator's precedence, then the format
string returned is wrapped with parentheses."
(cl-destructuring-bind (format-string arity precedence-value)
(emacsql--get-op op argument-count)
(let ((expanded-format-string (emacsql--expand-format-string op
format-string
arity
argument-count)))
(let ((expanded-format-string
(emacsql--expand-format-string
op
format-string
arity
argument-count)))
(cl-values (cond
((null format-string) nil)
((>= parent-precedence-value
@@ -397,10 +401,11 @@ string returned is wrapped with parentheses."
(emacsql--get-op-info op
(length args)
(or parent-precedence-value 0))
(cl-flet ((recur (n) (combine (emacsql--*expr (nth n args)
(or precedence-value 0))))
(cl-flet ((recur (n)
(combine (emacsql--*expr (nth n args)
(or precedence-value 0))))
(nops (op)
(emacsql-error "Wrong number of operands for %s" op)))
(emacsql-error "Wrong number of operands for %s" op)))
(cl-case op
;; Special cases <= >=
((<= >=)
@@ -466,7 +471,7 @@ string returned is wrapped with parentheses."
"Append parameters from PREPARED to `emacsql--vars', return the string.
Only use within `emacsql-with-params'!"
(cl-destructuring-bind (string . vars) prepared
(setf emacsql--vars (nconc emacsql--vars vars))
(setq emacsql--vars (nconc emacsql--vars vars))
string))
(defun emacsql-prepare--string (string)
@@ -506,9 +511,8 @@ Only use within `emacsql-with-params'!"
(emacsql-escape-format
(emacsql-escape-scalar item))))
into parts
do (setf last item)
finally (cl-return
(mapconcat #'identity parts " ")))))
do (setq last item)
finally (cl-return (string-join parts " ")))))
(defun emacsql-prepare (sql)
"Expand SQL (string or sexp) into a prepared statement."
@@ -539,4 +543,4 @@ Only use within `emacsql-with-params'!"
(provide 'emacsql-compiler)
;;; emacsql-compile.el ends here
;;; emacsql-compiler.el ends here

View File

@@ -3,11 +3,8 @@
;; 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
;; 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:
@@ -126,7 +123,7 @@ http://dev.mysql.com/doc/refman/5.5/en/reserved-words.html")
collect (read) into row
when (looking-at "\n")
collect row into rows
and do (setf row ())
and do (setq row ())
and do (forward-char)
finally (cl-return rows)))))

View File

@@ -3,17 +3,15 @@
;; 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
;; Maintainer: Jonas Bernoulli <emacs.emacsql@jonas.bernoulli.dev>
;; Package-Version: 3.1.1.50-git
;; Package-Requires: ((emacs "25.1") (emacsql "20230220") (pg "0.16"))
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; This library provides an EmacSQL back-end for PostgreSQL, which
;; uses the `pg' package to directly speak to the database.
;; uses the `pg' package to directly speak to the database. This
;; library requires at least Emacs 28.1.
;; (For an alternative back-end for PostgreSQL, see `emacsql-psql'.)
@@ -21,7 +19,9 @@
(require 'emacsql)
(require 'pg nil t)
(if (>= emacs-major-version 28)
(require 'pg nil t)
(message "emacsql-pg.el requires Emacs 28.1 or later"))
(declare-function pg-connect "pg"
( dbname user &optional
(password "") (host "localhost") (port 5432) (tls nil)))

View File

@@ -1,11 +1,11 @@
(define-package "emacsql" "20230417.1448" "High-level SQL database front-end"
'((emacs "25.1"))
:commit "64012261f65fcdd7ea137d1973ef051af1dced42" :authors
(define-package "emacsql" "20250223.1743" "High-level SQL database front-end"
'((emacs "26.1"))
:commit "e4f1dcae91f91c5fa6dc1b0097a6c524e98fdf2b" :authors
'(("Christopher Wellons" . "wellons@nullprogram.com"))
:maintainers
'(("Jonas Bernoulli" . "jonas@bernoul.li"))
'(("Jonas Bernoulli" . "emacs.emacsql@jonas.bernoulli.dev"))
:maintainer
'("Jonas Bernoulli" . "jonas@bernoul.li")
'("Jonas Bernoulli" . "emacs.emacsql@jonas.bernoulli.dev")
:url "https://github.com/magit/emacsql")
;; Local Variables:
;; no-byte-compile: t

View File

@@ -3,10 +3,8 @@
;; 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
;; Package-Version: 3.1.1.50-git
;; Package-Requires: ((emacs "25.1") (emacsql "20230220"))
;; Maintainer: Jonas Bernoulli <emacs.emacsql@jonas.bernoulli.dev>
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
@@ -77,7 +75,7 @@ http://www.postgresql.org/docs/7.3/static/sql-keywords-appendix.html")
(when hostname
(push "-h" args)
(push hostname args))
(setf args (nreverse args))
(setq args (nreverse args))
(let* ((buffer (generate-new-buffer " *emacsql-psql*"))
(psql emacsql-psql-executable)
(command (mapconcat #'shell-quote-argument (cons psql args) " "))
@@ -117,9 +115,9 @@ http://www.postgresql.org/docs/7.3/static/sql-keywords-appendix.html")
(cl-defmethod emacsql-waiting-p ((connection emacsql-psql-connection))
(with-current-buffer (emacsql-buffer connection)
(cond ((= (buffer-size) 1) (string= "]" (buffer-string)))
((> (buffer-size) 1) (string= "\n]"
(buffer-substring
(- (point-max) 2) (point-max)))))))
((> (buffer-size) 1) (string= "\n]" (buffer-substring
(- (point-max) 2)
(point-max)))))))
(cl-defmethod emacsql-check-error ((connection emacsql-psql-connection))
(with-current-buffer (emacsql-buffer connection)
@@ -139,7 +137,7 @@ http://www.postgresql.org/docs/7.3/static/sql-keywords-appendix.html")
collect (read) into row
when (looking-at "\n")
collect row into rows
and do (progn (forward-char 1) (setf row ()))
and do (progn (forward-char 1) (setq row ()))
finally (cl-return rows)))))
(provide 'emacsql-psql)

View File

@@ -2,11 +2,9 @@
;; This is free and unencumbered software released into the public domain.
;; Author: 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 "29") (emacsql "20230220"))
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
@@ -16,8 +14,7 @@
;;; Code:
(require 'emacsql)
(require 'emacsql-sqlite-common)
(require 'emacsql-sqlite)
(require 'sqlite nil t)
(declare-function sqlite-open "sqlite")
@@ -33,10 +30,8 @@
((connection emacsql-sqlite-builtin-connection) &rest _)
(require (quote sqlite))
(oset connection handle
(sqlite-open (slot-value connection 'file)))
(when emacsql-global-timeout
(emacsql connection [:pragma (= busy-timeout $s1)]
(/ (* emacsql-global-timeout 1000) 2)))
(sqlite-open (oref connection file)))
(emacsql-sqlite-set-busy-timeout connection)
(emacsql connection [:pragma (= foreign-keys on)])
(emacsql-register connection))
@@ -45,7 +40,7 @@
If FILE is nil use an in-memory database.
:debug LOG -- When non-nil, log all SQLite commands to a log
buffer. This is for debugging purposes."
buffer. This is for debugging purposes."
(let ((connection (make-instance #'emacsql-sqlite-builtin-connection
:file file)))
(when debug
@@ -62,14 +57,18 @@ buffer. This is for debugging purposes."
(cl-defmethod emacsql-send-message
((connection emacsql-sqlite-builtin-connection) message)
(condition-case err
(mapcar (lambda (row)
(mapcar (lambda (col)
(cond ((null col) nil)
((equal col "") "")
((numberp col) col)
(t (read col))))
row))
(sqlite-select (oref connection handle) message nil nil))
(let ((headerp emacsql-include-header))
(mapcar (lambda (row)
(cond
(headerp (setq headerp nil) row)
((mapcan (lambda (col)
(cond ((null col) (list nil))
((equal col "") (list ""))
((numberp col) (list col))
((emacsql-sqlite-read-column col))))
row))))
(sqlite-select (oref connection handle) message nil
(and emacsql-include-header 'full))))
((sqlite-error sqlite-locked-error)
(if (stringp (cdr err))
(signal 'emacsql-error (list (cdr err)))

View File

@@ -1,245 +1,22 @@
;;; emacsql-sqlite-common.el --- Code used by multiple SQLite back-ends -*- lexical-binding:t -*-
;;; emacsql-sqlite-common.el --- Transitional library that should not be loaded -*- lexical-binding:t -*-
;; This is free and unencumbered software released into the public domain.
;; Author: 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>
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; This library contains code that is used by multiple SQLite back-ends.
;; Transitional library that should not be loaded. If your package still
;; requires this library, change it to require `emacsql-sqlite' instead.
;;; Code:
(require 'emacsql)
;;; Base class
(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)
;;; Constants
(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.")
(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.")
;;; Utilities
(defun emacsql-sqlite-open (file &optional debug)
"Open a connected 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."
(let* ((class (emacsql-sqlite-default-connection))
(connection (make-instance class :file file)))
(when (eq class 'emacsql-sqlite-connection)
(set-process-query-on-exit-flag (oref connection handle) nil))
(when debug
(emacsql-enable-debugging connection))
connection))
(defun emacsql-sqlite-default-connection ()
"Determine and return the best SQLite connection class.
If a module or binary is required and that doesn't exist yet,
then try to compile it. Signal an error if no connection class
can be used."
(or (and (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. `sqlite3' elisp library isn't available.
;; 2. `libsqlite' shared library isn't available.
;; 3. `libsqlite' compilation fails.
;; 4. User chooses to not compile `libsqlite'.
(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, the best
EmacSQL backend is provided by the third-party `sqlite3' package
[2].
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'.
In the current Emacs instance the legacy backend is used, which
uses a custom SQLite executable. Using an external process like
that is less reliable and less performant, and in a few releases
support for that might be removed.
[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)))
(and (require 'emacsql-sqlite)
(boundp 'emacsql-sqlite-executable)
(or (file-exists-p emacsql-sqlite-executable)
(with-demoted-errors
"Cannot use `emacsql-sqlite-connection': %S"
(and (fboundp 'emacsql-sqlite-compile)
(emacsql-sqlite-compile 2))))
'emacsql-sqlite-connection)
(error "EmacSQL could not find or compile a back-end")))
(defun emacsql-sqlite-list-tables (connection)
"Return a list of the names of all tables in CONNECTION.
Tables whose names begin with \"sqlite_\", are not included
in the returned value."
(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
((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")))))
(defun emacsql-sqlite-restore-database (db dump)
"Restore database DB from DUMP.
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)))))
(require 'emacsql-sqlite)
(provide 'emacsql-sqlite-common)
;;; emacsql-sqlite-common.el ends here

View File

@@ -2,11 +2,9 @@
;; This is free and unencumbered software released into the public domain.
;; Author: 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") (emacsql "20230220") (sqlite3 "0.16"))
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
@@ -16,13 +14,12 @@
;;; Code:
(require 'emacsql)
(require 'emacsql-sqlite-common)
(require 'emacsql-sqlite)
(require 'sqlite3 nil t)
(declare-function sqlite3-open "sqlite3-api")
(declare-function sqlite3-exec "sqlite3-api")
(declare-function sqlite3-close "sqlite3-api")
(declare-function sqlite3-open "ext:sqlite3-api")
(declare-function sqlite3-exec "ext:sqlite3-api")
(declare-function sqlite3-close "ext:sqlite3-api")
(defvar sqlite-open-readwrite)
(defvar sqlite-open-create)
@@ -35,12 +32,10 @@
((connection emacsql-sqlite-module-connection) &rest _)
(require (quote sqlite3))
(oset connection handle
(sqlite3-open (or (slot-value connection 'file) ":memory:")
(sqlite3-open (or (oref connection file) ":memory:")
sqlite-open-readwrite
sqlite-open-create))
(when emacsql-global-timeout
(emacsql connection [:pragma (= busy-timeout $s1)]
(/ (* emacsql-global-timeout 1000) 2)))
(emacsql-sqlite-set-busy-timeout connection)
(emacsql connection [:pragma (= foreign-keys on)])
(emacsql-register connection))
@@ -49,7 +44,7 @@
If FILE is nil use an in-memory database.
:debug LOG -- When non-nil, log all SQLite commands to a log
buffer. This is for debugging purposes."
buffer. This is for debugging purposes."
(let ((connection (make-instance #'emacsql-sqlite-module-connection
:file file)))
(when debug
@@ -66,14 +61,19 @@ buffer. This is for debugging purposes."
(cl-defmethod emacsql-send-message
((connection emacsql-sqlite-module-connection) message)
(condition-case err
(let (rows)
(let ((include-header emacsql-include-header)
(rows ()))
(sqlite3-exec (oref connection handle)
message
(lambda (_ row __)
(push (mapcar (lambda (col)
(cond ((null col) nil)
((equal col "") "")
(t (read col))))
(lambda (_ row header)
(when include-header
(push header rows)
(setq include-header nil))
(push (mapcan (lambda (col)
(cond
((null col) (list nil))
((equal col "") (list ""))
((emacsql-sqlite-read-column col))))
row)
rows)))
(nreverse rows))

View File

@@ -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)

View File

@@ -3,61 +3,20 @@
;; This is free and unencumbered software released into the public domain.
;; Author: Christopher Wellons <wellons@nullprogram.com>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <emacs.emacsql@jonas.bernoulli.dev>
;; Homepage: https://github.com/magit/emacsql
;; Package-Version: 3.1.1.50-git
;; Package-Requires: ((emacs "25.1"))
;; Package-Version: 4.1.0
;; Package-Requires: ((emacs "26.1"))
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; EmacSQL is a high-level Emacs Lisp front-end for SQLite
;; (primarily), PostgreSQL, MySQL, and potentially other SQL
;; databases. On MELPA, each of the backends is provided through
;; separate packages: emacsql-sqlite, emacsql-psql, emacsql-mysql.
;; EmacSQL is a high-level Emacs Lisp front-end for SQLite.
;; Most EmacSQL functions operate on a database connection. For
;; example, a connection to SQLite is established with
;; `emacsql-sqlite'. For each such connection a sqlite3 inferior
;; process is kept alive in the background. Connections are closed
;; with `emacsql-close'.
;; (defvar db (emacsql-sqlite "company.db"))
;; Use `emacsql' to send an s-expression SQL statements to a connected
;; database. Identifiers for tables and columns are symbols. SQL
;; keywords are lisp keywords. Anything else is data.
;; (emacsql db [:create-table people ([name id salary])])
;; Column constraints can optionally be provided in the schema.
;; (emacsql db [:create-table people ([name (id integer :unique) salary])])
;; Insert some values.
;; (emacsql db [:insert :into people
;; :values (["Jeff" 1000 60000.0] ["Susan" 1001 64000.0])])
;; Currently all actions are synchronous and Emacs will block until
;; SQLite has indicated it is finished processing the last command.
;; Query the database for results:
;; (emacsql db [:select [name id] :from employees :where (> salary 60000)])
;; ;; => (("Susan" 1001))
;; Queries can be templates -- $i1, $s2, etc. -- so they don't need to
;; be built up dynamically:
;; (emacsql db
;; [:select [name id] :from employees :where (> salary $s1)]
;; 50000)
;; ;; => (("Jeff" 1000) ("Susan" 1001))
;; The letter declares the type (identifier, scalar, vector, Schema)
;; and the number declares the argument position.
;; PostgreSQL and MySQL are also supported, but use of these connectors
;; is not recommended.
;; See README.md for much more complete documentation.
@@ -73,11 +32,13 @@
"The EmacSQL SQL database front-end."
:group 'comm)
(defconst emacsql-version "3.1.1.50-git")
(defconst emacsql-version "4.1.0")
(defvar emacsql-global-timeout 30
"Maximum number of seconds to wait before bailing out on a SQL command.
If nil, wait forever.")
If nil, wait forever. This is used by the `mysql', `pg', `psql' and
`sqlite' back-ends. It is not being used by the `sqlite-builtin' and
`sqlite-module' back-ends, which only use `emacsql-sqlite-busy-timeout'.")
(defvar emacsql-data-root
(file-name-directory (or load-file-name buffer-file-name))
@@ -94,7 +55,6 @@ may return `process', `user-ptr' or `sqlite' for this value.")
(log-buffer :type (or null buffer)
:initarg :log-buffer
:initform nil
:accessor emacsql-log-buffer
:documentation "Output log (debug).")
(finalizer :documentation "Object returned from `make-finalizer'.")
(types :allocation :class
@@ -112,14 +72,14 @@ may return `process', `user-ptr' or `sqlite' for this value.")
(cl-defmethod emacsql-live-p ((connection emacsql-connection))
"Return non-nil if CONNECTION is still alive and ready."
(not (null (process-live-p (oref connection handle)))))
(and (process-live-p (oref connection handle)) t))
(cl-defgeneric emacsql-types (connection)
"Return an alist mapping EmacSQL types to database types.
This will mask `emacsql-type-map' during expression compilation.
This alist should have four key symbols: integer, float, object,
nil (default type). The values are strings to be inserted into a
SQL expression.")
nil (default type). The values are strings to be inserted into
a SQL expression.")
(cl-defmethod emacsql-buffer ((connection emacsql-connection))
"Get process buffer for CONNECTION."
@@ -127,14 +87,13 @@ SQL expression.")
(cl-defmethod emacsql-enable-debugging ((connection emacsql-connection))
"Enable debugging on CONNECTION."
(unless (buffer-live-p (emacsql-log-buffer connection))
(setf (emacsql-log-buffer connection)
(generate-new-buffer " *emacsql-log*"))))
(unless (buffer-live-p (oref connection log-buffer))
(oset connection log-buffer (generate-new-buffer " *emacsql-log*"))))
(cl-defmethod emacsql-log ((connection emacsql-connection) message)
"Log MESSAGE into CONNECTION's log.
MESSAGE should not have a newline on the end."
(let ((buffer (emacsql-log-buffer connection)))
(let ((buffer (oref connection log-buffer)))
(when buffer
(unless (buffer-live-p buffer)
(setq buffer (emacsql-enable-debugging connection)))
@@ -147,11 +106,11 @@ MESSAGE should not have a newline on the end."
Using this function to do it anyway, means additionally using a
misnamed and obsolete accessor function."
(and (slot-boundp this 'handle)
(eieio-oref this 'handle)))
(oref this handle)))
(cl-defmethod (setf emacsql-process) (value (this emacsql-connection))
(eieio-oset this 'handle value))
(oset this handle value))
(make-obsolete 'emacsql-process "underlying slot is for internal use only."
"Emacsql 4.0.0")
"EmacSQL 4.0.0")
(cl-defmethod slot-missing ((connection emacsql-connection)
slot-name operation &optional new-value)
@@ -187,7 +146,7 @@ misnamed and obsolete accessor function."
(cl-defmethod emacsql-wait ((connection emacsql-connection) &optional timeout)
"Block until CONNECTION is waiting for further input."
(let* ((real-timeout (or timeout emacsql-global-timeout))
(end (when real-timeout (+ (float-time) real-timeout))))
(end (and real-timeout (+ (float-time) real-timeout))))
(while (and (or (null real-timeout) (< (float-time) end))
(not (emacsql-waiting-p connection)))
(save-match-data
@@ -200,7 +159,7 @@ misnamed and obsolete accessor function."
(defun emacsql-compile (connection sql &rest args)
"Compile s-expression SQL for CONNECTION into a string."
(let* ((mask (when connection (emacsql-types connection)))
(let* ((mask (and connection (emacsql-types connection)))
(emacsql-type-map (or mask emacsql-type-map)))
(concat (apply #'emacsql-format (emacsql-prepare sql) args) ";")))
@@ -218,9 +177,9 @@ misnamed and obsolete accessor function."
(defclass emacsql-protocol-mixin () ()
"A mixin for back-ends following the EmacSQL protocol.
The back-end prompt must be a single \"]\" character. This prompt
value was chosen because it is unreadable. Output must have
exactly one row per line, fields separated by whitespace. NULL
The back-end prompt must be a single \"]\" character. This prompt
value was chosen because it is unreadable. Output must have
exactly one row per line, fields separated by whitespace. NULL
must display as \"nil\"."
:abstract t)
@@ -258,9 +217,9 @@ specific error conditions."
(defun emacsql-register (connection)
"Register CONNECTION for automatic cleanup and return CONNECTION."
(let ((finalizer (make-finalizer (lambda () (emacsql-close connection)))))
(prog1 connection
(setf (slot-value connection 'finalizer) finalizer))))
(prog1 connection
(oset connection finalizer
(make-finalizer (lambda () (emacsql-close connection))))))
;;; Useful macros
@@ -287,7 +246,7 @@ This macro can be nested indefinitely, wrapping everything in a
single transaction at the lowest level.
Warning: BODY should *not* have any side effects besides making
changes to the database behind CONNECTION. Body may be evaluated
changes to the database behind CONNECTION. Body may be evaluated
multiple times before the changes are committed."
(declare (indent 1))
`(let ((emacsql--connection ,connection)
@@ -301,10 +260,10 @@ multiple times before the changes are committed."
(when (= 1 emacsql--transaction-level)
(emacsql emacsql--connection [:begin]))
(let ((result (progn ,@body)))
(setf emacsql--result result)
(setq emacsql--result result)
(when (= 1 emacsql--transaction-level)
(emacsql emacsql--connection [:commit]))
(setf emacsql--completed t)))
(setq emacsql--completed t)))
(emacsql-locked (emacsql emacsql--connection [:rollback])
(sleep-for 0.05))))
(when (and (= 1 emacsql--transaction-level)
@@ -329,8 +288,8 @@ A statement can be a list, containing a statement with its arguments."
Returns the result of the last evaluated BODY.
All column names must be provided in the query ($ and * are not
allowed). Hint: all of the bound identifiers must be known at
compile time. For example, in the expression below the variables
allowed). Hint: all of the bound identifiers must be known at
compile time. For example, in the expression below the variables
`name' and `phone' will be bound for the body.
(emacsql-with-bind db [:select [name phone] :from people]
@@ -344,16 +303,16 @@ compile time. For example, in the expression below the variables
Each column must be a plain symbol, no expressions allowed here."
(declare (indent 2))
(let ((sql (if (vectorp sql-and-args) sql-and-args (car sql-and-args)))
(args (unless (vectorp sql-and-args) (cdr sql-and-args))))
(args (and (not (vectorp sql-and-args)) (cdr sql-and-args))))
(cl-assert (eq :select (elt sql 0)))
(let ((vars (elt sql 1)))
(when (eq '* vars)
(error "Must explicitly list columns in `emacsql-with-bind'."))
(error "Must explicitly list columns in `emacsql-with-bind'"))
(cl-assert (cl-every #'symbolp vars))
`(let ((emacsql--results (emacsql ,connection ,sql ,@args))
(emacsql--final nil))
(dolist (emacsql--result emacsql--results emacsql--final)
(setf emacsql--final
(setq emacsql--final
(cl-destructuring-bind ,(cl-coerce vars 'list) emacsql--result
,@body)))))))
@@ -380,14 +339,7 @@ Each column must be a plain symbol, no expressions allowed here."
(sql-mode)
(with-no-warnings ;; autoloaded by previous line
(sql-highlight-sqlite-keywords))
(if (and (fboundp 'font-lock-flush)
(fboundp 'font-lock-ensure))
(save-restriction
(widen)
(font-lock-flush)
(font-lock-ensure))
(with-no-warnings
(font-lock-fontify-buffer)))
(font-lock-ensure)
(emacsql--indent)
(buffer-string))))
(with-current-buffer (get-buffer-create emacsql-show-buffer-name)
@@ -432,9 +384,9 @@ A prefix argument causes the SQL to be printed into the current buffer."
(save-excursion
(beginning-of-defun)
(let ((containing-sexp (elt (parse-partial-sexp (point) start) 1)))
(when containing-sexp
(goto-char containing-sexp)
(looking-at "\\["))))))
(and containing-sexp
(progn (goto-char containing-sexp)
(looking-at "\\[")))))))
(defun emacsql--calculate-vector-indent (fn &optional parse-start)
"Don't indent vectors in `emacs-lisp-mode' like lists."

View File

@@ -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

View File

@@ -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