update of packages

This commit is contained in:
2023-11-04 19:26:41 +01:00
parent e162a12b58
commit 3b54a3236d
726 changed files with 297673 additions and 34585 deletions

View File

@@ -1,435 +0,0 @@
# EmacSQL
EmacSQL is a high-level Emacs Lisp front-end for SQLite (primarily),
PostgreSQL, MySQL, and potentially other SQL databases.
Any [readable lisp value][readable] can be stored as a value in
EmacSQL, including numbers, strings, symbols, lists, vectors, and
closures. EmacSQL has no concept of "TEXT" values; it's all just lisp
objects. The lisp object `nil` corresponds 1:1 with `NULL` in the
database.
On MELPA, each back-end is provided as a separate package, suffixed with
the database name. In the case of `emacsql-sqlite`, on first use EmacSQL
will attempt to find a C compiler and use it to compile a custom native
binary for communicating with a SQLite database.
Requires Emacs 25 or later.
### FAQ
#### Why are all values stored as strings?
EmacSQL is not intended to interact with arbitrary databases, but to
be an ACID-compliant database for Emacs extensions. This means that
EmacSQL cannot be used with a regular SQL database used by other
non-Emacs clients.
All database values must be s-expressions. When EmacSQL stores a
value — string, symbol, cons, etc. — it is printed and written to
the database in its printed form. Strings are wrapped in quotes
and escaped as necessary. That means "bare" symbols in the database
generally look like strings. The only exception is `nil`, which is
stored as `NULL`.
#### Will EmacSQL ever support arbitrary databases?
The author of EmacSQL [thinks][mistake] that it was probably a
design mistake to restrict it to Emacs by storing only printed values,
and that it would be a lot more useful if it just handled primitive
database types.
However, EmacSQL is in maintenance mode and there are no plans to
make any fundamental changes, not least because they would break all
existing packages and databases that rely on the current EmacSQL
behavior.
### Windows Issues
Emacs `start-process-shell-command` function is not supported on
Windows. Since both `emacsql-mysql` and `emacsql-psql` rely on this
function, neither of these connection types are supported on Windows.
## Example Usage
```el
(defvar db (emacsql-sqlite "~/company.db"))
;; Create a table. Table and column identifiers are symbols.
(emacsql db [:create-table people ([name id salary])])
;; Or optionally provide column constraints.
(emacsql db [:create-table people
([name (id integer :primary-key) (salary float)])])
;; Insert some data:
(emacsql db [:insert :into people
:values (["Jeff" 1000 60000.0] ["Susan" 1001 64000.0])])
;; Query the database for results:
(emacsql db [:select [name id]
:from people
:where (> salary 62000)])
;; => (("Susan" 1001))
;; Queries can be templates, using $1, $2, etc.:
(emacsql db [:select [name id]
:from people
:where (> salary $s1)]
50000)
;; => (("Jeff" 1000) ("Susan" 1001))
```
When editing these prepared SQL s-expression statements, the `M-x
emacsql-show-last-sql` command (think `eval-last-sexp`) is useful for
seeing what the actual SQL expression will become when compiled.
## Schema
A table schema is a list whose first element is a vector of column
specifications. The rest of the list specifies table constraints. A
column identifier is a symbol and a column's specification can either
be just this symbol or it can include constraints as a list. Because
EmacSQL stores entire lisp objects as values, the only relevant (and
allowed) types are `integer`, `float`, and `object` (default).
([(<column>) ...] (<table-constraint> ...) ...])
Dashes in identifiers are converted into underscores when compiled
into SQL. This allows for lisp-style identifiers to be used in SQL.
Constraints follow the compilation rules below.
```el
;; No constraints schema with four columns:
([name id building room])
;; Add some column constraints:
([(name :unique) (id integer :primary-key) building room])
;; Add some table constraints:
([(name :unique) (id integer :primary-key) building room]
(:unique [building room])
(:check (> id 0)))
```
Here's an example using foreign keys.
```el
;; "subjects" table schema
([(id integer :primary-key) subject])
;; "tag" table references subjects
([(subject-id integer) tag]
(:foreign-key [subject-id] :references subjects [id]
:on-delete :cascade))
```
Foreign key constraints are enabled by default in EmacSQL.
## Operators
Expressions are written lisp-style, with the operator first. If it
looks like an operator EmacSQL treats it like an operator. However,
several operators are special.
<= >= funcall quote
The `<=` and `>=` operators accept 2 or 3 operands, transforming into
a SQL `_ BETWEEN _ AND _` operator as appropriate.
For function-like "operators" like `count` and `max` use the `funcall`
"operator."
```el
[:select (funcall max age) :from people]
```
Inside expressions, EmacSQL cannot tell the difference between symbol
literals and column references. If you're talking about the symbol
itself, just quote it as you would in normal Elisp. Note that this
does not "escape" `$tn` parameter symbols.
```el
(emacsql db [... :where (= category 'hiking)])
```
Quoting a string makes EmacSQL handle it as a "raw string." These raw
strings are not printed when being assembled into a query. These are
intended for use in special circumstances like filenames (`ATTACH`) or
pattern matching (`LIKE`). It is vital that raw strings are not
returned as results.
```el
(emacsql db [... :where (like name '"%foo%")])
(emacsql db [:attach '"/path/to/foo.db" :as foo])
```
Since template parameters include their type they never need to be
quoted.
With `glob` and `like` SQL operators keep in mind that they're
matching the *printed* representations of these values, even if the
value is a string.
The `||` concatenation operator is unsupported because concatenating
printed representations breaks an important constraint: all values must
remain readable within SQLite.
## Prepared Statements
The database is interacted with via prepared SQL s-expression
statements. You shouldn't normally be concatenating strings on your
own. (And it leaves out any possibility of a SQL injection!) See the
"Usage" section above for examples. A statement is a vector of
keywords and other lisp object.
Prepared EmacSQL s-expression statements are compiled into SQL
statements. The statement compiler is memorized so that using the same
statement multiple times is fast. To assist in this, the statement can
act as a template -- using `$i1`, `$s2`, etc. -- working like the
Elisp `format` function.
### Compilation Rules
Rather than the typical uppercase SQL keywords, keywords in a prepared
EmacSQL statement are literally just that: lisp keywords. EmacSQL only
understands a very small amount of SQL's syntax. The compiler follows
some simple rules to convert an s-expression into SQL.
#### All prepared statements are vectors.
A prepared s-expression statement is a vector beginning with a keyword
followed by a series of keywords and special values. This includes
most kinds of sub-queries.
```el
[:select ... :from ...]
[:select tag :from tags
:where (in tag [:select ...])]
```
#### Keywords are split and capitalized.
Dashes are converted into spaces and the keyword gets capitalized. For
example, `:if-not-exists` becomes `IF NOT EXISTS`. How you choose to
combine keywords is up to your personal taste (e.g. `:drop :table` vs.
`:drop-table`).
#### Standalone symbols are identifiers.
EmacSQL doesn't know what symbols refer to identifiers and what
symbols should be treated as values. Use quotes to mark a symbol as a
value. For example, `people` here will be treated as an identifier.
```el
[:insert-into people :values ...]
```
#### Row-oriented information is always represented as vectors.
This includes rows being inserted, and sets of columns in a query. If
you're talking about a row-like thing then put it in a vector.
```el
[:select [id name] :from people]
```
Note that `*` is actually a SQL keyword, so don't put it in a vector.
```el
[:select * :from ...]
```
#### Lists are treated as expressions.
This is true even within row-oriented vectors.
```el
[... :where (= name "Bob")]
[:select [(/ seconds 60) count] :from ...]
```
Some things that are traditionally keywords -- particularly those that
are mixed in with expressions -- have been converted into operators
(`AS`, `ASC`, `DESC`).
```el
[... :order-by [(asc b), (desc a)]] ; "ORDER BY b ASC, a DESC"
[:select p:name :from (as people p)] ; "SELECT p.name FROM people AS p"
```
#### The `:values` keyword is special.
What follows `:values` is always treated like a vector or list of
vectors. Normally this sort of thing would appear to be a column
reference.
```el
[... :values [1 2 3]]
[... :values ([1 2 3] [4 5 6])] ; insert multiple rows
```
#### A list whose first element is a vector is a table schema.
This is to distinguish schemata from everything else. With the
exception of what follows `:values`, nothing else is shaped like this.
```el
[:create-table people ([(id :primary-key) name])]
```
### Templates
To make statement compilation faster, and to avoid making you build up
statements dynamically, you can insert `$tn` parameters in place of
identifiers and values. These refer to the argument's type and its
argument position after the statement in the `emacsql` function,
one-indexed.
```el
(emacsql db [:select * :from $i1 :where (> salary $s2)] 'employees 50000)
(emacsql db [:select * :from employees :where (like name $r1)] "%Smith%")
```
The letter before the number is the type.
* `i` : identifier
* `s` : scalar
* `v` : vector (or multiple vectors)
* `r` : raw, unprinted strings
* `S` : schema
When combined with `:values`, the vector type can refer to lists of
rows.
```el
(emacsql db [:insert-into favorite-characters :values $v1]
'([0 "Calvin"] [1 "Hobbes"] [3 "Susie"]))
```
This is why rows must be vectors and not lists.
## SQLite Support
The custom EmacSQL SQLite binary is compiled with [Soundex][soundex] and
[full-text search][fts] (FTS3, FTS4, and FTS5) enabled -- features
disabled by the default SQLite build. This back-end should work on any
system with a conforming ANSI C compiler installed under a command name
listed in `emacsql-sqlite-c-compilers`.
### Ignored Features
EmacSQL doesn't cover all of SQLite's features. Here are a list of
things that aren't supported, and probably will never be.
* Collating. SQLite has three built-in collation functions: BINARY
(default), NOCASE, and RTRIM. EmacSQL values never have right-hand
whitespace, so RTRIM won't be of any use. NOCASE is broken
(ASCII-only) and there's little reason to use it.
* Text manipulation functions. Like collating this is incompatible
with EmacSQL s-expression storage.
* Date and time. These are incompatible with the printed values
stored by EmacSQL and therefore have little use.
## Limitations
EmacSQL is *not* intended to play well with other programs accessing
the SQLite database. Non-numeric values are stored encoded as
s-expressions TEXT values. This avoids ambiguities in parsing output
from the command line and allows for storage of Emacs richer data
types. This is an efficient, ACID-compliant database specifically for
Emacs.
## Emacs Lisp Indentation Annoyance
By default, `emacs-lisp-mode` indents vectors as if they were regular
function calls.
```el
;; Ugly indentation!
(emacsql db [:select *
:from people
:where (> age 60)])
```
Calling the function `emacsql-fix-vector-indentation` (interactive)
advises the major mode to fix this annoyance.
```el
;; Such indent!
(emacsql db [:select *
:from people
:where (> age 60)])
```
## Contributing and Extending
To run the test suite, clone the `pg` and `finalize` packages into
sibling directories. The Makefile will automatically put these paths on
the Emacs load path (override `LDFLAGS` if your situation is different).
$ cd ..
$ git clone https://github.com/cbbrowne/pg.el pg
$ git clone https://github.com/skeeto/elisp-finalize finalize
$ cd -
Then invoke make:
$ make test
If the environment variable `PGDATABASE` is present then the unit
tests will also be run with PostgreSQL (emacsql-psql). Provide
`PGHOST`, `PGPORT`, and `PGUSER` if needed. If `PGUSER` is provided,
the pg.el back-end (emacsql-pg) will also be tested.
If the environment variable `MYSQL_DBNAME` is present then the unit
tests will also be run with MySQL in the named database. Note that
this is not an official MySQL variable, just something made up for
EmacSQL.
### Creating a New Front-end
EmacSQL uses EIEIO so that interactions with a connection occur
through generic functions. You need to define a new class that
inherits from `emacsql-connection`.
* Implement `emacsql-send-message`, `emacsql-waiting-p`,
`emacsql-parse`, and `emacsql-close`.
* Provide a constructor that initializes the connection and calls
`emacsql-register` (for automatic connection cleanup).
* Provide `emacsql-types` if needed (hint: use a class-allocated slot).
* Ensure that you properly read NULL as nil (hint: ask your back-end
to print it that way).
* Register all reserved words with `emacsql-register-reserved`.
* Preferably provide `emacsql-reconnect` if possible.
* Set the default isolation level to *serializable*.
* Enable autocommit mode by default.
* Prefer ANSI syntax (value escapes, identifier escapes, etc.).
* Enable foreign key constraints by default.
The goal of the autocommit, isolation, parsing, and foreign key
configuration settings is to normalize the interface as much as
possible. The connection's user should have the option to be agnostic
about which back-end is actually in use.
The provided implementations should serve as useful examples. If your
back-end outputs data in a clean, standard way you may be able to use
the emacsql-protocol-mixin class to do most of the work.
## See Also
* [SQLite Documentation](https://www.sqlite.org/docs.html)
[readable]: http://nullprogram.com/blog/2013/12/30/#almost_everything_prints_readably
[stderr]: http://thread.gmane.org/gmane.comp.db.sqlite.general/85824
[foreign]: http://www.sqlite.org/foreignkeys.html
[batch]: http://lists.gnu.org/archive/html/emacs-pretest-bug/2005-11/msg00320.html
[fts]: http://www.sqlite.org/fts3.html
[soundex]: http://www.sqlite.org/compile.html#soundex
[mistake]: https://github.com/magit/emacsql/issues/35#issuecomment-346352439
<!-- LocalWords: EIEIO Elisp EmacSQL FTS MELPA Makefile NOCASE RTRIM SQL's Soundex -->
<!-- LocalWords: autocommit el emacsql mixin psql schemas unprinted whitespace -->

View File

@@ -0,0 +1,135 @@
;;; emacsql-mysql.el --- EmacSQL back-end for MySQL -*- 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
;; Package-Version: 3.1.1.50-git
;; Package-Requires: ((emacs "25.1") (emacsql "20230220"))
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; This library provides an EmacSQL back-end for MySQL, which uses
;; the standard `msql' command line program.
;;; Code:
(require 'emacsql)
(defvar emacsql-mysql-executable "mysql"
"Path to the mysql command line executable.")
(defvar emacsql-mysql-sentinel "--------------\n\n--------------\n\n"
"What MySQL will print when it has completed its output.")
(defconst emacsql-mysql-reserved
(emacsql-register-reserved
'( ACCESSIBLE ADD ALL ALTER ANALYZE AND AS ASC ASENSITIVE BEFORE
BETWEEN BIGINT BINARY BLOB BOTH BY CALL CASCADE CASE CHANGE CHAR
CHARACTER CHECK COLLATE COLUMN CONDITION CONSTRAINT CONTINUE
CONVERT CREATE CROSS CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP
CURRENT_USER CURSOR DATABASE DATABASES DAY_HOUR DAY_MICROSECOND
DAY_MINUTE DAY_SECOND DEC DECIMAL DECLARE DEFAULT DELAYED DELETE
DESC DESCRIBE DETERMINISTIC DISTINCT DISTINCTROW DIV DOUBLE DROP
DUAL EACH ELSE ELSEIF ENCLOSED ESCAPED EXISTS EXIT EXPLAIN FALSE
FETCH FLOAT FLOAT4 FLOAT8 FOR FORCE FOREIGN FROM FULLTEXT GENERAL
GRANT GROUP HAVING HIGH_PRIORITY HOUR_MICROSECOND HOUR_MINUTE
HOUR_SECOND IF IGNORE IGNORE_SERVER_IDS IN INDEX INFILE INNER
INOUT INSENSITIVE INSERT INT INT1 INT2 INT3 INT4 INT8 INTEGER
INTERVAL INTO IS ITERATE JOIN KEY KEYS KILL LEADING LEAVE LEFT
LIKE LIMIT LINEAR LINES LOAD LOCALTIME LOCALTIMESTAMP LOCK LONG
LONGBLOB LONGTEXT LOOP LOW_PRIORITY MASTER_HEARTBEAT_PERIOD
MASTER_SSL_VERIFY_SERVER_CERT MATCH MAXVALUE MAXVALUE MEDIUMBLOB
MEDIUMINT MEDIUMTEXT MIDDLEINT MINUTE_MICROSECOND MINUTE_SECOND
MOD MODIFIES NATURAL NOT NO_WRITE_TO_BINLOG NULL NUMERIC ON
OPTIMIZE OPTION OPTIONALLY OR ORDER OUT OUTER OUTFILE PRECISION
PRIMARY PROCEDURE PURGE RANGE READ READS READ_WRITE REAL
REFERENCES REGEXP RELEASE RENAME REPEAT REPLACE REQUIRE RESIGNAL
RESIGNAL RESTRICT RETURN REVOKE RIGHT RLIKE SCHEMA SCHEMAS
SECOND_MICROSECOND SELECT SENSITIVE SEPARATOR SET SHOW SIGNAL
SIGNAL SLOW SMALLINT SPATIAL SPECIFIC SQL SQL_BIG_RESULT
SQL_CALC_FOUND_ROWS SQLEXCEPTION SQL_SMALL_RESULT SQLSTATE
SQLWARNING SSL STARTING STRAIGHT_JOIN TABLE TERMINATED THEN
TINYBLOB TINYINT TINYTEXT TO TRAILING TRIGGER TRUE UNDO UNION
UNIQUE UNLOCK UNSIGNED UPDATE USAGE USE USING UTC_DATE UTC_TIME
UTC_TIMESTAMP VALUES VARBINARY VARCHAR VARCHARACTER VARYING WHEN
WHERE WHILE WITH WRITE XOR YEAR_MONTH ZEROFILL))
"List of all of MySQL's reserved words.
http://dev.mysql.com/doc/refman/5.5/en/reserved-words.html")
(defclass emacsql-mysql-connection (emacsql-connection)
((dbname :reader emacsql-psql-dbname :initarg :dbname)
(types :allocation :class
:reader emacsql-types
:initform '((integer "BIGINT")
(float "DOUBLE")
(object "LONGTEXT")
(nil "LONGTEXT"))))
"A connection to a MySQL database.")
(cl-defun emacsql-mysql (database &key user password host port debug)
"Connect to a MySQL server using the mysql command line program."
(let* ((mysql (or (executable-find emacsql-mysql-executable)
(error "No mysql binary available, aborting")))
(command (list database "--skip-pager" "-rfBNL" mysql)))
(when user (push (format "--user=%s" user) command))
(when password (push (format "--password=%s" password) command))
(when host (push (format "--host=%s" host) command))
(when port (push (format "--port=%s" port) command))
(let* ((process-connection-type t)
(buffer (generate-new-buffer " *emacsql-mysql*"))
(command (mapconcat #'shell-quote-argument (nreverse command) " "))
(process (start-process-shell-command
"emacsql-mysql" buffer (concat "stty raw &&" command)))
(connection (make-instance 'emacsql-mysql-connection
:handle process
:dbname database)))
(set-process-sentinel process
(lambda (proc _) (kill-buffer (process-buffer proc))))
(set-process-query-on-exit-flag (oref connection handle) nil)
(when debug (emacsql-enable-debugging connection))
(emacsql connection
[:set-session (= sql-mode 'NO_BACKSLASH_ESCAPES\,ANSI_QUOTES)])
(emacsql connection
[:set-transaction-isolation-level :serializable])
(emacsql-register connection))))
(cl-defmethod emacsql-close ((connection emacsql-mysql-connection))
(let ((process (oref connection handle)))
(when (process-live-p process)
(process-send-eof process))))
(cl-defmethod emacsql-send-message ((connection emacsql-mysql-connection) message)
(let ((process (oref connection handle)))
(process-send-string process message)
(process-send-string process "\\c\\p\n")))
(cl-defmethod emacsql-waiting-p ((connection emacsql-mysql-connection))
(let ((length (length emacsql-mysql-sentinel)))
(with-current-buffer (emacsql-buffer connection)
(and (>= (buffer-size) length)
(progn (goto-char (- (point-max) length))
(looking-at emacsql-mysql-sentinel))))))
(cl-defmethod emacsql-parse ((connection emacsql-mysql-connection))
(with-current-buffer (emacsql-buffer connection)
(let ((standard-input (current-buffer)))
(goto-char (point-min))
(when (looking-at "ERROR")
(search-forward ": ")
(signal 'emacsql-error
(list (buffer-substring (point) (line-end-position)))))
(cl-loop until (looking-at emacsql-mysql-sentinel)
collect (read) into row
when (looking-at "\n")
collect row into rows
and do (setf row ())
and do (forward-char)
finally (cl-return rows)))))
(provide 'emacsql-mysql)
;;; emacsql-mysql.el ends here

View File

@@ -0,0 +1,80 @@
;;; emacsql-pg.el --- EmacSQL back-end for PostgreSQL via pg -*- 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
;; 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.
;; (For an alternative back-end for PostgreSQL, see `emacsql-psql'.)
;;; Code:
(require 'emacsql)
(require 'pg nil t)
(declare-function pg-connect "pg"
( dbname user &optional
(password "") (host "localhost") (port 5432) (tls nil)))
(declare-function pg-disconnect "pg" (con))
(declare-function pg-exec "pg" (connection &rest args))
(declare-function pg-result "pg" (result what &rest arg))
(defclass emacsql-pg-connection (emacsql-connection)
((pgcon :reader emacsql-pg-pgcon :initarg :pgcon)
(dbname :reader emacsql-pg-dbname :initarg :dbname)
(result :accessor emacsql-pg-result)
(types :allocation :class
:reader emacsql-types
:initform '((integer "BIGINT")
(float "DOUBLE PRECISION")
(object "TEXT")
(nil "TEXT"))))
"A connection to a PostgreSQL database via pg.el.")
(cl-defun emacsql-pg (dbname user &key
(host "localhost") (password "") (port 5432) debug)
"Connect to a PostgreSQL server using pg.el."
(require 'pg)
(let* ((pgcon (pg-connect dbname user password host port))
(connection (make-instance 'emacsql-pg-connection
:handle (and (fboundp 'pgcon-process)
(pgcon-process pgcon))
:pgcon pgcon
:dbname dbname)))
(when debug (emacsql-enable-debugging connection))
(emacsql connection [:set (= default-transaction-isolation 'SERIALIZABLE)])
(emacsql-register connection)))
(cl-defmethod emacsql-close ((connection emacsql-pg-connection))
(ignore-errors (pg-disconnect (emacsql-pg-pgcon connection))))
(cl-defmethod emacsql-send-message ((connection emacsql-pg-connection) message)
(condition-case error
(setf (emacsql-pg-result connection)
(pg-exec (emacsql-pg-pgcon connection) message))
(error (signal 'emacsql-error error))))
(cl-defmethod emacsql-waiting-p ((_connection emacsql-pg-connection))
;; pg-exec will block
t)
(cl-defmethod emacsql-parse ((connection emacsql-pg-connection))
(let ((tuples (pg-result (emacsql-pg-result connection) :tuples)))
(cl-loop for tuple in tuples collect
(cl-loop for value in tuple
when (stringp value) collect (read value)
else collect value))))
(provide 'emacsql-pg)
;;; emacsql-pg.el ends here

View File

@@ -1,7 +1,9 @@
(define-package "emacsql" "20221127.2146" "High-level SQL database front-end"
(define-package "emacsql" "20230417.1448" "High-level SQL database front-end"
'((emacs "25.1"))
:commit "6b2e65bdf785364cf7c34c31fea5812e1e58c657" :authors
:commit "64012261f65fcdd7ea137d1973ef051af1dced42" :authors
'(("Christopher Wellons" . "wellons@nullprogram.com"))
:maintainers
'(("Jonas Bernoulli" . "jonas@bernoul.li"))
:maintainer
'("Jonas Bernoulli" . "jonas@bernoul.li")
:url "https://github.com/magit/emacsql")

View File

@@ -0,0 +1,147 @@
;;; emacsql-psql.el --- EmacSQL back-end for PostgreSQL via psql -*- 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
;; Package-Version: 3.1.1.50-git
;; Package-Requires: ((emacs "25.1") (emacsql "20230220"))
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; This library provides an EmacSQL back-end for PostgreSQL, which
;; uses the standard `psql' command line program.
;; (For an alternative back-end for PostgreSQL, see `emacsql-pg'.)
;;; Code:
(require 'emacsql)
(defvar emacsql-psql-executable "psql"
"Path to the psql (PostgreSQL client) executable.")
(defun emacsql-psql-unavailable-p ()
"Return a reason if the psql executable is not available.
:no-executable -- cannot find the executable
:cannot-execute -- cannot run the executable
:old-version -- sqlite3 version is too old"
(let ((psql emacsql-psql-executable))
(if (null (executable-find psql))
:no-executable
(condition-case _
(with-temp-buffer
(call-process psql nil (current-buffer) nil "--version")
(let ((version (cl-third (split-string (buffer-string)))))
(if (version< version "1.0.0")
:old-version
nil)))
(error :cannot-execute)))))
(defconst emacsql-psql-reserved
(emacsql-register-reserved
'( ALL ANALYSE ANALYZE AND ANY AS ASC AUTHORIZATION BETWEEN BINARY
BOTH CASE CAST CHECK COLLATE COLUMN CONSTRAINT CREATE CROSS
CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER DEFAULT
DEFERRABLE DESC DISTINCT DO ELSE END EXCEPT FALSE FOR FOREIGN
FREEZE FROM FULL GRANT GROUP HAVING ILIKE IN INITIALLY INNER
INTERSECT INTO IS ISNULL JOIN LEADING LEFT LIKE LIMIT LOCALTIME
LOCALTIMESTAMP NATURAL NEW NOT NOTNULL NULL OFF OFFSET OLD ON
ONLY OR ORDER OUTER OVERLAPS PLACING PRIMARY REFERENCES RIGHT
SELECT SESSION_USER SIMILAR SOME TABLE THEN TO TRAILING TRUE
UNION UNIQUE USER USING VERBOSE WHEN WHERE))
"List of all of PostgreSQL's reserved words.
http://www.postgresql.org/docs/7.3/static/sql-keywords-appendix.html")
(defclass emacsql-psql-connection (emacsql-connection)
((dbname :reader emacsql-psql-dbname :initarg :dbname)
(types :allocation :class
:reader emacsql-types
:initform '((integer "BIGINT")
(float "DOUBLE PRECISION")
(object "TEXT")
(nil "TEXT"))))
"A connection to a PostgreSQL database via psql.")
(cl-defun emacsql-psql (dbname &key username hostname port debug)
"Connect to a PostgreSQL server using the psql command line program."
(let ((args (list dbname)))
(when username
(push username args))
(push "-n" args)
(when port
(push "-p" args)
(push port args))
(when hostname
(push "-h" args)
(push hostname args))
(setf args (nreverse args))
(let* ((buffer (generate-new-buffer " *emacsql-psql*"))
(psql emacsql-psql-executable)
(command (mapconcat #'shell-quote-argument (cons psql args) " "))
(process (start-process-shell-command
"emacsql-psql" buffer (concat "stty raw && " command)))
(connection (make-instance 'emacsql-psql-connection
:handle process
:dbname dbname)))
(setf (process-sentinel process)
(lambda (proc _) (kill-buffer (process-buffer proc))))
(set-process-query-on-exit-flag (oref connection handle) nil)
(when debug (emacsql-enable-debugging connection))
(mapc (apply-partially #'emacsql-send-message connection)
'("\\pset pager off"
"\\pset null nil"
"\\a"
"\\t"
"\\f ' '"
"SET client_min_messages TO ERROR;"
"\\set PROMPT1 ]"
"EMACSQL;")) ; error message flush
(emacsql-wait connection)
(emacsql connection
[:set (= default-transaction-isolation 'SERIALIZABLE)])
(emacsql-register connection))))
(cl-defmethod emacsql-close ((connection emacsql-psql-connection))
(let ((process (oref connection handle)))
(when (process-live-p process)
(process-send-string process "\\q\n"))))
(cl-defmethod emacsql-send-message ((connection emacsql-psql-connection) message)
(let ((process (oref connection handle)))
(process-send-string process message)
(process-send-string process "\n")))
(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)))))))
(cl-defmethod emacsql-check-error ((connection emacsql-psql-connection))
(with-current-buffer (emacsql-buffer connection)
(let ((case-fold-search t))
(goto-char (point-min))
(when (looking-at "error:")
(let* ((beg (line-beginning-position))
(end (line-end-position)))
(signal 'emacsql-error (list (buffer-substring beg end))))))))
(cl-defmethod emacsql-parse ((connection emacsql-psql-connection))
(emacsql-check-error connection)
(with-current-buffer (emacsql-buffer connection)
(let ((standard-input (current-buffer)))
(goto-char (point-min))
(cl-loop until (looking-at "]")
collect (read) into row
when (looking-at "\n")
collect row into rows
and do (progn (forward-char 1) (setf row ()))
finally (cl-return rows)))))
(provide 'emacsql-psql)
;;; emacsql-psql.el ends here

View File

@@ -0,0 +1,89 @@
;;; emacsql-sqlite-builtin.el --- EmacSQL back-end for SQLite using builtin support -*- 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
;; Package-Version: 3.1.1.50-git
;; Package-Requires: ((emacs "29") (emacsql "20230220"))
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; This library provides an EmacSQL back-end for SQLite, which uses
;; the built-in SQLite support in Emacs 29 an later.
;;; Code:
(require 'emacsql)
(require 'emacsql-sqlite-common)
(require 'sqlite nil t)
(declare-function sqlite-open "sqlite")
(declare-function sqlite-select "sqlite")
(declare-function sqlite-close "sqlite")
(emacsql-register-reserved emacsql-sqlite-reserved)
(defclass emacsql-sqlite-builtin-connection (emacsql--sqlite-base) ()
"A connection to a SQLite database using builtin support.")
(cl-defmethod initialize-instance :after
((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)))
(emacsql connection [:pragma (= foreign-keys on)])
(emacsql-register connection))
(cl-defun emacsql-sqlite-builtin (file &key debug)
"Open a connected to database stored in FILE.
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."
(let ((connection (make-instance #'emacsql-sqlite-builtin-connection
:file file)))
(when debug
(emacsql-enable-debugging connection))
connection))
(cl-defmethod emacsql-live-p ((connection emacsql-sqlite-builtin-connection))
(and (oref connection handle) t))
(cl-defmethod emacsql-close ((connection emacsql-sqlite-builtin-connection))
(sqlite-close (oref connection handle))
(oset connection handle nil))
(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))
((sqlite-error sqlite-locked-error)
(if (stringp (cdr err))
(signal 'emacsql-error (list (cdr err)))
(pcase-let* ((`(,_ ,errstr ,errmsg ,errcode ,ext-errcode) err)
(`(,_ ,_ ,signal ,_)
(assq errcode emacsql-sqlite-error-codes)))
(signal (or signal 'emacsql-error)
(list errmsg errcode ext-errcode errstr)))))
(error
(signal 'emacsql-error (cdr err)))))
(cl-defmethod emacsql ((connection emacsql-sqlite-builtin-connection) sql &rest args)
(emacsql-send-message connection (apply #'emacsql-compile connection sql args)))
(provide 'emacsql-sqlite-builtin)
;;; emacsql-sqlite-builtin.el ends here

View File

@@ -0,0 +1,245 @@
;;; emacsql-sqlite-common.el --- Code used by multiple SQLite back-ends -*- 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
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; This library contains code that is used by multiple SQLite back-ends.
;;; 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)))))
(provide 'emacsql-sqlite-common)
;;; emacsql-sqlite-common.el ends here

View File

@@ -0,0 +1,94 @@
;;; emacsql-sqlite-module.el --- EmacSQL back-end for SQLite using a module -*- 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
;; Package-Version: 3.1.1.50-git
;; Package-Requires: ((emacs "25") (emacsql "20230220") (sqlite3 "0.16"))
;; SPDX-License-Identifier: Unlicense
;;; Commentary:
;; This library provides an EmacSQL back-end for SQLite, which uses
;; the Emacs module provided by the `sqlite3' package.
;;; Code:
(require 'emacsql)
(require 'emacsql-sqlite-common)
(require 'sqlite3 nil t)
(declare-function sqlite3-open "sqlite3-api")
(declare-function sqlite3-exec "sqlite3-api")
(declare-function sqlite3-close "sqlite3-api")
(defvar sqlite-open-readwrite)
(defvar sqlite-open-create)
(emacsql-register-reserved emacsql-sqlite-reserved)
(defclass emacsql-sqlite-module-connection (emacsql--sqlite-base) ()
"A connection to a SQLite database using a module.")
(cl-defmethod initialize-instance :after
((connection emacsql-sqlite-module-connection) &rest _)
(require (quote sqlite3))
(oset connection handle
(sqlite3-open (or (slot-value 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 connection [:pragma (= foreign-keys on)])
(emacsql-register connection))
(cl-defun emacsql-sqlite-module (file &key debug)
"Open a connected to database stored in FILE.
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."
(let ((connection (make-instance #'emacsql-sqlite-module-connection
:file file)))
(when debug
(emacsql-enable-debugging connection))
connection))
(cl-defmethod emacsql-live-p ((connection emacsql-sqlite-module-connection))
(and (oref connection handle) t))
(cl-defmethod emacsql-close ((connection emacsql-sqlite-module-connection))
(sqlite3-close (oref connection handle))
(oset connection handle nil))
(cl-defmethod emacsql-send-message
((connection emacsql-sqlite-module-connection) message)
(condition-case err
(let (rows)
(sqlite3-exec (oref connection handle)
message
(lambda (_ row __)
(push (mapcar (lambda (col)
(cond ((null col) nil)
((equal col "") "")
(t (read col))))
row)
rows)))
(nreverse rows))
((db-error sql-error)
(pcase-let* ((`(,_ ,errmsg ,errcode) err)
(`(,_ ,_ ,signal ,errstr)
(assq errcode emacsql-sqlite-error-codes)))
(signal (or signal 'emacsql-error)
(list errmsg errcode nil errstr))))
(error
(signal 'emacsql-error (cdr err)))))
(cl-defmethod emacsql ((connection emacsql-sqlite-module-connection) sql &rest args)
(emacsql-send-message connection (apply #'emacsql-compile connection sql args)))
(provide 'emacsql-sqlite-module)
;;; emacsql-sqlite-module.el ends here

View File

@@ -0,0 +1,183 @@
;;; emacsql-sqlite.el --- EmacSQL back-end for SQLite -*- 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
;; 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.
;;; Code:
(require 'emacsql)
(require 'emacsql-sqlite-common)
(emacsql-register-reserved emacsql-sqlite-reserved)
;;; SQLite connection
(defvar emacsql-sqlite-data-root
(file-name-directory (or load-file-name buffer-file-name))
"Directory where EmacSQL is installed.")
(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.")
(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).")
(defvar emacsql-sqlite-c-compilers '("cc" "gcc" "clang")
"List of names to try when searching for a C compiler.
Each is queried using `executable-find', so full paths are
allowed. Only the first compiler which is successfully found will
used.")
(defclass emacsql-sqlite-connection
(emacsql--sqlite-base emacsql-protocol-mixin) ()
"A connection to a SQLite database.")
(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)))
(cl-defun emacsql-sqlite (file &key debug)
"Open a connected to database stored in FILE.
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."
(let ((connection (make-instance 'emacsql-sqlite-connection :file file)))
(set-process-query-on-exit-flag (oref connection handle) nil)
(when debug
(emacsql-enable-debugging 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))))
(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")))
(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))))
;;; SQLite compilation
(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)))))
(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)))
(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))))))))))))))
;;; Ensure the SQLite binary is available
(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))))))
(provide 'emacsql-sqlite)
;;; emacsql-sqlite.el ends here

View File

@@ -66,6 +66,7 @@
(require 'cl-lib)
(require 'cl-generic)
(require 'eieio)
(require 'emacsql-compiler)
(defgroup emacsql nil
@@ -85,8 +86,11 @@ If nil, wait forever.")
;;; Database connection
(defclass emacsql-connection ()
((process :initarg :process
:accessor emacsql-process)
((handle :initarg :handle
:documentation "Internal connection handler.
The value is a record-like object and should not be accessed
directly. Depending on the concrete implementation, `type-of'
may return `process', `user-ptr' or `sqlite' for this value.")
(log-buffer :type (or null buffer)
:initarg :log-buffer
:initform nil
@@ -97,7 +101,7 @@ If nil, wait forever.")
:initform nil
:reader emacsql-types
:documentation "Maps EmacSQL types to SQL types."))
(:documentation "A connection to a SQL database.")
"A connection to a SQL database."
:abstract t)
(cl-defgeneric emacsql-close (connection)
@@ -108,7 +112,7 @@ If nil, wait forever.")
(cl-defmethod emacsql-live-p ((connection emacsql-connection))
"Return non-nil if CONNECTION is still alive and ready."
(not (null (process-live-p (emacsql-process connection)))))
(not (null (process-live-p (oref connection handle)))))
(cl-defgeneric emacsql-types (connection)
"Return an alist mapping EmacSQL types to database types.
@@ -119,7 +123,7 @@ SQL expression.")
(cl-defmethod emacsql-buffer ((connection emacsql-connection))
"Get process buffer for CONNECTION."
(process-buffer (emacsql-process connection)))
(process-buffer (oref connection handle)))
(cl-defmethod emacsql-enable-debugging ((connection emacsql-connection))
"Enable debugging on CONNECTION."
@@ -138,6 +142,29 @@ MESSAGE should not have a newline on the end."
(goto-char (point-max))
(princ (concat message "\n") buffer)))))
(cl-defgeneric emacsql-process (this)
"Access internal `handle' slot directly, which you shouldn't do.
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)))
(cl-defmethod (setf emacsql-process) (value (this emacsql-connection))
(eieio-oset this 'handle value))
(make-obsolete 'emacsql-process "underlying slot is for internal use only."
"Emacsql 4.0.0")
(cl-defmethod slot-missing ((connection emacsql-connection)
slot-name operation &optional new-value)
"Treat removed `process' slot-name as an alias for internal `handle' slot."
(pcase (list operation slot-name)
('(oref process)
(message "EmacSQL: Slot `process' is obsolete")
(oref connection handle))
('(oset process)
(message "EmacSQL: Slot `process' is obsolete")
(oset connection handle new-value))
(_ (cl-call-next-method))))
;;; Sending and receiving
(cl-defgeneric emacsql-send-message (connection message)
@@ -148,7 +175,7 @@ MESSAGE should not have a newline on the end."
(emacsql-log connection message))
(cl-defmethod emacsql-clear ((connection emacsql-connection))
"Clear the process buffer for CONNECTION-SPEC."
"Clear the connection buffer for CONNECTION-SPEC."
(let ((buffer (emacsql-buffer connection)))
(when (and buffer (buffer-live-p buffer))
(with-current-buffer buffer
@@ -164,7 +191,7 @@ MESSAGE should not have a newline on the end."
(while (and (or (null real-timeout) (< (float-time) end))
(not (emacsql-waiting-p connection)))
(save-match-data
(accept-process-output (emacsql-process connection) real-timeout)))
(accept-process-output (oref connection handle) real-timeout)))
(unless (emacsql-waiting-p connection)
(signal 'emacsql-timeout (list "Query timed out" real-timeout)))))
@@ -189,21 +216,23 @@ MESSAGE should not have a newline on the end."
;;; Helper mixin class
(defclass emacsql-protocol-mixin ()
()
(:documentation
"A mixin for back-ends following the EmacSQL protocol.
(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
must display as \"nil\".")
must display as \"nil\"."
:abstract t)
(cl-defmethod emacsql-waiting-p ((connection emacsql-protocol-mixin))
"Return true if the end of the buffer has a properly-formatted prompt."
(with-current-buffer (emacsql-buffer connection)
(and (>= (buffer-size) 2)
(string= "#\n" (buffer-substring (- (point-max) 2) (point-max))))))
"Return t if the end of the buffer has a properly-formatted prompt.
Also return t if the connection buffer has been killed."
(let ((buffer (emacsql-buffer connection)))
(or (not (buffer-live-p buffer))
(with-current-buffer buffer
(and (>= (buffer-size) 2)
(string= "#\n"
(buffer-substring (- (point-max) 2) (point-max))))))))
(cl-defmethod emacsql-handle ((_ emacsql-protocol-mixin) code message)
"Signal a specific condition for CODE from CONNECTION.
@@ -395,59 +424,6 @@ A prefix argument causes the SQL to be printed into the current buffer."
(emacsql-show-sql sql)))
(user-error "Invalid SQL: %S" sexp))))
;;; Common SQLite values
(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.")
;;; Fix Emacs' broken vector indentation
(defun emacsql--inside-vector-p ()
@@ -460,18 +436,18 @@ ERRSTR). Also see https://www.sqlite.org/rescode.html.")
(goto-char containing-sexp)
(looking-at "\\["))))))
(defadvice calculate-lisp-indent (around emacsql-vector-indent disable)
(defun emacsql--calculate-vector-indent (fn &optional parse-start)
"Don't indent vectors in `emacs-lisp-mode' like lists."
(if (save-excursion (beginning-of-line) (emacsql--inside-vector-p))
(let ((lisp-indent-offset 1))
ad-do-it)
ad-do-it))
(funcall fn parse-start))
(funcall fn parse-start)))
(defun emacsql-fix-vector-indentation ()
"When called, advise `calculate-lisp-indent' to stop indenting vectors.
Once activate, vector contents no longer indent like lists."
Once activated, vector contents no longer indent like lists."
(interactive)
(ad-enable-advice 'calculate-lisp-indent 'around 'emacsql-vector-indent)
(ad-activate 'calculate-lisp-indent))
(advice-add 'calculate-lisp-indent :around
#'emacsql--calculate-vector-indent))
;;; emacsql.el ends here

View File

@@ -0,0 +1,19 @@
-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

@@ -0,0 +1,183 @@
/* 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;
}

241687
lisp/emacsql/sqlite/sqlite3.c Normal file

File diff suppressed because it is too large Load Diff

12836
lisp/emacsql/sqlite/sqlite3.h Normal file

File diff suppressed because it is too large Load Diff