update of packages
This commit is contained in:
@@ -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 -->
|
||||
135
lisp/emacsql/emacsql-mysql.el
Normal file
135
lisp/emacsql/emacsql-mysql.el
Normal 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
|
||||
80
lisp/emacsql/emacsql-pg.el
Normal file
80
lisp/emacsql/emacsql-pg.el
Normal 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
|
||||
@@ -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")
|
||||
|
||||
147
lisp/emacsql/emacsql-psql.el
Normal file
147
lisp/emacsql/emacsql-psql.el
Normal 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
|
||||
89
lisp/emacsql/emacsql-sqlite-builtin.el
Normal file
89
lisp/emacsql/emacsql-sqlite-builtin.el
Normal 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
|
||||
245
lisp/emacsql/emacsql-sqlite-common.el
Normal file
245
lisp/emacsql/emacsql-sqlite-common.el
Normal 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
|
||||
94
lisp/emacsql/emacsql-sqlite-module.el
Normal file
94
lisp/emacsql/emacsql-sqlite-module.el
Normal 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
|
||||
183
lisp/emacsql/emacsql-sqlite.el
Normal file
183
lisp/emacsql/emacsql-sqlite.el
Normal 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
|
||||
@@ -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
|
||||
|
||||
19
lisp/emacsql/sqlite/Makefile
Normal file
19
lisp/emacsql/sqlite/Makefile
Normal 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
|
||||
183
lisp/emacsql/sqlite/emacsql.c
Normal file
183
lisp/emacsql/sqlite/emacsql.c
Normal 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
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
12836
lisp/emacsql/sqlite/sqlite3.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user