aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Ingebrigtsen2021-12-11 06:23:57 +0100
committerLars Ingebrigtsen2021-12-11 06:23:57 +0100
commite5f71051a00a4ceb63cadc2513cb9619a1adffcc (patch)
tree1ae0bd2ca4bf1ad279285cb3ec3f1a812439db12
parent7364b60fe9cd37c4ea7650f00df2d6cb4cd601da (diff)
downloademacs-e5f71051a00a4ceb63cadc2513cb9619a1adffcc.tar.gz
emacs-e5f71051a00a4ceb63cadc2513cb9619a1adffcc.zip
Add a new mode for examining sqlite files
* lisp/sqlite-mode.el: New file.
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/sqlite-mode.el161
2 files changed, 165 insertions, 0 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 6f7ce0ab53f..a40c8f82a57 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -95,6 +95,10 @@ the 'variable-pitch' face, or add this to your "~/.emacs":
95** Emacs now comes with optional built-in support for sqlite3. 95** Emacs now comes with optional built-in support for sqlite3.
96This allows you to examine/manipulate sqlite3 databases. 96This allows you to examine/manipulate sqlite3 databases.
97 97
98** New command 'sqlite-mode-open-file' for examining an sqlite3 file.
99This uses the new 'sqlite-mode' which allows listing the tables
100in a file, the columns, and the contents of the tables.
101
98--- 102---
99** 'write-file' will now copy some file mode bits. 103** 'write-file' will now copy some file mode bits.
100If the current buffer is visiting a file that is executable, the 104If the current buffer is visiting a file that is executable, the
diff --git a/lisp/sqlite-mode.el b/lisp/sqlite-mode.el
new file mode 100644
index 00000000000..0e018b62d5d
--- /dev/null
+++ b/lisp/sqlite-mode.el
@@ -0,0 +1,161 @@
1;;; sqlite-mode.el --- Mode for examining sqlite3 database files -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2021 Free Software Foundation, Inc.
4
5;; This file is part of GNU Emacs.
6
7;; GNU Emacs is free software: you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;;
23
24;;; Code:
25
26(defvar-keymap sqlite-mode-map
27 "g" #'sqlite-mode-list-tables
28 "c" #'sqlite-mode-list-columns
29 "RET" #'sqlite-mode-list-data)
30
31(define-derived-mode sqlite-mode special-mode "Sqlite"
32 "This mode lists the contents of an .sqlite3 file"
33 :interactive nil
34 (buffer-disable-undo)
35 (setq-local buffer-read-only t
36 truncate-lines t))
37
38(defvar sqlite--db nil)
39
40;;;###autoload
41(defun sqlite-mode-open-file (file)
42 "Browse the contents of an sqlite file."
43 (interactive "fSQLite file name: ")
44 (pop-to-buffer (get-buffer-create
45 (format "*SQLite %s*" (file-name-nondirectory file))))
46 (sqlite-mode)
47 (setq-local sqlite--db (sqlite-open file))
48 (sqlite-list-tables))
49
50(defun sqlite-mode-list-tables ()
51 "Re-list the tables from the currently selected database."
52 (interactive nil sqlite-mode)
53 (let ((inhibit-read-only t)
54 (db sqlite--db)
55 (entries nil))
56 (erase-buffer)
57 (dolist (table (sqlite-select db "select name from sqlite_schema where type = 'table' and name not like 'sqlite_%' order by name"))
58 (push (list (car table)
59 (caar (sqlite-select db (format "select count(*) from %s"
60 (car table)))))
61 entries))
62 (sqlite-mode--tablify '("Table Name" "Number of Rows")
63 (nreverse entries))
64 (goto-char (point-min))))
65
66(defun sqlite-mode--tablify (columns rows &optional prefix)
67 (let ((widths
68 (mapcar
69 (lambda (i)
70 (1+ (seq-max (mapcar (lambda (row)
71 (length (format "%s" (nth i row))))
72 (cons columns rows)))))
73 (number-sequence 0 (1- (length columns))))))
74 (when prefix
75 (insert prefix))
76 (dotimes (i (length widths))
77 (insert (propertize (format (format "%%-%ds" (nth i widths))
78 (nth i columns))
79 'face 'header-line)))
80 (insert "\n")
81 (dolist (row rows)
82 (let ((start (point)))
83 (when prefix
84 (insert prefix))
85 (dotimes (i (length widths))
86 (insert (format (format "%%%s%ds"
87 (if (numberp (nth i row))
88 "" "-")
89 (nth i widths))
90 (or (nth i row) ""))))
91 (put-text-property start (point) 'sqlite--row row)
92 (insert "\n")))))
93
94(defun sqlite-mode-list-columns ()
95 "List the columns of the table under point."
96 (interactive nil sqlite-mode)
97 (let ((row (get-text-property (point) 'sqlite--row)))
98 (unless row
99 (user-error "No table under point"))
100 (let ((columns (sqlite-mode--column-names (car row)))
101 (inhibit-read-only t))
102 (save-excursion
103 (forward-line 1)
104 (if (looking-at " ")
105 ;; Delete the info.
106 (delete-region (point) (if (re-search-forward "^[^ ]" nil t)
107 (match-beginning 0)
108 (point-max)))
109 ;; Insert the info.
110 (dolist (column columns)
111 (insert (format " %s\n" column))))))))
112
113(defun sqlite-mode--column-names (table)
114 (let ((sql
115 (caar
116 (sqlite-select
117 sqlite--db
118 "select sql from sqlite_master where tbl_name = ? AND type = 'table'"
119 (list table)))))
120 (mapcar
121 #'string-trim
122 (split-string (replace-regexp-in-string "^.*(\\|)$" "" sql) ","))))
123
124(defun sqlite-mode-list-data ()
125 "List the data from the table under poing."
126 (interactive nil sqlite-mode)
127 (let ((row (get-text-property (point) 'sqlite--row)))
128 (unless row
129 (user-error "No table under point"))
130 (let ((stmt (sqlite-select sqlite--db
131 (format "select * from %s" (car row)) nil 'set))
132 (inhibit-read-only t))
133 (save-excursion
134 (forward-line 1)
135 (if (looking-at " ")
136 ;; Delete the info.
137 (delete-region (point) (if (re-search-forward "^[^ ]" nil t)
138 (match-beginning 0)
139 (point-max)))
140 (sqlite--mode--list-data stmt))))))
141
142(defun sqlite-mode--more-data (stmt)
143 (let ((inhibit-read-only t))
144 (beginning-of-line)
145 (delete-region (point) (progn (forward-line 1) (point)))
146 (sqlite--mode--list-data stmt)))
147
148(defun sqlite--mode--list-data (stmt)
149 (let ((rows
150 (cl-loop for i from 0 upto 1000
151 for row = (sqlite-next stmt)
152 while row
153 collect row)))
154 (sqlite-mode--tablify (sqlite-columns stmt) rows " ")
155 (when (sqlite-more-p stmt)
156 (insert (buttonize " More data...\n"
157 #'sqlite-mode--more-data stmt)))))
158
159(provide 'sqlite-mode)
160
161;;; sqlite-mode.el ends here