lisp/org-table.el: fix table alignment
[org-mode/org-tableheadings.git] / lisp / ob-sql.el
blob1bbfd44528cd1d93aa7374d9879936be86833936
1 ;;; ob-sql.el --- Babel Functions for SQL -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2009-2019 Free Software Foundation, Inc.
5 ;; Author: Eric Schulte
6 ;; Keywords: literate programming, reproducible research
7 ;; Homepage: https://orgmode.org
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
24 ;;; Commentary:
26 ;; Org-Babel support for evaluating sql source code.
27 ;; (see also ob-sqlite.el)
29 ;; SQL is somewhat unique in that there are many different engines for
30 ;; the evaluation of sql (Mysql, PostgreSQL, etc...), so much of this
31 ;; file will have to be implemented engine by engine.
33 ;; Also SQL evaluation generally takes place inside of a database.
35 ;; Header args used:
36 ;; - engine
37 ;; - cmdline
38 ;; - dbhost
39 ;; - dbport
40 ;; - dbuser
41 ;; - dbpassword
42 ;; - dbconnection (to reference connections in sql-connection-alist)
43 ;; - database
44 ;; - colnames (default, nil, means "yes")
45 ;; - result-params
46 ;; - out-file
48 ;; The following are used but not really implemented for SQL:
49 ;; - colname-names
50 ;; - rownames
51 ;; - rowname-names
53 ;; Engines supported:
54 ;; - mysql
55 ;; - dbi
56 ;; - mssql
57 ;; - sqsh
58 ;; - postgresql
59 ;; - oracle
60 ;; - vertica
62 ;; TODO:
64 ;; - support for sessions
65 ;; - support for more engines
66 ;; - what's a reasonable way to drop table data into SQL?
69 ;;; Code:
70 (require 'ob)
72 (declare-function org-table-import "org-table" (file arg))
73 (declare-function orgtbl-to-csv "org-table" (table params))
74 (declare-function org-table-to-lisp "org-table" (&optional txt))
75 (declare-function cygwin-convert-file-name-to-windows "cygw32.c" (file &optional absolute-p))
77 (defvar sql-connection-alist)
78 (defvar org-babel-default-header-args:sql '())
80 (defconst org-babel-header-args:sql
81 '((engine . :any)
82 (out-file . :any)
83 (dbhost . :any)
84 (dbport . :any)
85 (dbuser . :any)
86 (dbpassword . :any)
87 (database . :any))
88 "SQL-specific header arguments.")
90 (defun org-babel-expand-body:sql (body params)
91 "Expand BODY according to the values of PARAMS."
92 (org-babel-sql-expand-vars
93 body (org-babel--get-vars params)))
95 (defun org-babel-sql-dbstring-mysql (host port user password database)
96 "Make MySQL cmd line args for database connection. Pass nil to omit that arg."
97 (combine-and-quote-strings
98 (delq nil
99 (list (when host (concat "-h" host))
100 (when port (format "-P%d" port))
101 (when user (concat "-u" user))
102 (when password (concat "-p" password))
103 (when database (concat "-D" database))))))
105 (defun org-babel-sql-dbstring-postgresql (host port user database)
106 "Make PostgreSQL command line args for database connection.
107 Pass nil to omit that arg."
108 (combine-and-quote-strings
109 (delq nil
110 (list (when host (concat "-h" host))
111 (when port (format "-p%d" port))
112 (when user (concat "-U" user))
113 (when database (concat "-d" database))))))
115 (defun org-babel-sql-dbstring-oracle (host port user password database)
116 "Make Oracle command line arguments for database connection.
118 If HOST and PORT are nil then don't pass them. This allows you
119 to use names defined in your \"TNSNAMES\" file. So you can
120 connect with
122 <user>/<password>@<host>:<port>/<database>
126 <user>/<password>@<database>
128 using its alias."
129 (cond ((and user password database host port)
130 (format "%s/%s@%s:%s/%s" user password host port database))
131 ((and user password database)
132 (format "%s/%s@%s" user password database))
133 (t (user-error "Missing information to connect to database"))))
135 (defun org-babel-sql-dbstring-mssql (host user password database)
136 "Make sqlcmd command line args for database connection.
137 `sqlcmd' is the preferred command line tool to access Microsoft
138 SQL Server on Windows and Linux platform."
139 (mapconcat #'identity
140 (delq nil
141 (list (when host (format "-S \"%s\"" host))
142 (when user (format "-U \"%s\"" user))
143 (when password (format "-P \"%s\"" password))
144 (when database (format "-d \"%s\"" database))))
145 " "))
147 (defun org-babel-sql-dbstring-sqsh (host user password database)
148 "Make sqsh command line args for database connection.
149 \"sqsh\" is one method to access Sybase or MS SQL via Linux platform"
150 (mapconcat #'identity
151 (delq nil
152 (list (when host (format "-S \"%s\"" host))
153 (when user (format "-U \"%s\"" user))
154 (when password (format "-P \"%s\"" password))
155 (when database (format "-D \"%s\"" database))))
156 " "))
158 (defun org-babel-sql-dbstring-vertica (host port user password database)
159 "Make Vertica command line args for database connection. Pass nil to omit that arg."
160 (mapconcat #'identity
161 (delq nil
162 (list (when host (format "-h %s" host))
163 (when port (format "-p %d" port))
164 (when user (format "-U %s" user))
165 (when password (format "-w %s" (shell-quote-argument password) ))
166 (when database (format "-d %s" database))))
167 " "))
169 (defun org-babel-sql-convert-standard-filename (file)
170 "Convert FILE to OS standard file name.
171 If in Cygwin environment, uses Cygwin specific function to
172 convert the file name. In a Windows-NT environment, do nothing.
173 Otherwise, use Emacs' standard conversion function."
174 (cond ((fboundp 'cygwin-convert-file-name-to-windows)
175 (format "%S" (cygwin-convert-file-name-to-windows file)))
176 ((string= "windows-nt" system-type) file)
177 (t (format "%S" (convert-standard-filename file)))))
179 (defun org-babel-find-db-connection-param (params name)
180 "Return database connection parameter NAME.
181 Given a parameter NAME, if :dbconnection is defined in PARAMS
182 then look for the parameter into the corresponding connection
183 defined in `sql-connection-alist`, otherwise look into PARAMS.
184 Look `sql-connection-alist` (part of SQL mode) for how to define
185 database connections."
186 (if (assq :dbconnection params)
187 (let* ((dbconnection (cdr (assq :dbconnection params)))
188 (name-mapping '((:dbhost . sql-server)
189 (:dbport . sql-port)
190 (:dbuser . sql-user)
191 (:dbpassword . sql-password)
192 (:database . sql-database)))
193 (mapped-name (cdr (assq name name-mapping))))
194 (cadr (assq mapped-name
195 (cdr (assoc dbconnection sql-connection-alist)))))
196 (cdr (assq name params))))
198 (defun org-babel-execute:sql (body params)
199 "Execute a block of Sql code with Babel.
200 This function is called by `org-babel-execute-src-block'."
201 (let* ((result-params (cdr (assq :result-params params)))
202 (cmdline (cdr (assq :cmdline params)))
203 (dbhost (org-babel-find-db-connection-param params :dbhost))
204 (dbport (org-babel-find-db-connection-param params :dbport))
205 (dbuser (org-babel-find-db-connection-param params :dbuser))
206 (dbpassword (org-babel-find-db-connection-param params :dbpassword))
207 (database (org-babel-find-db-connection-param params :database))
208 (engine (cdr (assq :engine params)))
209 (colnames-p (not (equal "no" (cdr (assq :colnames params)))))
210 (in-file (org-babel-temp-file "sql-in-"))
211 (out-file (or (cdr (assq :out-file params))
212 (org-babel-temp-file "sql-out-")))
213 (header-delim "")
214 (command (pcase (intern engine)
215 (`dbi (format "dbish --batch %s < %s | sed '%s' > %s"
216 (or cmdline "")
217 (org-babel-process-file-name in-file)
218 "/^+/d;s/^|//;s/(NULL)/ /g;$d"
219 (org-babel-process-file-name out-file)))
220 (`monetdb (format "mclient -f tab %s < %s > %s"
221 (or cmdline "")
222 (org-babel-process-file-name in-file)
223 (org-babel-process-file-name out-file)))
224 (`mssql (format "sqlcmd %s -s \"\t\" %s -i %s -o %s"
225 (or cmdline "")
226 (org-babel-sql-dbstring-mssql
227 dbhost dbuser dbpassword database)
228 (org-babel-sql-convert-standard-filename
229 (org-babel-process-file-name in-file))
230 (org-babel-sql-convert-standard-filename
231 (org-babel-process-file-name out-file))))
232 (`mysql (format "mysql %s %s %s < %s > %s"
233 (org-babel-sql-dbstring-mysql
234 dbhost dbport dbuser dbpassword database)
235 (if colnames-p "" "-N")
236 (or cmdline "")
237 (org-babel-process-file-name in-file)
238 (org-babel-process-file-name out-file)))
239 (`postgresql (format
240 "%spsql --set=\"ON_ERROR_STOP=1\" %s -A -P \
241 footer=off -F \"\t\" %s -f %s -o %s %s"
242 (if dbpassword
243 (format "PGPASSWORD=%s " dbpassword)
245 (if colnames-p "" "-t")
246 (org-babel-sql-dbstring-postgresql
247 dbhost dbport dbuser database)
248 (org-babel-process-file-name in-file)
249 (org-babel-process-file-name out-file)
250 (or cmdline "")))
251 (`sqsh (format "sqsh %s %s -i %s -o %s -m csv"
252 (or cmdline "")
253 (org-babel-sql-dbstring-sqsh
254 dbhost dbuser dbpassword database)
255 (org-babel-sql-convert-standard-filename
256 (org-babel-process-file-name in-file))
257 (org-babel-sql-convert-standard-filename
258 (org-babel-process-file-name out-file))))
259 (`vertica (format "vsql %s -f %s -o %s %s"
260 (org-babel-sql-dbstring-vertica
261 dbhost dbport dbuser dbpassword database)
262 (org-babel-process-file-name in-file)
263 (org-babel-process-file-name out-file)
264 (or cmdline "")))
265 (`oracle (format
266 "sqlplus -s %s < %s > %s"
267 (org-babel-sql-dbstring-oracle
268 dbhost dbport dbuser dbpassword database)
269 (org-babel-process-file-name in-file)
270 (org-babel-process-file-name out-file)))
271 (_ (error "No support for the %s SQL engine" engine)))))
272 (with-temp-file in-file
273 (insert
274 (pcase (intern engine)
275 (`dbi "/format partbox\n")
276 (`oracle "SET PAGESIZE 50000
277 SET NEWPAGE 0
278 SET TAB OFF
279 SET SPACE 0
280 SET LINESIZE 9999
281 SET TRIMOUT ON TRIMSPOOL ON
282 SET ECHO OFF
283 SET FEEDBACK OFF
284 SET VERIFY OFF
285 SET HEADING ON
286 SET MARKUP HTML OFF SPOOL OFF
287 SET COLSEP '|'
290 ((or `mssql `sqsh) "SET NOCOUNT ON
293 (`vertica "\\a\n")
294 (_ ""))
295 (org-babel-expand-body:sql body params)
296 ;; "sqsh" requires "go" inserted at EOF.
297 (if (string= engine "sqsh") "\ngo" "")))
298 (org-babel-eval command "")
299 (org-babel-result-cond result-params
300 (with-temp-buffer
301 (progn (insert-file-contents-literally out-file) (buffer-string)))
302 (with-temp-buffer
303 (cond
304 ((memq (intern engine) '(dbi mysql postgresql sqsh vertica))
305 ;; Add header row delimiter after column-names header in first line
306 (cond
307 (colnames-p
308 (with-temp-buffer
309 (insert-file-contents out-file)
310 (goto-char (point-min))
311 (forward-line 1)
312 (insert "-\n")
313 (setq header-delim "-")
314 (write-file out-file)))))
316 ;; Need to figure out the delimiter for the header row
317 (with-temp-buffer
318 (insert-file-contents out-file)
319 (goto-char (point-min))
320 (when (re-search-forward "^\\(-+\\)[^-]" nil t)
321 (setq header-delim (match-string-no-properties 1)))
322 (goto-char (point-max))
323 (forward-char -1)
324 (while (looking-at "\n")
325 (delete-char 1)
326 (goto-char (point-max))
327 (forward-char -1))
328 (write-file out-file))))
329 (org-table-import out-file (if (string= engine "sqsh") '(4) '(16)))
330 (org-babel-reassemble-table
331 (mapcar (lambda (x)
332 (if (string= (car x) header-delim)
333 'hline
335 (org-table-to-lisp))
336 (org-babel-pick-name (cdr (assq :colname-names params))
337 (cdr (assq :colnames params)))
338 (org-babel-pick-name (cdr (assq :rowname-names params))
339 (cdr (assq :rownames params))))))))
341 (defun org-babel-sql-expand-vars (body vars)
342 "Expand the variables held in VARS in BODY."
343 (mapc
344 (lambda (pair)
345 (setq body
346 (replace-regexp-in-string
347 (format "$%s" (car pair))
348 (let ((val (cdr pair)))
349 (if (listp val)
350 (let ((data-file (org-babel-temp-file "sql-data-")))
351 (with-temp-file data-file
352 (insert (orgtbl-to-csv
353 val '(:fmt (lambda (el) (if (stringp el)
355 (format "%S" el)))))))
356 data-file)
357 (if (stringp val) val (format "%S" val))))
358 body)))
359 vars)
360 body)
362 (defun org-babel-prep-session:sql (_session _params)
363 "Raise an error because Sql sessions aren't implemented."
364 (error "SQL sessions not yet implemented"))
366 (provide 'ob-sql)
370 ;;; ob-sql.el ends here