branch: elpa/emacsql commit 3e6a24e22af95460a05bfab5c258c1ffd34ffc2a Author: Christopher Wellons <well...@nullprogram.com> Commit: Christopher Wellons <well...@nullprogram.com>
Escape identifiers that collide with keywords. --- README.md | 1 + emacsql-compiler.el | 16 +++++++++++++++- emacsql-mysql.el | 35 +++++++++++++++++++++++++++++++++++ emacsql-psql.el | 15 +++++++++++++++ emacsql-sqlite.el | 19 +++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0714831c59..d0c55b7923 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,7 @@ inherits from `emacsql-connection`. * 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. * If available, ensure foreign key constraints are enabled by default. diff --git a/emacsql-compiler.el b/emacsql-compiler.el index baa45bba2e..8879c97748 100644 --- a/emacsql-compiler.el +++ b/emacsql-compiler.el @@ -32,6 +32,19 @@ ;; Escaping functions: +(defvar emacsql-reserved (make-hash-table :test 'equal) + "Collection of all known reserved words, used for escaping.") + +(defun emacsql-register-reserved (seq) + "Register sequence of keywords as reserved words, returning SEQ." + (cl-loop for word being the elements of seq + do (setf (gethash (upcase (format "%s" word)) emacsql-reserved) t) + finally (cl-return seq))) + +(defun emacsql-reserved-p (name) + "Returns non-nil if string NAME is a SQL keyword." + (gethash (upcase name) emacsql-reserved)) + (defun emacsql-quote-scalar (string) "Single-quote (scalar) STRING for use in a SQL expression." (format "'%s'" (replace-regexp-in-string "'" "''" string))) @@ -53,7 +66,8 @@ (let ((print (replace-regexp-in-string "-" "_" (format "%S" identifier))) (special "[]-\000-\040!\"#%&'()*+,./:;<=>?@[\\^`{|}~\177]")) (if (or (string-match-p special print) - (string-match-p "^[0-9$]" print)) + (string-match-p "^[0-9$]" print) + (emacsql-reserved-p print)) (emacsql-quote-identifier print) print))))) diff --git a/emacsql-mysql.el b/emacsql-mysql.el index 4930497b07..32d1248a21 100644 --- a/emacsql-mysql.el +++ b/emacsql-mysql.el @@ -12,6 +12,41 @@ (defvar emacsql-mysql-sentinel "--------------\n\n--------------\n\n" "What MySQL will print when it has completed its output.") +(defvar 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 diff --git a/emacsql-psql.el b/emacsql-psql.el index ab1fc44040..a2bbc99f02 100644 --- a/emacsql-psql.el +++ b/emacsql-psql.el @@ -26,6 +26,21 @@ nil))) (error :cannot-execute))))) +(defvar 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 diff --git a/emacsql-sqlite.el b/emacsql-sqlite.el index a62f35fcab..ff8e3c307d 100644 --- a/emacsql-sqlite.el +++ b/emacsql-sqlite.el @@ -34,6 +34,25 @@ version." emacsql-data-root) "Path to the EmacSQL backend (this is not the sqlite3 shell).") +(defvar emacsql-sqlite-reserved + (emacsql-register-reserved + '(ABORT ACTION ADD AFTER ALL ALTER ANALYZE AND AS ASC ATTACH + AUTOINCREMENT BEFORE BEGIN BETWEEN BY CASCADE CASE CAST CHECK + COLLATE COLUMN COMMIT CONFLICT CONSTRAINT CREATE CROSS + CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP DATABASE DEFAULT + DEFERRABLE DEFERRED DELETE DESC DETACH DISTINCT DROP EACH ELSE END + ESCAPE EXCEPT EXCLUSIVE EXISTS EXPLAIN FAIL FOR FOREIGN FROM FULL + GLOB GROUP HAVING IF IGNORE IMMEDIATE IN INDEX INDEXED INITIALLY + INNER INSERT INSTEAD INTERSECT INTO IS ISNULL JOIN KEY LEFT LIKE + LIMIT MATCH NATURAL NO NOT NOTNULL NULL OF OFFSET ON OR ORDER + OUTER PLAN PRAGMA PRIMARY QUERY RAISE RECURSIVE REFERENCES REGEXP + REINDEX RELEASE RENAME REPLACE RESTRICT RIGHT ROLLBACK ROW + SAVEPOINT SELECT SET TABLE TEMP TEMPORARY THEN TO TRANSACTION + TRIGGER UNION UNIQUE UPDATE USING VACUUM VALUES VIEW VIRTUAL WHEN + WHERE WITH WITHOUT)) + "List of all of SQLite's reserved words. +http://www.sqlite.org/lang_keywords.html") + (defclass emacsql-sqlite-connection (emacsql-connection emacsql-protocol-mixin) ((file :initarg :file :type (or null string)