diff options
Diffstat (limited to 'lisp/progmodes/sql.el')
-rw-r--r-- | lisp/progmodes/sql.el | 622 |
1 files changed, 438 insertions, 184 deletions
diff --git a/lisp/progmodes/sql.el b/lisp/progmodes/sql.el index e7d7494d2ca..51f78bd840b 100644 --- a/lisp/progmodes/sql.el +++ b/lisp/progmodes/sql.el @@ -213,7 +213,7 @@ ;; Drew Adams <drew.adams@oracle.com> -- Emacs 20 support ;; Harald Maier <maierh@myself.com> -- sql-send-string ;; Stefan Monnier <monnier@iro.umontreal.ca> -- font-lock corrections; -;; code polish +;; code polish; on-going guidance and mentorship ;; Paul Sleigh <bat@flurf.net> -- MySQL keyword enhancement ;; Andrew Schein <andrew@andrewschein.com> -- sql-port bug ;; Ian Bjorhovde <idbjorh@dataproxy.com> -- db2 escape newlines @@ -221,6 +221,8 @@ ;; Roman Scherer <roman.scherer@nugg.ad> -- Connection documentation ;; Mark Wilkinson <wilkinsonmr@gmail.com> -- file-local variables ignored ;; Simen Heggestøyl <simenheg@gmail.com> -- Postgres database completion +;; Robert Cochran <robert-emacs@cochranmail.com> -- MariaDB support +;; Alex Harsanyi <alexharsanyi@gmail.com> -- sql-indent package and support ;; @@ -344,7 +346,8 @@ file. Since that is a plaintext file, this could be dangerous." (const :format "" :completion) (sexp :tag ":completion") (const :format "" :must-match) - (symbol :tag ":must-match"))) + (restricted-sexp + :match-alternatives (listp stringp)))) (const port))) ;; SQL Product support @@ -415,6 +418,21 @@ file. Since that is a plaintext file, this could be dangerous." :prompt-regexp "^SQL>" :prompt-length 4) + (mariadb + :name "MariaDB" + :free-software t + :font-lock sql-mode-mariadb-font-lock-keywords + :sqli-program sql-mariadb-program + :sqli-options sql-mariadb-options + :sqli-login sql-mariadb-login-params + :sqli-comint-func sql-comint-mariadb + :list-all "SHOW TABLES;" + :list-table "DESCRIBE %s;" + :prompt-regexp "^MariaDB \\[.*]> " + :prompt-cont-regexp "^ [\"'`-]> " + :syntax-alist ((?# . "< b")) + :input-filter sql-remove-tabs-filter) + (ms :name "Microsoft" :font-lock sql-mode-ms-font-lock-keywords @@ -691,6 +709,8 @@ making new SQLi sessions." :version "24.1" :group 'SQL) +(defvaralias 'sql-dialect 'sql-product) + (defcustom sql-product 'ansi "Select the SQL database product used. This allows highlighting buffers properly when you open them." @@ -703,7 +723,30 @@ This allows highlighting buffers properly when you open them." sql-product-alist)) :group 'SQL :safe 'symbolp) -(defvaralias 'sql-dialect 'sql-product) + +;; SQL indent support + +(defcustom sql-use-indent-support t + "If non-nil then use the SQL indent support features of sql-indent. +The `sql-indent' package in ELPA provides indentation support for +SQL statements with easy customizations to support varied layout +requirements. + +The package must be available to be loaded and activated." + :group 'SQL + :link '(url-link "https://elpa.gnu.org/packages/sql-indent.html") + :type 'booleanp + :version "27.1") + +(defun sql-is-indent-available () + "Check if sql-indent module is available." + (when (locate-library "sql-indent") + (fboundp 'sqlind-minor-mode))) + +(defun sql-indent-enable () + "Enable `sqlind-minor-mode' if available and requested." + (when (sql-is-indent-available) + (sqlind-minor-mode (if sql-use-indent-support +1 -1)))) ;; misc customization of sql.el behavior @@ -759,16 +802,20 @@ Globally should be set to nil; it will be non-nil in `sql-mode', (defvar sql-login-delay 7.5 ;; Secs "Maximum number of seconds you are willing to wait for a login connection.") -(defcustom sql-pop-to-buffer-after-send-region nil - "When non-nil, pop to the buffer SQL statements are sent to. +(defvaralias 'sql-pop-to-buffer-after-send-region 'sql-display-sqli-buffer-function) -After a call to `sql-sent-string', `sql-send-region', -`sql-send-paragraph' or `sql-send-buffer', the window is split -and the SQLi buffer is shown. If this variable is not nil, that -buffer's window will be selected by calling `pop-to-buffer'. If -this variable is nil, that buffer is shown using -`display-buffer'." - :type 'boolean +(defcustom sql-display-sqli-buffer-function 'display-buffer + "Function to be called to display a SQLi buffer after `sql-send-*'. + +When set to a function, it will be called to display the buffer. +When set to t, the default function `pop-to-buffer' will be +called. If not set, no attempt will be made to display the +buffer." + + :type '(choice (const :tag "Default" t) + (const :tag "No display" nil) + (function :tag "Display Buffer function")) + :version "27.1" :group 'SQL) ;; imenu support for sql-mode. @@ -788,7 +835,7 @@ this variable is nil, that buffer is shown using This is used to set `imenu-generic-expression' when SQL mode is entered. Subsequent changes to `sql-imenu-generic-expression' will -not affect existing SQL buffers because imenu-generic-expression is +not affect existing SQL buffers because `imenu-generic-expression' is a local variable.") ;; history file @@ -828,15 +875,17 @@ commands when the input history is read, as if you had set ;; The usual hooks -(defcustom sql-interactive-mode-hook '() +(defcustom sql-interactive-mode-hook '(sql-indent-enable) "Hook for customizing `sql-interactive-mode'." :type 'hook - :group 'SQL) + :group 'SQL + :version "27.1") -(defcustom sql-mode-hook '() +(defcustom sql-mode-hook '(sql-indent-enable) "Hook for customizing `sql-mode'." :type 'hook - :group 'SQL) + :group 'SQL + :version "27.1") (defcustom sql-set-sqli-hook '() "Hook for reacting to changes of `sql-buffer'. @@ -953,10 +1002,19 @@ Starts `sql-interactive-mode' after doing some setup." :version "26.1" :group 'SQL) +;; Customization for MariaDB + +;; MariaDB is a drop-in replacement for MySQL, so just make the +;; MariaDB variables aliases of the MySQL ones. + +(defvaralias 'sql-mariadb-program 'sql-mysql-program) +(defvaralias 'sql-mariadb-options 'sql-mysql-options) +(defvaralias 'sql-mariadb-login-params 'sql-mysql-login-params) + ;; Customization for MySQL (defcustom sql-mysql-program "mysql" - "Command to start mysql by TcX. + "Command to start mysql by Oracle. Starts `sql-interactive-mode' after doing some setup." :type 'file @@ -1103,8 +1161,11 @@ add your name with a \"-U\" prefix (such as \"-Umark\") to the list." (when (executable-find sql-postgres-program) (let ((res '())) (ignore-errors - (dolist (row (process-lines sql-postgres-program "-ltX")) - (when (string-match "^ \\([[:alnum:]-_]+\\) +|.*" row) + (dolist (row (process-lines sql-postgres-program + "--list" + "--no-psqlrc" + "--tuples-only")) + (when (string-match "^ \\([^ |]+\\) +|.*" row) (push (match-string 1 row) res)))) (nreverse res)))) @@ -1237,7 +1298,8 @@ specified, it's `sql-product' or `sql-connection' must match." (or (not product) (eq product sql-product)) (or (not connection) - (eq connection sql-connection))))))) + (and (stringp connection) + (string= connection sql-connection)))))))) ;; Keymap for sql-interactive-mode. @@ -2312,75 +2374,148 @@ regular expressions are created during compilation by calling the function `regexp-opt'. Therefore, take a look at the source before you define your own `sql-mode-solid-font-lock-keywords'.") +(defvaralias 'sql-mode-mariadb-font-lock-keywords 'sql-mode-mysql-font-lock-keywords + "MariaDB is SQL compatible with MySQL.") + (defvar sql-mode-mysql-font-lock-keywords (eval-when-compile (list ;; MySQL Functions (sql-font-lock-keywords-builder 'font-lock-builtin-face nil -"ascii" "avg" "bdmpolyfromtext" "bdmpolyfromwkb" "bdpolyfromtext" -"bdpolyfromwkb" "benchmark" "bin" "bit_and" "bit_length" "bit_or" -"bit_xor" "both" "cast" "char_length" "character_length" "coalesce" -"concat" "concat_ws" "connection_id" "conv" "convert" "count" -"curdate" "current_date" "current_time" "current_timestamp" "curtime" -"elt" "encrypt" "export_set" "field" "find_in_set" "found_rows" "from" +"acos" "adddate" "addtime" "aes_decrypt" "aes_encrypt" "area" +"asbinary" "ascii" "asin" "astext" "aswkb" "aswkt" "atan" "atan2" +"avg" "bdmpolyfromtext" "bdmpolyfromwkb" "bdpolyfromtext" +"bdpolyfromwkb" "benchmark" "bin" "binlog_gtid_pos" "bit_and" +"bit_count" "bit_length" "bit_or" "bit_xor" "both" "boundary" "buffer" +"cast" "ceil" "ceiling" "centroid" "character_length" "char_length" +"charset" "coalesce" "coercibility" "column_add" "column_check" +"column_create" "column_delete" "column_exists" "column_get" +"column_json" "column_list" "compress" "concat" "concat_ws" +"connection_id" "conv" "convert" "convert_tz" "convexhull" "cos" "cot" +"count" "crc32" "crosses" "cume_dist" "cume_dist" "curdate" +"current_date" "current_time" "current_timestamp" "curtime" "date_add" +"datediff" "date_format" "date_sub" "dayname" "dayofmonth" "dayofweek" +"dayofyear" "decode" "decode_histogram" "degrees" "dense_rank" +"dense_rank" "des_decrypt" "des_encrypt" "dimension" "disjoint" "div" +"elt" "encode" "encrypt" "endpoint" "envelope" "exp" "export_set" +"exteriorring" "extractvalue" "field" "find_in_set" "floor" "format" +"found_rows" "from" "from_base64" "from_days" "from_unixtime" "geomcollfromtext" "geomcollfromwkb" "geometrycollectionfromtext" "geometrycollectionfromwkb" "geometryfromtext" "geometryfromwkb" -"geomfromtext" "geomfromwkb" "get_lock" "group_concat" "hex" "ifnull" -"instr" "interval" "isnull" "last_insert_id" "lcase" "leading" -"length" "linefromtext" "linefromwkb" "linestringfromtext" -"linestringfromwkb" "load_file" "locate" "lower" "lpad" "ltrim" -"make_set" "master_pos_wait" "max" "mid" "min" "mlinefromtext" -"mlinefromwkb" "mpointfromtext" "mpointfromwkb" "mpolyfromtext" -"mpolyfromwkb" "multilinestringfromtext" "multilinestringfromwkb" +"geometryn" "geometrytype" "geomfromtext" "geomfromwkb" "get_format" +"get_lock" "glength" "greatest" "group_concat" "hex" "ifnull" +"inet6_aton" "inet6_ntoa" "inet_aton" "inet_ntoa" "instr" +"interiorringn" "intersects" "interval" "isclosed" "isempty" +"is_free_lock" "is_ipv4" "is_ipv4_compat" "is_ipv4_mapped" "is_ipv6" +"isnull" "isring" "issimple" "is_used_lock" "json_array" +"json_array_append" "json_array_insert" "json_compact" "json_contains" +"json_contains_path" "json_depth" "json_detailed" "json_exists" +"json_extract" "json_insert" "json_keys" "json_length" "json_loose" +"json_merge" "json_object" "json_query" "json_quote" "json_remove" +"json_replace" "json_search" "json_set" "json_type" "json_unquote" +"json_valid" "json_value" "lag" "last_day" "last_insert_id" "lastval" +"last_value" "last_value" "lcase" "lead" "leading" "least" "length" +"linefromtext" "linefromwkb" "linestringfromtext" "linestringfromwkb" +"ln" "load_file" "locate" "log" "log10" "log2" "lower" "lpad" "ltrim" +"makedate" "make_set" "maketime" "master_gtid_wait" "master_pos_wait" +"max" "mbrcontains" "mbrdisjoint" "mbrequal" "mbrintersects" +"mbroverlaps" "mbrtouches" "mbrwithin" "md5" "median" +"mid" "min" "mlinefromtext" "mlinefromwkb" "monthname" +"mpointfromtext" "mpointfromwkb" "mpolyfromtext" "mpolyfromwkb" +"multilinestringfromtext" "multilinestringfromwkb" "multipointfromtext" "multipointfromwkb" "multipolygonfromtext" -"multipolygonfromwkb" "now" "nullif" "oct" "octet_length" "ord" -"pointfromtext" "pointfromwkb" "polyfromtext" "polyfromwkb" -"polygonfromtext" "polygonfromwkb" "position" "quote" "rand" -"release_lock" "repeat" "replace" "reverse" "rpad" "rtrim" "soundex" -"space" "std" "stddev" "substring" "substring_index" "sum" "sysdate" -"trailing" "trim" "ucase" "unix_timestamp" "upper" "user" "variance" +"multipolygonfromwkb" "name_const" "nextval" "now" "nth_value" "ntile" +"ntile" "nullif" "numgeometries" "numinteriorrings" "numpoints" "oct" +"octet_length" "old_password" "ord" "percentile_cont" +"percentile_disc" "percent_rank" "percent_rank" "period_add" +"period_diff" "pi" "pointfromtext" "pointfromwkb" "pointn" +"pointonsurface" "polyfromtext" "polyfromwkb" "polygonfromtext" +"polygonfromwkb" "position" "pow" "power" "quote" "radians" +"rand" "rank" "rank" "regexp" "regexp_instr" "regexp_replace" +"regexp_substr" "release_lock" "repeat" "replace" "reverse" "rlike" +"row_number" "row_number" "rpad" "rtrim" "sec_to_time" "setval" "sha" +"sha1" "sha2" "sign" "sin" "sleep" "soundex" "space" +"spider_bg_direct_sql" "spider_copy_tables" "spider_direct_sql" +"spider_flush_table_mon_cache" "sqrt" "srid" "st_area" "startpoint" +"st_asbinary" "st_astext" "st_aswkb" "st_aswkt" "st_boundary" +"st_buffer" "st_centroid" "st_contains" "st_convexhull" "st_crosses" +"std" "stddev" "stddev_pop" "stddev_samp" "st_difference" +"st_dimension" "st_disjoint" "st_distance" "st_endpoint" "st_envelope" +"st_equals" "st_exteriorring" "st_geomcollfromtext" +"st_geomcollfromwkb" "st_geometrycollectionfromtext" +"st_geometrycollectionfromwkb" "st_geometryfromtext" +"st_geometryfromwkb" "st_geometryn" "st_geometrytype" +"st_geomfromtext" "st_geomfromwkb" "st_interiorringn" +"st_intersection" "st_intersects" "st_isclosed" "st_isempty" +"st_isring" "st_issimple" "st_length" "st_linefromtext" +"st_linefromwkb" "st_linestringfromtext" "st_linestringfromwkb" +"st_numgeometries" "st_numinteriorrings" "st_numpoints" "st_overlaps" +"st_pointfromtext" "st_pointfromwkb" "st_pointn" "st_pointonsurface" +"st_polyfromtext" "st_polyfromwkb" "st_polygonfromtext" +"st_polygonfromwkb" "strcmp" "st_relate" "str_to_date" "st_srid" +"st_startpoint" "st_symdifference" "st_touches" "st_union" "st_within" +"st_x" "st_y" "subdate" "substr" "substring" "substring_index" +"subtime" "sum" "sysdate" "tan" "timediff" "time_format" +"timestampadd" "timestampdiff" "time_to_sec" "to_base64" "to_days" +"to_seconds" "touches" "trailing" "trim" "ucase" "uncompress" +"uncompressed_length" "unhex" "unix_timestamp" "updatexml" "upper" +"user" "utc_date" "utc_time" "utc_timestamp" "uuid" "uuid_short" +"variance" "var_pop" "var_samp" "version" "weekday" +"weekofyear" "weight_string" "within" ) ;; MySQL Keywords (sql-font-lock-keywords-builder 'font-lock-keyword-face nil -"action" "add" "after" "against" "all" "alter" "and" "as" "asc" -"auto_increment" "avg_row_length" "bdb" "between" "by" "cascade" -"case" "change" "character" "check" "checksum" "close" "collate" -"collation" "column" "columns" "comment" "committed" "concurrent" -"constraint" "create" "cross" "data" "database" "default" -"delay_key_write" "delayed" "delete" "desc" "directory" "disable" -"distinct" "distinctrow" "do" "drop" "dumpfile" "duplicate" "else" "elseif" -"enable" "enclosed" "end" "escaped" "exists" "fields" "first" "for" -"force" "foreign" "from" "full" "fulltext" "global" "group" "handler" -"having" "heap" "high_priority" "if" "ignore" "in" "index" "infile" -"inner" "insert" "insert_method" "into" "is" "isam" "isolation" "join" -"key" "keys" "last" "left" "level" "like" "limit" "lines" "load" -"local" "lock" "low_priority" "match" "max_rows" "merge" "min_rows" -"mode" "modify" "mrg_myisam" "myisam" "natural" "next" "no" "not" -"null" "offset" "oj" "on" "open" "optionally" "or" "order" "outer" -"outfile" "pack_keys" "partial" "password" "prev" "primary" -"procedure" "quick" "raid0" "raid_type" "read" "references" "rename" -"repeatable" "restrict" "right" "rollback" "rollup" "row_format" -"savepoint" "select" "separator" "serializable" "session" "set" -"share" "show" "sql_big_result" "sql_buffer_result" "sql_cache" -"sql_calc_found_rows" "sql_no_cache" "sql_small_result" "starting" -"straight_join" "striped" "table" "tables" "temporary" "terminated" -"then" "to" "transaction" "truncate" "type" "uncommitted" "union" -"unique" "unlock" "update" "use" "using" "values" "when" "where" -"with" "write" "xor" +"accessible" "action" "add" "after" "against" "all" "alter" "analyze" +"and" "as" "asc" "auto_increment" "avg_row_length" "bdb" "between" +"body" "by" "cascade" "case" "change" "character" "check" "checksum" +"close" "collate" "collation" "column" "columns" "comment" "committed" +"concurrent" "condition" "constraint" "create" "cross" "data" +"database" "databases" "default" "delayed" "delay_key_write" "delete" +"desc" "directory" "disable" "distinct" "distinctrow" "do" "drop" +"dual" "dumpfile" "duplicate" "else" "elseif" "elsif" "enable" +"enclosed" "end" "escaped" "exists" "exit" "explain" "fields" "first" +"for" "force" "foreign" "from" "full" "fulltext" "global" "group" +"handler" "having" "heap" "high_priority" "history" "if" "ignore" +"ignore_server_ids" "in" "index" "infile" "inner" "insert" +"insert_method" "into" "is" "isam" "isolation" "join" "key" "keys" +"kill" "last" "leave" "left" "level" "like" "limit" "linear" "lines" +"load" "local" "lock" "long" "loop" "low_priority" +"master_heartbeat_period" "master_ssl_verify_server_cert" "match" +"max_rows" "maxvalue" "merge" "min_rows" "mode" "modify" "mrg_myisam" +"myisam" "natural" "next" "no" "not" "no_write_to_binlog" "null" +"offset" "oj" "on" "open" "optimize" "optionally" "or" "order" "outer" +"outfile" "over" "package" "pack_keys" "partial" "partition" +"password" "period" "prev" "primary" "procedure" "purge" "quick" +"raid0" "raid_type" "raise" "range" "read" "read_write" "references" +"release" "rename" "repeatable" "require" "resignal" "restrict" +"returning" "right" "rollback" "rollup" "row_format" "rowtype" +"savepoint" "schemas" "select" "separator" "serializable" "session" +"set" "share" "show" "signal" "slow" "spatial" "sql_big_result" +"sql_buffer_result" "sql_cache" "sql_calc_found_rows" "sql_no_cache" +"sql_small_result" "ssl" "starting" "straight_join" "striped" +"system_time" "table" "tables" "temporary" "terminated" "then" "to" +"transaction" "truncate" "type" "uncommitted" "undo" "union" "unique" +"unlock" "update" "use" "using" "values" "versioning" "when" "where" +"while" "window" "with" "write" "xor" ) ;; MySQL Data Types (sql-font-lock-keywords-builder 'font-lock-type-face nil -"bigint" "binary" "bit" "blob" "bool" "boolean" "char" "curve" "date" -"datetime" "dec" "decimal" "double" "enum" "fixed" "float" "geometry" -"geometrycollection" "int" "integer" "line" "linearring" "linestring" -"longblob" "longtext" "mediumblob" "mediumint" "mediumtext" +"bigint" "binary" "bit" "blob" "bool" "boolean" "byte" "char" "curve" +"date" "datetime" "day" "day_hour" "day_microsecond" "day_minute" +"day_second" "dec" "decimal" "double" "enum" "fixed" "float" "float4" +"float8" "geometry" "geometrycollection" "hour" "hour_microsecond" +"hour_minute" "hour_second" "int" "int1" "int2" "int3" "int4" "int8" +"integer" "json" "line" "linearring" "linestring" "longblob" +"longtext" "mediumblob" "mediumint" "mediumtext" "microsecond" +"middleint" "minute" "minute_microsecond" "minute_second" "month" "multicurve" "multilinestring" "multipoint" "multipolygon" "multisurface" "national" "numeric" "point" "polygon" "precision" -"real" "smallint" "surface" "text" "time" "timestamp" "tinyblob" -"tinyint" "tinytext" "unsigned" "varchar" "year" "year2" "year4" -"zerofill" +"quarter" "real" "second" "second_microsecond" "signed" "smallint" +"surface" "text" "time" "timestamp" "tinyblob" "tinyint" "tinytext" +"unsigned" "varbinary" "varchar" "varcharacter" "week" "year" "year2" +"year4" "year_month" "zerofill" ))) "MySQL SQL keywords used by font-lock. @@ -2712,18 +2847,52 @@ adds a fontification pattern to fontify identifiers ending in ;; Save product setting and fontify. (setq sql-product product) (sql-highlight-product)) +(defalias 'sql-set-dialect 'sql-set-product) - -;;; Compatibility functions - -(if (not (fboundp 'comint-line-beginning-position)) - ;; comint-line-beginning-position is defined in Emacs 21 - (defun comint-line-beginning-position () - "Return the buffer position of the beginning of the line, after any prompt. -The prompt is assumed to be any text at the beginning of the line -matching the regular expression `comint-prompt-regexp', a buffer -local variable." - (save-excursion (comint-bol nil) (point)))) +(defun sql-buffer-hidden-p (buf) + "Is the buffer hidden?" + (string-prefix-p " " + (cond + ((stringp buf) + (when (get-buffer buf) + buf)) + ((bufferp buf) + (buffer-name buf)) + (t nil)))) + +(defun sql-display-buffer (buf) + "Display a SQLi buffer based on `sql-display-sqli-buffer-function'. + +If BUF is hidden or `sql-display-sqli-buffer-function' is nil, +then the buffer will not be displayed. Otherwise the BUF is +displayed." + (unless (sql-buffer-hidden-p buf) + (cond + ((eq sql-display-sqli-buffer-function t) + (pop-to-buffer buf)) + ((not sql-display-sqli-buffer-function) + nil) + ((functionp sql-display-sqli-buffer-function) + (funcall sql-display-sqli-buffer-function buf)) + (t + (message "Invalid setting of `sql-display-sqli-buffer-function'") + (pop-to-buffer buf))))) + +(defun sql-make-progress-reporter (buf message &optional min-value max-value current-value min-change min-time) + "Make a progress reporter if BUF is not hidden." + (unless (or (sql-buffer-hidden-p buf) + (not sql-display-sqli-buffer-function)) + (make-progress-reporter message min-value max-value current-value min-change min-time))) + +(defun sql-progress-reporter-update (reporter &optional value) + "Report progress of an operation in the echo area." + (when reporter + (progress-reporter-update reporter value))) + +(defun sql-progress-reporter-done (reporter) + "Print reporter’s message followed by word \"done\" in echo area." + (when reporter + (progress-reporter-done reporter))) ;;; SMIE support @@ -2760,8 +2929,8 @@ local variable." (prod-stmt (sql-get-product-feature prod :statement))) (concat "^\\<" (if prod-stmt - ansi-stmt - (concat "\\(" ansi-stmt "\\|" prod-stmt "\\)")) + (concat "\\(" ansi-stmt "\\|" prod-stmt "\\)") + ansi-stmt) "\\>"))) (defun sql-beginning-of-statement (arg) @@ -2952,7 +3121,12 @@ regexp pattern specified in its value. The `:completion' property prompts for a string specified by its value. (The property value is used as the PREDICATE argument to -`completing-read'.)" +`completing-read'.) + +For both `:file' and `:completion', there can also be a +`:must-match' property that controls REQUIRE-MATCH parameter to +`completing-read'." + (set-default symbol (let* ((default (plist-get plist :default)) @@ -2972,7 +3146,9 @@ value. (The property value is used as the PREDICATE argument to (read-file-name prompt (file-name-directory last-value) default - (plist-get plist :must-match) + (if (plist-member plist :must-match) + (plist-get plist :must-match) + t) (file-name-nondirectory last-value) (when (plist-get plist :file) `(lambda (f) @@ -2989,7 +3165,9 @@ value. (The property value is used as the PREDICATE argument to (completing-read prompt-def (plist-get plist :completion) nil - (plist-get plist :must-match) + (if (plist-member plist :must-match) + (plist-get plist :must-match) + t) last-value history-var default)) @@ -3031,21 +3209,21 @@ function like this: (sql-get-login \\='user \\='password \\='database)." (dolist (w what) (let ((plist (cdr-safe w))) (pcase (or (car-safe w) w) - (`user + ('user (sql-get-login-ext 'sql-user "User: " 'sql-user-history plist)) - (`password + ('password (setq-default sql-password (read-passwd "Password: " nil (sql-default-value 'sql-password)))) - (`server + ('server (sql-get-login-ext 'sql-server "Server: " 'sql-server-history plist)) - (`database + ('database (sql-get-login-ext 'sql-database "Database: " 'sql-database-history plist)) - (`port + ('port (sql-get-login-ext 'sql-port "Port: " nil (append '(:number t) plist))))))) @@ -3129,7 +3307,7 @@ See also `sql-help' on how to create such a buffer." (sql-set-sqli-buffer)) (display-buffer sql-buffer)) -(defun sql-make-alternate-buffer-name () +(defun sql-make-alternate-buffer-name (&optional product) "Return a string that can be used to rename a SQLi buffer. This is used to set `sql-alternate-buffer-name' within `sql-interactive-mode'. @@ -3151,23 +3329,23 @@ server/database name." (cdr (apply #'append nil (sql-for-each-login - (sql-get-product-feature sql-product :sqli-login) + (sql-get-product-feature (or product sql-product) :sqli-login) (lambda (token plist) (pcase token - (`user + ('user (unless (string= "" sql-user) (list "/" sql-user))) - (`port + ('port (unless (or (not (numberp sql-port)) (= 0 sql-port)) (list ":" (number-to-string sql-port)))) - (`server + ('server (unless (string= "" sql-server) (list "." (if (plist-member plist :file) (file-name-nondirectory sql-server) sql-server)))) - (`database + ('database (unless (string= "" sql-database) (list "@" (if (plist-member plist :file) @@ -3198,6 +3376,34 @@ server/database name." ;; Use the name we've got name)))) +(defun sql-generate-unique-sqli-buffer-name (product base) + "Generate a new, unique buffer name for a SQLi buffer. + +Append a sequence number until a unique name is found." + (let ((base-name (when (stringp base) + (substring-no-properties + (or base + (sql-get-product-feature product :name) + (symbol-name product))))) + buf-fmt-1st buf-fmt-rest) + + ;; Calculate buffer format + (if base-name + (setq buf-fmt-1st (format "*SQL: %s*" base-name) + buf-fmt-rest (format "*SQL: %s-%%d*" base-name)) + (setq buf-fmt-1st "*SQL*" + buf-fmt-rest "*SQL-%d*")) + + ;; See if we can find an unused buffer + (let ((buf-name buf-fmt-1st) + (i 1)) + (while (sql-buffer-live-p buf-name) + ;; Check a sequence number on the BASE + (setq buf-name (format buf-fmt-rest i) + i (1+ i))) + + buf-name))) + (defun sql-rename-buffer (&optional new-name) "Rename a SQL interactive buffer. @@ -3213,18 +3419,20 @@ NEW-NAME is empty, then the buffer name will be \"*SQL*\"." (user-error "Current buffer is not a SQL interactive buffer") (setq sql-alternate-buffer-name - (cond - ((stringp new-name) new-name) - ((consp new-name) - (read-string "Buffer name (\"*SQL: XXX*\"; enter `XXX'): " - sql-alternate-buffer-name)) - (t sql-alternate-buffer-name))) - - (setq sql-alternate-buffer-name (substring-no-properties sql-alternate-buffer-name)) - (rename-buffer (if (string= "" sql-alternate-buffer-name) - "*SQL*" - (format "*SQL: %s*" sql-alternate-buffer-name)) - t))) + (substring-no-properties + (cond + ((stringp new-name) + new-name) + ((consp new-name) + (read-string "Buffer name (\"*SQL: XXX*\"; enter `XXX'): " + sql-alternate-buffer-name)) + (t + sql-alternate-buffer-name)))) + + (rename-buffer + (sql-generate-unique-sqli-buffer-name sql-product + sql-alternate-buffer-name) + t))) (defun sql-copy-column () "Copy current column to the end of buffer. @@ -3439,15 +3647,14 @@ to avoid deleting non-prompt output." (sql-input-sender (get-buffer-process sql-buffer) s) ;; Send a command terminator if we must - (if sql-send-terminator - (sql-send-magic-terminator sql-buffer s sql-send-terminator)) + (when sql-send-terminator + (sql-send-magic-terminator sql-buffer s sql-send-terminator)) - (message "Sent string to buffer %s" sql-buffer))) + (when sql-pop-to-buffer-after-send-region + (message "Sent string to buffer %s" sql-buffer)))) ;; Display the sql buffer - (if sql-pop-to-buffer-after-send-region - (pop-to-buffer sql-buffer) - (display-buffer sql-buffer))) + (sql-display-buffer sql-buffer)) ;; We don't have no stinkin' sql (user-error "No SQL process started")))) @@ -3546,15 +3753,22 @@ of commands accepted by the SQLi program. COMMAND may also be a list of SQLi command strings." (let* ((visible (and outbuf - (not (string= " " (substring outbuf 0 1)))))) + (not (sql-buffer-hidden-p outbuf)))) + (this-save save-prior) + (next-save t)) + (when visible (message "Executing SQL command...")) + (if (consp command) - (mapc (lambda (c) (sql-redirect-one sqlbuf c outbuf save-prior)) - command) + (dolist (onecmd command) + (sql-redirect-one sqlbuf onecmd outbuf this-save) + (setq this-save next-save)) (sql-redirect-one sqlbuf command outbuf save-prior)) + (when visible - (message "Executing SQL command...done")))) + (message "Executing SQL command...done")) + nil)) (defun sql-redirect-one (sqlbuf command outbuf save-prior) (when command @@ -3603,7 +3817,7 @@ list of SQLi command strings." (replace-match "" t t)) (goto-char start)))))))) -(defun sql-redirect-value (sqlbuf command regexp &optional regexp-groups) +(defun sql-redirect-value (sqlbuf command &optional regexp regexp-groups) "Execute the SQL command and return part of result. SQLBUF must be an active SQL interactive buffer. COMMAND should @@ -3618,7 +3832,7 @@ for each match." (results nil)) (sql-redirect sqlbuf command outbuf nil) (with-current-buffer outbuf - (while (re-search-forward regexp nil t) + (while (re-search-forward (or regexp "^.+$") nil t) (push (cond ;; no groups-return all of them @@ -4031,15 +4245,16 @@ Writes the input history to a history file using This function is a sentinel watching the SQL interpreter process. Sentinels will always get the two parameters PROCESS and EVENT." - (with-current-buffer (process-buffer process) - (let - ((comint-input-ring-separator sql-input-ring-separator) - (comint-input-ring-file-name sql-input-ring-file-name)) - (comint-write-input-ring)) + (when (buffer-live-p (process-buffer process)) + (with-current-buffer (process-buffer process) + (let + ((comint-input-ring-separator sql-input-ring-separator) + (comint-input-ring-file-name sql-input-ring-file-name)) + (comint-write-input-ring)) - (if (not buffer-read-only) - (insert (format "\nProcess %s %s\n" process event)) - (message "Process %s %s" process event)))) + (if (not buffer-read-only) + (insert (format "\nProcess %s %s\n" process event)) + (message "Process %s %s" process event))))) @@ -4099,11 +4314,11 @@ is specified in the connection settings." (mapcar (lambda (v) (pcase (car v) - (`sql-user 'user) - (`sql-password 'password) - (`sql-server 'server) - (`sql-database 'database) - (`sql-port 'port) + ('sql-user 'user) + ('sql-password 'password) + ('sql-server 'server) + ('sql-database 'database) + ('sql-port 'port) (s s))) connect-set)) @@ -4167,11 +4382,11 @@ optionally is saved to the user's init file." `(product ,@login) (lambda (token _plist) (pcase token - (`product `(sql-product ',product)) - (`user `(sql-user ,user)) - (`database `(sql-database ,database)) - (`server `(sql-server ,server)) - (`port `(sql-port ,port))))))) + ('product `(sql-product ',product)) + ('user `(sql-user ,user)) + ('database `(sql-database ,database)) + ('server `(sql-server ,server)) + ('port `(sql-port ,port))))))) (setq alist (append alist (list connect))) @@ -4215,31 +4430,30 @@ the call to \\[sql-product-interactive] with ;; Handle universal arguments if specified (when (not (or executing-kbd-macro noninteractive)) - (when (and (consp product) - (not (cdr product)) - (numberp (car product))) - (when (>= (prefix-numeric-value product) 16) - (when (not new-name) - (setq new-name '(4))) - (setq product '(4))))) + (when (>= (prefix-numeric-value product) 16) + (when (not new-name) + (setq new-name '(4))) + (setq product '(4)))) ;; Get the value of product that we need (setq product (cond ((= (prefix-numeric-value product) 4) ; C-u, prompt for product (sql-read-product "SQL product: " sql-product)) - ((and product ; Product specified - (symbolp product)) product) + ((assoc product sql-product-alist) ; Product specified + product) (t sql-product))) ; Default to sql-product ;; If we have a product and it has an interactive mode (if product (when (sql-get-product-feature product :sqli-comint-func) - ;; If no new name specified, try to pop to an active SQL - ;; interactive for the same product + ;; If no new name specified or new name in buffer name, + ;; try to pop to an active SQL interactive for the same product (let ((buf (sql-find-sqli-buffer product sql-connection))) - (if (and (not new-name) buf) - (pop-to-buffer buf) + (if (and buf (or (not new-name) + (and (stringp new-name) + (string-match-p (regexp-quote new-name) buf)))) + (sql-display-buffer buf) ;; We have a new name or sql-buffer doesn't exist or match ;; Start by remembering where we start @@ -4251,34 +4465,37 @@ the call to \\[sql-product-interactive] with (sql-get-product-feature product :sqli-login)) ;; Connect to database. - (setq rpt (make-progress-reporter "Login")) + (setq rpt (sql-make-progress-reporter nil "Login")) (let ((sql-user (default-value 'sql-user)) (sql-password (default-value 'sql-password)) (sql-server (default-value 'sql-server)) (sql-database (default-value 'sql-database)) (sql-port (default-value 'sql-port)) - (default-directory (or sql-default-directory - default-directory))) + (default-directory + (or sql-default-directory + default-directory))) + + ;; Call the COMINT service (funcall (sql-get-product-feature product :sqli-comint-func) product (sql-get-product-feature product :sqli-options) + ;; generate a buffer name (cond - ((null new-name) - "*SQL*") - ((stringp new-name) - (if (string-prefix-p "*SQL: " new-name t) - new-name - (concat "*SQL: " new-name "*"))) - ((equal new-name '(4)) - (concat - "*SQL: " + ((not new-name) + (sql-generate-unique-sqli-buffer-name product nil)) + ((consp new-name) + (sql-generate-unique-sqli-buffer-name product (read-string "Buffer name (\"*SQL: XXX*\"; enter `XXX'): " - sql-alternate-buffer-name) - "*")) + (sql-make-alternate-buffer-name product)))) + ((or (string-prefix-p " " new-name) + (string-match-p "\\`[*].*[*]\\'" new-name)) + new-name) + ((stringp new-name) + (sql-generate-unique-sqli-buffer-name product new-name)) (t - (format "*SQL: %s*" new-name))))) + (sql-generate-unique-sqli-buffer-name product nil))))) ;; Set SQLi mode. (let ((sql-interactive-product product)) @@ -4306,25 +4523,26 @@ the call to \\[sql-product-interactive] with (<= 0.0 (setq secs (- secs step)))) (progn (goto-char (point-max)) (not (re-search-backward sql-prompt-regexp 0 t)))) - (progress-reporter-update rpt))) + (sql-progress-reporter-update rpt))) (goto-char (point-max)) (when (re-search-backward sql-prompt-regexp nil t) (run-hooks 'sql-login-hook)) ;; All done. - (progress-reporter-done rpt) - (pop-to-buffer new-sqli-buffer) + (sql-progress-reporter-done rpt) (goto-char (point-max)) - (current-buffer))))) - (user-error "No default SQL product defined. Set `sql-product'."))) + (let ((sql-display-sqli-buffer-function t)) + (sql-display-buffer new-sqli-buffer)) + (get-buffer new-sqli-buffer))))) + (user-error "No default SQL product defined: set `sql-product'"))) (defun sql-comint (product params &optional buf-name) "Set up a comint buffer to run the SQL processor. PRODUCT is the SQL product. PARAMS is a list of strings which are passed as command line arguments. BUF-NAME is the name of the new -buffer. If nil, a name is chosen for it." +buffer. If nil, a name is chosen for it." (let ((program (sql-get-product-feature product :sqli-program))) ;; Make sure we can find the program. `executable-find' does not @@ -4337,15 +4555,10 @@ buffer. If nil, a name is chosen for it." ;; if not specified, try *SQL* then *SQL-product*, then *SQL-product1*, ... ;; otherwise, use *buf-name* (if buf-name - (unless (string-match-p "\\`[*].*[*]\\'" buf-name) + (unless (or (string-prefix-p " " buf-name) + (string-match-p "\\`[*].*[*]\\'" buf-name)) (setq buf-name (concat "*" buf-name "*"))) - (setq buf-name "*SQL*") - (when (sql-buffer-live-p buf-name) - (setq buf-name (format "*SQL-%s*" product))) - (let ((i 1)) - (while (sql-buffer-live-p buf-name) - (setq buf-name (format "*SQL-%s%d*" product i) - i (1+ i))))) + (setq buf-name (sql-generate-unique-sqli-buffer-name product nil))) (set-text-properties 0 (length buf-name) nil buf-name) ;; Start the command interpreter in the buffer @@ -4426,7 +4639,8 @@ The default comes from `process-coding-system-alist' and (or coding 'utf-8)) (when (string-match (format "\\.%s\\'" (car cs)) nlslang) (setq coding (cdr cs))))) - (set-buffer-process-coding-system coding coding))) + (set-process-coding-system (get-buffer-process (current-buffer)) + coding coding))) (defun sql-oracle-save-settings (sqlbuf) "Save most SQL*Plus settings so they may be reset by \\[sql-redirect]." @@ -4787,6 +5001,46 @@ The default comes from `process-coding-system-alist' and (list sql-database))))) (sql-comint product params buf-name))) +;;;###autoload +(defun sql-mariadb (&optional buffer) + "Run mysql by MariaDB as an inferior process. + +MariaDB is free software. + +If buffer `*SQL*' exists but no process is running, make a new process. +If buffer exists and a process is running, just switch to buffer +`*SQL*'. + +Interpreter used comes from variable `sql-mariadb-program'. Login uses +the variables `sql-user', `sql-password', `sql-database', and +`sql-server' as defaults, if set. Additional command line parameters +can be stored in the list `sql-mariadb-options'. + +The buffer is put in SQL interactive mode, giving commands for sending +input. See `sql-interactive-mode'. + +To set the buffer name directly, use \\[universal-argument] +before \\[sql-mariadb]. Once session has started, +\\[sql-rename-buffer] can be called separately to rename the +buffer. + +To specify a coding system for converting non-ASCII characters +in the input and output to the process, use \\[universal-coding-system-argument] +before \\[sql-mariadb]. You can also specify this with \\[set-buffer-process-coding-system] +in the SQL buffer, after you start the process. +The default comes from `process-coding-system-alist' and +`default-process-coding-system'. + +\(Type \\[describe-mode] in the SQL buffer for a list of commands.)" + (interactive "P") + (sql-product-interactive 'mariadb buffer)) + +(defun sql-comint-mariadb (product options &optional buf-name) + "Create comint buffer and connect to MariaDB. + +Use the MySQL comint driver since the two are compatible." + (sql-comint-mysql product options buf-name)) + ;;;###autoload |