Как я могу заставить emacs sql-mode использовать конфигурационный файл mysql (.my.cnf)?

#mysql #emacs #sql-mode

#mysql #emacs #sql-mode

Вопрос:

Когда я набираю mysql dbname в командной строке bash, я автоматически подключаюсь к базе данных dbname с username , password и host информацией, включенной в мой .my.cnf файл.

Когда я использую M-x sql-mysql в emacs, у меня снова запрашивают всю эту информацию.

Есть ли способ заставить emacs sql mode использовать информацию в моем .my.cnf файле?

Ответ №1:

Я не думаю, что это возможно, но вы можете установить такие вещи в самой конфигурации режима:

 (setq sql-connection-alist
'((pool-a
(sql-product 'mysql)
(sql-server "1.2.3.4")
(sql-user "me")
(sql-password "mypassword")
(sql-database "thedb")
(sql-port 3306))
(pool-b
(sql-product 'mysql)
(sql-server "1.2.3.4")
(sql-user "me")
(sql-password "mypassword")
(sql-database "thedb")
(sql-port 3307))))

(defun sql-connect-preset (name)
  "Connect to a predefined SQL connection listed in `sql-connection-alist'"
  (eval `(let ,(cdr (assoc name sql-connection-alist))
    (flet ((sql-get-login (amp;rest what)))
      (sql-product-interactive sql-product)))))

(defun sql-pool-a ()
  (interactive)
  (sql-connect-preset 'pool-a))
  

Взгляните на эту статью для получения дополнительной информации.

Комментарии:

1. Поскольку flet макрос устарел в Emacs 24.3 , я заменил его на noflet , из этого пакета github.com/nicferrier/emacs-noflet , и это работает.

2. Ссылка на статью кажется неработающей…

Ответ №2:

Уверен, что это возможно. Хотя это довольно сложно.

Грубо говоря, шаги следующие:

  1. Чтение и синтаксический анализ ini-файла с помощью ini.el.
  2. Объединение конфигурации разных хостов с конфигурацией клиентов, поскольку последняя применяется глобально.
  3. Преобразование каждого хоста в подходящий формат для sql-connection-alist
  4. Заполнение sql-connection-alist
  5. Используя M-x sql-connect (который автоматически завершается!)

Следующий код также включает в себя анализатор .pgpass, на всякий случай. Вы заметите, что реализация проще.

 ;;; .pgpass parser
(defun read-file (file)
  "Returns file as list of lines."
  (with-temp-buffer
    (insert-file-contents file)
    (split-string (buffer-string) "n" t)))

(defun pgpass-to-sql-connection (config)
  "Returns a suitable list for sql-connection-alist from a pgpass file."
          (let ((server (lambda (host port db user _pass)
                          (list
                           (concat db ":" user ":" port ":" host)
                           (list 'sql-product ''postgres)
                           (list 'sql-server host)
                           (list 'sql-user user)
                           (list 'sql-port (string-to-number port))
                           (list 'sql-database db))))
                (pgpass-line (lambda (line)
                               (apply server (split-string line ":" t)))))
            (mapcar pgpass-line config)))

;;; .my.cnf parser
;;; Copied verbatim from https://github.com/daniel-ness/ini.el/blob/master/ini.el
(defun ini-decode (ini_text) 
  ;; text -> alist
  (interactive)
  (if (not (stringp ini_text))
      (error "Must be a string"))
  (let ((lines (split-string ini_text "n"))
    (section)
    (section-list)
    (alist))
    (dolist (l lines)
      ;; skip comments
      (unless (or (string-match "^;" l)
          (string-match "^[ t]$" l))
    ;; catch sections
    (if (string-match "^\[\(.*\)\]$" l)
        (progn 
          (if section
          ;; add as sub-list
          (setq alist (cons `(,section . ,section-list) alist))
        (setq alist section-list))
          (setq section (match-string 1 l))
          (setq section-list nil)))
          ;; catch properties
          (if (string-match "^\([^st] \)[st]*=[st]*\(. \)$" l)
          (let ((property (match-string 1 l))
            (value (match-string 2 l)))
            (progn 
              (setq section-list (cons `(,property . ,value) section-list)))))))
    (if section
    ;; add as sub-list
    (setq alist (cons `(,section . ,section-list) alist))
      (setq alist section-list))
    alist))

(defun read-ini (file)
  "Returns ini file as alist."
  (with-temp-buffer
    (insert-file-contents file)
    (ini-decode (buffer-string))))

