Emacs Database Mode | A New Emacs Mode For Working With Databases
Let me demonstrate with a fairly substantial example that mimics typical usage of comint mode. Normally comint is used for interacting with an interpreter. I do a lot of database work with Sybase so a new emacs mode that helped with that would be great. Adapting this for other databases should be fairly straightforward.
I can leverage off the useful sql-mode that already provides syntax highlighting and database interaction. However, as far as I know, it doesn't really help me with my favourite usage style where I have a command window containing my queries and a separate results window.
When you have finished looking at this lens, have a look at the db-mode extension for working with multiple databases.
Emacs Core Libraries
(require 'cl)
(require 'comint)
(require 'derived)
Constants
Specify where the interpreter lives
(defconst *db-cmd* "/usr/bin/isql")
Choosing Between Different Databases
(defvar db-server "")
(defvar db-database "")
(defvar db-user "")
(defvar db-password "")
(defun db-set-params (server database user password)
(setq db-server server
db-database database
db-user user
db-password password))
(db-set-params "DB_SERVER" "database" "user" "password")
Low Level Wrappers
(defvar db-buffer "db")
(defsubst db-get-buffer () (concat "*" db-buffer "*"))
(defsubst db-get-process ()
(get-buffer-process (db-get-buffer)))
Post Connection Hooks
(defvar db-post-connect-hooks nil
"List of functions to call after connecting to the database")
Inserting Text Into The Result Buffer
(defun ordinary-insertion-filter (proc string)
(with-current-buffer (process-buffer proc)
(let ((moving (= (point) (process-mark proc))))
(save-excursion
;; Insert the text, advancing the process marker.
(goto-char (process-mark proc))
(insert string)
(set-marker (process-mark proc) (point)))
(if moving (goto-char (process-mark proc))))))
(defun db-insert-text (str)
(ordinary-insertion-filter (db-get-process) str))
Ugly Results Without Filtering
echo 'select *
from person
where surname = "Doe"' | isql
gives the following output
1> 2> 3> name surname
---- -------
John Doe
(1 row affected)
Required (prettier) Result
select *
from person
where surname = "Doe"
name surname
---- -------
John Doe
(1 row affected)
Adding The Filter
(defconst db-re-isql-prompt (concat "^ *"
"\\([0-9]+> +\\)"
"\\([0-9]+> +\\)*"))
(defun db-output-filter (output)
(let ((match (string-match db-re-isql-prompt output)))
(when (and (numberp match) (= 0 match))
(setq output (concat (substring output 0 (match-beginning 0))
(substring output (match-end 0))))))
(when (string-match "affected)[ \n]+\\(1>\\)" output)
(setq output (concat (substring output 0 (match-beginning 1)) "\n")))
output)
(add-hook 'comint-preoutput-filter-functions 'db-output-filter)
;; (remove-hook 'comint-preoutput-filter-functions 'db-output-filter)
I will add the echo query when the query is sent to the stdin of isql.
Connecting to the Database
(defun db-cmd-args ()
(list "-w9999"
(concat "-S" db-server)
(concat "-D" db-database)
(concat "-U" db-user)
(concat "-P" db-password)))
(defun db-connect-to-db ()
(interactive)
(when (not (comint-check-proc (db-get-buffer)))
(message "Connecting to the database...")
(apply 'make-comint db-buffer *db-cmd* nil (db-cmd-args))
(set-buffer (db-get-buffer))
(toggle-truncate-lines 1)
(setq comint-move-point-for-output t)
(add-hook 'comint-output-filter-functions
'comint-postoutput-scroll-to-bottom nil t)
(run-hooks 'db-post-connect-hooks))
t)
Adding Quote Words Syntax
(disclaimer: this might be a bad idea, I haven't thought it through in a lot of detail. It is more to demonstrate what is possible)
(defun db-replace-quote-words (sql)
(let ((case-fold-search nil))
(with-temp-buffer
(insert sql)
(goto-char (point-min))
(while (re-search-forward "qw(" nil t)
(let (begin end words)
(setq begin (point))
(backward-char)
(forward-sexp)
(setq end (point))
(goto-char begin)
(while (re-search-forward "[^ \n]+" end t)
(push (match-string 0) words))
(delete-region (- begin 3) end)
(insert (format "(%s)"
(mapconcat (lambda (s)
(concat "\""
(replace-quote s)
"\""))
(reverse words) ", ")))))
(buffer-substring-no-properties (point-min) (point-max)))))
I replace all the qw(...) sections prior to sending the query to isql.
Sending Queries to the Database
(defun db-send-sql-region (start end)
(interactive "r")
(db-connect-to-db)
(db-set-window-layout)
(let* ((sql (buffer-substring-no-properties start end)))
(setq sql (db-replace-quote-words sql))
(db-insert-text sql)
(when (string-match "^go" sql)
(db-insert-text "\n\n"))
(comint-send-string (db-get-process) sql)
(comint-send-string (db-get-process) "\n")))
Send The Current SQL
(defun db-send-sql-current ()
(interactive)
(save-excursion
(backward-paragraph)
(when (not (bobp))
(next-line))
(move-beginning-of-line nil)
(let ((s (point)))
(forward-paragraph)
(when (not (eobp))
(next-line -1))
(move-end-of-line nil)
(db-send-sql-region s (point)))))
Deriving DB Mode
(define-derived-mode db-mode sql-mode "Database"
"Major mode for interacting with databases.
Special commands:
\\{db-mode-map}")
Useful Key Chords
(define-key db-mode-map (kbd "C-x C-e") 'db-send-sql-current)
(define-key db-mode-map (kbd "C-c C-r") 'db-send-sql-region)
An Easy Way to Start DB Mode
(defun db-set-window-layout ()
(switch-to-buffer-other-window (db-get-buffer) t)
(other-window -1))
(defun enter-db-mode ()
(interactive)
(switch-to-buffer "*sql*" t)
(db-mode)
(db-connect-to-db)
(db-set-window-layout))
(global-set-key [f12] 'enter-db-mode)
Other Emacs Lenses
Check out these other lenses for more information about extending emacs-
The Magic of Emacs Comint
-
Comint is one of the main reasons I moved to emacs after using vi/vim for 14 years. It is an emacs library for interacting with external processes. Many text editors have a way of calling out to a process and synchronously getting the result. Emacs g...
-
Emacs Hooks - An Introduction
-
Hooks provide an clean, organised mechanism for extending emacs. Any function in emacs may include one or more hooks where users can add their own functions to be run. Windows Notepad users can add .LOG to the top of a textfile in order to support a...
-
HowTo Query Multiple Databases With Emacs Db Mode
-
In my experience it is rare that you will work with a single database. even if you have data in only one region, you will hopefully at least have a QA database. Therefore to be a serious tool, you need to have some way of making it easy to query mult...
Reader Feedback
Let me know if you found this lens useful
-
Reply
- Patrick Patrick Apr 17, 2009 @ 10:50 pm | in reply to jareddavison2009
- Hey - you know what I'd love to see is an explanation of how to get pcomplete (or whatever) hooked up with sql-mode. In my experiments with this, the database-dictionary (which is supposed to examine and give you tab-complete on schema) has never worked. You obviously have more lisp chops or patience to get it to work. Cheers!
-
Reply
- jareddavison2009 jareddavison2009 Apr 16, 2009 @ 3:44 pm
- @Sojourn - thanks :)
@Ian Eure - great, I was looking for a simple way to do this in sql-mode but I guess I missed this. Connecting to the database is just a couple of lines in db-mode. The rest is the additional features and I think it stands as a nice example of comint mode.
@Patrick - fair enough. Look out for a blog post on using sql-mode in a similar way soon. :)
-
Reply
- Patrick Patrick Apr 14, 2009 @ 5:11 pm
- What Ian said.
Nice description of comint, though, never-the-less.
-
Reply
- Ian Eure Ian Eure Apr 12, 2009 @ 4:26 pm
- Sadtrombone! This is alrady supported by sql-mode. Fire up an interactive SQL window (M-x sql-sybase), then open a sql-mode buffer (C-x b *sql* RET M-x sql-mode RET)
Now you can write your SQL and use:
C-c C-b sql-send-buffer
C-c C-c sql-send-paragraph
C-c C-r sql-send-region
C-c C-s sql-send-string
To send the contents to the sqli buffer. The other features you want, such as filtering, can be added with advice or a minor mode extending sqli-mode.
-
Reply
- Sojourn Sojourn Apr 12, 2009 @ 1:10 pm
- You obviously know your stuff, Jared. Nice job!
- Load More
These Posts May Help With Your (Emacs) Lisp
- fare: Boston Lisp Meeting: Monday 2009-12-14 Sam Tobin-Hochstadt
- Tell us by sending email to boston-lisp-meeting-register at common-lisp.net. We won't send any acknowledgement unless requested; importantly, we'll keep your identity and address confidential and won't communicate any such information ...





