(require 'sql)
(defvar sql-mode-mysql-font-lock-keywords nil
"MySQL keywords used by font-lock.")
(let ((mysql-types (sql-keywords-re
"bigint" "binary" "bit" "blob" "char" "character" "date" "datetime" "dec"
"decimal" "double" "enum" "float" "float4" "float8" "int" "int1" "int2" "int3"
"int4" "int8" "integer" "long" "longblob" "longtext" "mediumblob" "mediumint"
"mediumtext" "numeric" "precision" "real" "smallint" "time" "timestamp"
"tinyblob" "tinyint" "tinytext" "unsigned" "varbinary" "varchar"
"varcharacter" "year" "zerofill"
))
(mysql-keywords (sql-keywords-re
"all" "alter" "and" "as" "asc" "between" "by" "check" "create" "cross"
"declare" "default" "delete" "desc" "distinct" "drop" "exists" "for" "from"
"grant" "group" "having" "in" "inner" "insert" "into" "is" "join" "left"
"like" "not" "null" "on" "option" "or" "order" "outer" "right" "select" "set"
"table" "to" "top" "union" "unique" "update" "values" "view" "where" "with"
))
(mysql-reserved-words (sql-keywords-re
"add" "after" "analyze" "asensitive" "before" "both" "call" "cascade" "btree"
"change" "collate" "column" "condition" "connection" "constraint" "continue"
"convert" "cursor" "database" "databases" "delayed" "describe" "deterministic"
"distinctrow" "div" "dual" "each" "else" "elseif" "enclosed" "escaped" "exit"
"explain" "false" "fetch" "force" "foreign" "fulltext" "goto" "high_priority"
"if" "ignore" "index" "infile" "inout" "insensitive" "interval" "iterate"
"key" "keys" "kill" "label" "leading" "leave" "leave" "limit" "lines" "load"
"lock" "loop" "match" "mod" "modifies" "natural" "optimize" "optionally" "out"
"outfile" "primary" "procedure" "purge" "raid0" "read" "reads" "real"
"references" "regexp" "release" "rename" "repeat" "replace" "require"
"restrict" "return" "revoke" "rlike" "schema" "schemas" "sensitive"
"separator" "show" "soname" "spatial" "specific" "sql" "sqlexception"
"sqlstate" "sqlwarning" "ssl" "starting" "straight_join" "terminated" "then"
"trailing" "trigger" "true" "undo" "unlock" "unsigned" "upgrade" "usage" "use"
"using" "varying" "when" "while" "write" "x509" "xor"
))
(mysql-builtin-functions (sql-keywords-re
"case" "current_date" "current_time" "current_timestamp" "current_user"
"day_hour" "day_microsecond" "day_minute" "day_second" "hour_microsecond"
"hour_minute" "hour_second" "localtime" "localtimestamp" "low_priority"
"minute_microsecond" "minute_second" "no_write_to_binlog" "second_microsecond"
"sql_big_result" "sql_calc_found_rows" "sql_small_result" "utc_date"
"utc_time" "utc_timestamp" "year_month"
)))
(setq sql-mode-mysql-font-lock-keywords
(list (cons mysql-types 'font-lock-type-face)
(cons mysql-keywords 'font-lock-keyword-face)
(cons mysql-reserved-words 'font-lock-function-name-face)
(cons mysql-builtin-functions 'font-lock-builtin-face))))
(defun sql-highlight-mysql-keywords ()
"Highlight MySQL keywords.
Set `font-lock-keywords' appropriately."
(interactive)
(setq font-lock-keywords sql-mode-mysql-font-lock-keywords)
(font-lock-fontify-buffer))
(easy-menu-add-item sql-mode-map '("menu-bar" "SQL")
'("Highlighting"
["MySQL keywords" sql-highlight-mysql-keywords t]
["T-SQL keywords" sql-highlight-tsql-keywords t]
["ANSI SQL keywords" sql-highlight-ansi-keywords t]
["Oracle keywords" sql-highlight-oracle-keywords t]
["Postgres keywords" sql-highlight-postgres-keywords t]))
(setq sql-mode-font-lock-keywords sql-mode-mysql-font-lock-keywords)
(defvar sql-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?/ ". 14" table)
(modify-syntax-entry ?* ". 23" table)
(if (string-match "XEmacs\\|Lucid" emacs-version)
(modify-syntax-entry ?- ". 56" table)
(modify-syntax-entry ?- ". 12b" table))
(modify-syntax-entry ?\n "> b" table)
(modify-syntax-entry ?\f "> b" table)
(modify-syntax-entry ?' "\"" table)
(modify-syntax-entry ?_ "w" table)
(modify-syntax-entry ?@ "w" table)
table)
"Syntax table used in `sql-mode' and `sql-interactive-mode'.")
(defvar sql-mode-keyword-upcase-p t
"Whether or not to upcase SQL keywords.")
(defvar sql-mode-keyword-upcase-face
(list 'font-lock-type-face
'font-lock-keyword-face
'font-lock-function-name-face
'font-lock-builtin-face)
"Font-lock faces that are considered keywords.
These will be uppercased if `sql-mode-keyword-upcase-p' is
true.")
(defconst sql-mode-keyword-space-regexp
"\\s-"
"Regular expression that matches a single white-space
character.")
(defun sql-mode-keyword-upcase (beg end)
"Upcase SQL keywords in range."
(interactive "*r")
(save-excursion
(let (face
face-list)
(goto-char beg)
(while (< (point) end)
(while (and
(< (point) end)
(looking-at sql-mode-keyword-space-regexp))
(forward-char))
(setq face (get-char-property (point) 'face))
(when face
(setq face-list sql-mode-keyword-upcase-face)
(while face-list
(if (equal face (car face-list))
(progn
(save-excursion
(upcase-word 1))
(setq face-list ()))
(setq face-list (cdr face-list)))))
(forward-word 1)
))))
(defconst sql-indent-blank-regexp
"^\\s-*$"
"Regular expression that matches a blank line.")
(defconst sql-indent-begin-regexp
"^\\s-*\\bbegin\\b"
"Regular expression that matches a begin block.")
(defconst sql-indent-end-regexp
"^\\s-*\\b\\(end\\s-*$\\|commit tran\\)\\b"
"Regular expression that matches an end block.
Does not match the end of an inline case statement.")
(defconst sql-indent-if-else-regexp
"^\\s-*\\b\\(if\\|else\\)\\b"
"Regular expression that matches an if or else statement.")
(defconst sql-indent-comment-regexp
"^\\s-*\\(\\-\\-\\|\\/\\*\\)"
"Regular expression that matches a comment begin or line.")
(defconst sql-indent-comment-line-regexp
"^\\s-*\\-\\-"
"Regular expression that matches a comment line.")
(defconst sql-indent-comment-begin-regexp
"^.*\\/\\*"
"Regular expression that matches a comment begin.")
(defconst sql-indent-comment-end-regexp
"^.*\\*\\/"
"Regular expression that matches a comment end.")
(defconst sql-indent-asterisk-regexp
"^\\s-*\\*"
"Regular expression that matches a line starting with `*'.")
(defconst sql-indent-statement-regexp
(eval-when-compile
(concat "^\\s-*\\b"
(regexp-opt '(
"add" "alter" "as" "authorization" "backup" "break" "browse" "bulk" "cascade"
"checkpoint" "close" "clustered" "coalesce" "collate" "column" "commit"
"compute" "constraint" "contains" "containstable" "continue" "convert"
"create" "cross" "current_date" "current_time" "current_timestamp"
"current_user" "cursor" "database" "dbcc" "deallocate" "declare" "delete"
"deny" "disk" "distributed" "drop" "dummy" "dump" "else" "errlvl" "escape"
"except" "exec" "execute" "exit" "fetch" "file" "for" "freetext"
"freetexttable" "full" "go" "goto" "grant" "holdlock" "identity_insert"
"identitycol" "if" "insert" "intersect" "key" "kill" "lineno" "load"
"national" "offsets" "open" "opendatasource" "openquery" "openrowset"
"openxml" "over" "plan" "print" "public" "raiserror" "read" "reconfigure"
"references" "replication" "restore" "restrict" "return" "revoke" "rollback"
"rowguidcol" "rule" "save" "schema" "select" "session_user" "set" "setuser"
"shutdown" "some" "statistics" "system_user" "textsize" "truncate" "tsequal"
"union" "update" "use" "varying" "waitfor" "while"
) t) "\\b"))
"Regular expression that matches the beginning of SQL statements.")
(defconst sql-indent-dml-regexp
(eval-when-compile
(concat "^\\s-*\\b"
(regexp-opt '("delete" "insert" "select" "update") t) "\\b"))
"Regular expression that matches the beginning of DML SQL statements.
Ones that start with `select', `insert', `update', or `delete'.")
(defconst sql-indent-select-regexp
"^\\s-*\\bselect\\b"
"Regular expression that matches the beginning of a select statement.")
(defconst sql-indent-insert-regexp
"^\\s-*\\binsert\\b"
"Regular expression that matches an insert statement.")
(defconst sql-indent-insert-values-regexp
"^\\s-*\\binsert\\b.*\\bvalues\\b"
"Regular expression that matches an insert/values statement.")
(defconst sql-indent-cursor-regexp
"^\\s-*\\bdeclare\\b.*\\bcursor\\b\\s-*\\bfor\\b\\s-*$"
"Regular expression that matches a declare cursor statement.")
(defun sql-indent-line-get-info ()
"Get info about statement on current line.
Returns a list containing the following:
type: type of SQL statement
keyword: starting SQL keyword (lowercased)
indent: column number of indentation
Moves point to start of statement. If in a comment block, will
move point to the start of the comment block.
You may call it again after doing `forward-word -1' to get info
on the previous statement.
Possible types are:
bob: beginning of block
comment-line: -- type of comment
comment-block-begin: /* */ type of comment (first line)
comment-block-end: /* */ type of comment (last line)
comment-block-middle: /* */ type of comment (a middle line)
blank: blank line
begin: begin statement (block begin)
end: end statement (block end)
if-else: if or else statement
comment: (should never happend; should get a more specific type, above)
statement: other sql statement
statement-select: an sql statement that may be followed by a select
continue: continuation of an sql statement"
(interactive)
(let ((type nil)
(keyword nil)
(indent nil)
(case-fold-search t))
(beginning-of-line)
(if (bobp)
(progn
(setq type 'bob)
(setq indent 0))
(progn
(goto-char (+ (point-at-bol) (current-indentation)))
(when (eq (get-char-property (point) 'face) 'font-lock-comment-face)
(save-excursion
(beginning-of-line)
(cond
((looking-at sql-indent-comment-line-regexp)
(setq type 'comment-line)
(save-excursion
(forward-line -1)
(end-of-line)
(while (and (not (bobp))
(eq (get-char-property (point) 'face)
'font-lock-comment-face))
(forward-char -1))
(when (looking-at sql-indent-comment-begin-regexp)
(setq type 'comment-block-middle))
))
((looking-at sql-indent-comment-begin-regexp)
(setq type 'comment-block-begin))
((looking-at sql-indent-comment-end-regexp)
(setq type 'comment-block-end))
(t (setq type 'comment-block-middle)))))
(when type
(while (and (not (bobp))
(eq (get-char-property (point) 'face)
'font-lock-comment-face))
(forward-line -1)
(beginning-of-line)
(unless (bobp)
(goto-char (+ (point-at-bol) (current-indentation)))))
(unless (and (bobp)
(eq (get-char-property (point) 'face)
'font-lock-comment-face))
(forward-line 1)))
(unless type
(beginning-of-line)
(cond
((looking-at sql-indent-blank-regexp)
(setq type 'blank))
((looking-at sql-indent-begin-regexp)
(setq type 'begin))
((looking-at sql-indent-end-regexp)
(setq type 'end))
((looking-at sql-indent-if-else-regexp)
(setq type 'if-else))
((looking-at sql-indent-comment-regexp)
(setq type 'comment))
((looking-at sql-indent-statement-regexp)
(setq type 'statement)
(when (and
(looking-at sql-indent-insert-regexp)
(not (looking-at sql-indent-insert-values-regexp)))
(setq type 'statement-select))
(when (looking-at sql-indent-cursor-regexp)
(setq type 'statement-select)))
(t (setq type 'continue))))))
(goto-char (+ (point-at-bol) (current-indentation)))
(when (stringp (thing-at-point 'word))
(setq keyword (downcase (thing-at-point 'word))))
(unless indent
(setq indent (current-indentation)))
(list type keyword indent)
))
(defun sql-indent-line-get-info-root ()
"Calls sql-indent-line-get-info until a non-continue type line is reached.
Types `continue' and `statement-select' are skipped."
(let ((line-info nil))
(while (or (not (line-info))
(eq (car line-info) 'continue)
(eq (car line-info) 'statement-select))
(setq line-info (sql-indent-line-get-info)))))
(defun sql-indent-line-is-comment (type)
"Tests if type is a comment symbol."
(or
(eq type 'comment)
(eq type 'comment-line)
(eq type 'comment-block-begin)
(eq type 'comment-block-end)
(eq type 'comment-block-middle)))
(defun sql-indent-line-is-statement (type)
"Tests if type is a statement symbol."
(or
(eq type 'statement)
(eq type 'statement-select)))
(defun sql-indent-line ()
"Indent current SQL line."
(interactive)
(when sql-mode-keyword-upcase-p
(sql-mode-keyword-upcase (point-at-bol) (point-at-eol)))
(let (line-info
line-type
line-keyword
line-indent
prev-line-info
prev-line-type
prev-line-keyword
prev-line-indent
first-prev-line-indent
indent
(check-if-else t))
(save-excursion
(setq line-info (sql-indent-line-get-info))
(setq line-type (car line-info))
(setq line-keyword (car (cdr line-info)))
(setq line-indent (car (cdr (cdr line-info))))
(when (eq line-type 'bob)
(setq indent line-indent))
(when (or (eq line-type 'comment-block-middle)
(eq line-type 'comment-block-end))
(setq indent (+ line-indent 3))
(when (looking-at sql-indent-asterisk-regexp)
(setq indent (- indent 2))))
(unless indent
(while (and (not (bobp)) (not indent))
(forward-line -1)
(setq prev-line-info (sql-indent-line-get-info))
(setq prev-line-type (car prev-line-info))
(setq prev-line-keyword (car (cdr prev-line-info)))
(setq prev-line-indent (car (cdr (cdr prev-line-info))))
(unless first-prev-line-indent
(setq first-prev-line-indent prev-line-indent))
(when (eq prev-line-type 'bob)
(setq indent prev-line-indent))
(when (eq prev-line-type 'begin)
(setq indent (+ prev-line-indent default-tab-width))
(setq check-if-else nil))
(when (and (eq line-type 'end)
(not (eq prev-line-type 'continue))
(not (eq prev-line-type 'statement-select)))
(if (eq prev-line-type 'begin)
(setq indent prev-line-indent)
(setq indent (- prev-line-indent default-tab-width)))
(setq check-if-else nil))
(when (eq prev-line-type 'if-else)
(if (eq line-type 'begin)
(setq indent prev-line-indent)
(setq indent (+ prev-line-indent default-tab-width))))
(when (and (not indent)
(or
(sql-indent-line-is-statement prev-line-type)
(sql-indent-line-is-comment prev-line-type)
(eq prev-line-type 'end)))
(setq indent prev-line-indent)
(when (and
(eq prev-line-type 'statement-select)
(eq line-type 'statement)
(string= line-keyword "select"))
(setq indent (+ indent (/ default-tab-width 2)))
(setq check-if-else nil)))
))
(while (and (not (bobp))
(not (eq line-type 'continue))
(not (eq line-type 'statement-select))
(not (eq prev-line-type 'begin))
(not (eq prev-line-type 'end))
(not (eq prev-line-type 'if-else))
check-if-else)
(forward-line -1)
(setq prev-line-info (sql-indent-line-get-info))
(setq prev-line-type (car prev-line-info))
(setq prev-line-keyword (car (cdr prev-line-info)))
(setq prev-line-indent (car (cdr (cdr prev-line-info))))
(when (eq prev-line-type 'bob)
(setq check-if-else nil))
(when (eq prev-line-type 'begin)
(setq check-if-else nil))
(when (eq prev-line-type 'end)
(setq check-if-else nil))
(when (eq prev-line-type 'if-else)
(setq indent (current-indentation))
(setq check-if-else nil))
(when (sql-indent-line-is-statement prev-line-type)
(setq check-if-else nil))
)
(when (and (bobp) (not indent))
(setq indent 0))
(when (eq line-type 'continue)
(setq indent (+ indent (/ default-tab-width 2))))
(when (eq line-type 'blank)
(setq indent first-prev-line-indent))
)
(if (and indent (>= indent 0))
(indent-line-to indent)
(indent-line-to 0))
))
(defun indent-newline-and-indent ()
"Indent current line, then add a newline at the end, then
indent the new line."
(interactive)
(save-excursion
(indent-for-tab-command))
(newline-and-indent))
(defun mysql-mode-hook ()
(set-syntax-table sql-mode-syntax-table)
(sql-add-product-keywords 'mysql sql-mode-mysql-font-lock-keywords 'set)
(make-local-variable 'indent-line-function)
(setq indent-line-function 'sql-indent-line)
(local-set-key "\C-m" 'indent-newline-and-indent)
)
(add-hook 'sql-mode-hook 'mysql-mode-hook)
(provide 'mysql)