(defun filter-alist (wanted-members alist)
  "Returns a copy of given alist, with only fields from wanted-members."
  (let ((result nil)
        (add-if-member (lambda (elt)
                         (when (member (car elt) wanted-members)
                           (add-to-list 'result elt t)))))
    (mapc add-if-member alist)
    result))

(defun merge-alist (original override)
  "Returns a union of original and override alist. On key conflict, the latter wins."
  (let ((result (copy-alist override))
        (add (lambda (elt)
               (setq result (add-to-list
                             'result elt t
                             (lambda (left right) (equal (car left) (car right))))))))
    (mapc add original)
    result))
    
(defun parse-mycnf-hosts (file-path)
  "Returns list of hosts with clients' section applied to all hosts."
  (let ((hosts nil)
        (global nil)
        (fields '("user" "host" "database" "password" "port"))
        (section-parse (lambda(section)
                         (if (equal (car section) "client")
                             (setq global (filter-alist fields (cdr section)))
                           (let ((host (car section))
                                 (config (filter-alist fields (cdr section))))
                             (when config (add-to-list 'hosts (cons host config) t))))))
        (merge-host-with-global (lambda (host)
                                  (cons (car host) (merge-alist global (cdr host))))))
    (mapc section-parse (read-ini file-path))
    (mapcar merge-host-with-global hosts)))

(defun mycnf-to-sql-connection (config)
  (let ((add-sql-product
     (lambda (config)
       (let ((head (car config))
             (tail (cdr config)))
         (cons head (append tail (list (list 'sql-product ''mysql)))))))
    (parse-keys-and-values
     (lambda (config)
       (let ((head (car config))
             (tail (cdr config)))
         (cons
          head
          (mapcar
           (lambda (element)
            (let ((key (car element))
                  (value (cdr element)))
              (cond ((equal key "host") (list 'sql-server value))
                    ((equal key "port") (list 'sql-port (string-to-number value)))
                    ((equal key "user") (list 'sql-user value))
                    ((equal key "password") (list 'sql-password value))
                    ((equal key "database") (list 'sql-database value))
                    (t (error (format "Unknown key %s" key))))))
           tail))))))
  (mapcar add-sql-product (mapcar parse-keys-and-values config))))

;;; Actually populating sql-connection-alist
(setq sql-connection-alist
      (append
       (mycnf-to-sql-connection (parse-mycnf-hosts "~/.my.cnf"))
       (pgpass-to-sql-connection (read-file "~/.pgpass"))
       ))
  

С помощью следующего .my.cnf :

 [client]
user=me

[host1]
database=db1
host=db.host1.com

[host2]
database=db2
user=notme
host=db.host2.com
  

Выполнение выражения (mycnf-to-sql-connection (parse-mycnf-hosts "~/.my.cnf")) дает мне (довольно напечатанное от руки):

 (("host2" ((sql-server "db.host2.com")
           (sql-user "notme")
           (sql-database "db2")))
 ("host1" ((sql-server "db.host1.com")
           (sql-database "db1")
           (sql-user "me"))))
  

Наконец, вместо использования M-x sql-mysql используйте M-x sql-connect , и вы сможете подключиться, используя псевдоним, с автоматическим завершением.

Ответ №3:

Для этого нам нужно «обмануть» sql-mode для запуска mysql --login-path=some-connection-name some-db-name ( --login-path это параметр, который sql-mode не передает по умолчанию.)

Создайте свои именованные соединения на стороне sql через (по умолчанию хранятся в .mylogin.cnf ):

mysql_config_editor set --login-path=wow --host=127.0.0.1 --port=3306 --user=root --password

Затем обозначьте соединения на стороне emacs как so (в вашем init.el ):

 (setq sql-connection-alist
  '((wow-local
     (sql-product 'mysql)
     (sql-mysql-options '("--login-path=wow")) ; Note: use the login-path specified earlier
     (sql-server "") ; Note: All of these empty string parameters prevent being prompted for these values and are ignored.
     (sql-user "")
     (sql-password "")
     (sql-database "wowza"))
    (wow-local-test
     (sql-product 'mysql)
     (sql-mysql-options '("--login-path=wow")) ; Note: You can have multiple connections using the same login-path just with different parameters
     (sql-server "")
     (sql-user "")
     (sql-password "")
     (sql-database "wowza-test"))
    (production
     (sql-product 'mysql)
     (sql-mysql-options '("--login-path=production"))
     (sql-server "")
     (sql-port 0) ; Note: 0 ignores, anything else overrides your .cnf
     (sql-user "")
     (sql-password "")
     (sql-database "wowza_prod"))))
  

Наконец, когда наши sql-соединения созданы и наше соединение помечено на стороне emacs, мы можем запустить M-x sql-connect , который затем должен предложить вам выбрать одно из ваших любимых подключений.

Примечание:

  • установка переменной emacs sql-mysql-options позволяет вам использовать ваши подключения, как определено в файле .mylogin.cnf (YMMV в других файлах .cnf) (вы также можете использовать это для передачи любых параметров mysql, которые вы хотите)
  • установка sql-server , sql-user и т.д. в пустую строку предотвращает запрос указанного значения
  • настройка sql-server , sql-user и т.д… все, что угодно, кроме пустой строки, переопределит значения, установленные в .cnf
  • Смотрите M-x (describe-variable sql-mysql-options) , и M-x (describe-function sql-comint-mysql) в sql.el файле для получения дополнительной информации о логике вокруг игнорируемых параметров.

TLDR: использовать sql-mysql-options и --login-path=<your-login-path> магию

Ответ №4:

Просто нажмите return, он будет выбран по умолчанию.