├── README.org └── ob-cypher.el /README.org: -------------------------------------------------------------------------------- 1 | * ob-cypher 2 | 3 | [[http://melpa.org/#/ob-cypher][file:http://melpa.org/packages/ob-cypher-badge.svg]] 4 | 5 | query neo4j using cypher in org-mode blocks 6 | 7 | ** install 8 | 9 | #+begin_src emacs-lisp 10 | (use-package ob-cypher 11 | :ensure t 12 | :config 13 | (add-to-list 'org-babel-load-languages '(cypher . t)) 14 | (org-babel-do-load-languages 'org-babel-load-languages org-babel-load-languages) 15 | (add-to-list 'org-babel-tangle-lang-exts '("cypher" . "cypher"))) 16 | #+end_src 17 | 18 | ** options 19 | 20 | | option | example | 21 | |--------------+----------------------------------------------| 22 | | =:host= | =127.0.0.1= | 23 | | =:port= | =1337= | 24 | | =:http-port= | =7474= | 25 | | =:username= | ="neo4j"= | 26 | | =:password= | ="neo4j"= | 27 | | =:file= | requires curl and graphviz =:file graph.png= | 28 | 29 | ** examples 30 | 31 | : #+BEGIN_SRC cypher 32 | : CREATE (matrix1:Movie { title : 'The Matrix', year : '1999-03-31' }) 33 | : CREATE (matrix2:Movie { title : 'The Matrix Reloaded', year : '2003-05-07' }) 34 | : CREATE (matrix3:Movie { title : 'The Matrix Revolutions', year : '2003-10-27' }) 35 | : CREATE (keanu:Actor { name:'Keanu Reeves' }) 36 | : CREATE (laurence:Actor { name:'Laurence Fishburne' }) 37 | : CREATE (carrieanne:Actor { name:'Carrie-Anne Moss' }) 38 | : CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix1) 39 | : CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix2) 40 | : CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix3) 41 | : CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix1) 42 | : CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix2) 43 | : CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix3) 44 | : CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix1) 45 | : CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix2) 46 | : CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix3) 47 | : #+END_SRC 48 | 49 | : #+RESULTS: 50 | : : +-------------------+ 51 | : : | No data returned. | 52 | : : +-------------------+ 53 | : : Nodes created: 6 54 | : : Relationships created: 9 55 | : : Properties set: 18 56 | : : Labels added: 6 57 | : : 1353 ms 58 | 59 | : #+BEGIN_SRC cypher :file movie.png 60 | : match (actor)-[r:ACTS_IN]->(movie) 61 | : return * 62 | : #+END_SRC 63 | 64 | : #+RESULTS: 65 | 66 | [[http://i.imgur.com/dpCyOo5.png]] 67 | -------------------------------------------------------------------------------- /ob-cypher.el: -------------------------------------------------------------------------------- 1 | ;;; ob-cypher.el --- query neo4j using cypher in org-mode blocks 2 | 3 | ;; Copyright (C) 2015 ZHOU Feng 4 | 5 | ;; Author: ZHOU Feng 6 | ;; URL: http://github.com/zweifisch/ob-cypher 7 | ;; Keywords: org babel cypher neo4j 8 | ;; Version: 0.0.1 9 | ;; Created: 8th Feb 2015 10 | ;; Package-Requires: ((s "1.9.0") (cypher-mode "0.0.6") (dash "2.10.0") (dash-functional "1.2.0")) 11 | 12 | ;; This file is free software; you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation; either version 3, or (at your option) 15 | ;; any later version. 16 | 17 | ;; This file is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; 27 | ;; query neo4j using cypher in org-mode blocks 28 | ;; 29 | 30 | ;;; Code: 31 | (require 'org) 32 | (require 'ob) 33 | (require 's) 34 | (require 'dash) 35 | (require 'json) 36 | 37 | (defvar org-babel-default-header-args:cypher 38 | '((:results . "output"))) 39 | 40 | (defun ob-cypher/parse-result (output) 41 | (->> (s-lines output) 42 | (-filter (-partial 's-starts-with? "|")) 43 | (-map (-partial 's-chop-suffix "|")) 44 | (-map (-partial 's-chop-prefix "|")) 45 | (-map (-partial 's-split " | ")))) 46 | 47 | (defun ob-cypher/table (output) 48 | (org-babel-script-escape (ob-cypher/parse-result output))) 49 | 50 | (defun ob-cypher/property (property) 51 | (format "%s: %s" (car property) (cdr property))) 52 | 53 | (defun ob-cypher/node-to-dot (node) 54 | (let ((labels (cdr (assoc 'labels node)))) 55 | (s-format "n${id} [label=\"{${head} ${body}}\"]" 'aget 56 | `(("id" . ,(cdr (assoc 'id node))) 57 | ("head" . ,(if (> (length labels) 0) (concat "" (s-join ":" labels) "|") "")) 58 | ("body" . ,(s-join "\\n" (-map 'ob-cypher/property (cdr (assoc 'properties node))))))))) 59 | 60 | (defun ob-cypher/rel-to-dot (rel) 61 | (s-format "n${start} -> n${end} [label = ${label}]" 'aget 62 | `(("start" . ,(cdr (assoc 'startNode rel))) 63 | ("end" . ,(cdr (assoc 'endNode rel))) 64 | ("label" . ,(cdr (assoc 'type rel)))))) 65 | 66 | (defun ob-cypher/json-to-dot (output) 67 | (let* ((parsed (json-read-from-string output)) 68 | (results (cdr (assoc 'results parsed))) 69 | (data (if (> (length results) 0) 70 | (cdr (assoc 'data (elt results 0))))) 71 | (graphs (-map (lambda (graph) (cdr (assoc 'graph graph))) 72 | data)) 73 | (rels (-mapcat 74 | (lambda (graph) 75 | (append (cdr (assoc 'relationships graph)) nil)) 76 | graphs)) 77 | (nodes (-mapcat 78 | (lambda (graph) 79 | (append (cdr (assoc 'nodes graph)) nil)) 80 | graphs))) 81 | (s-format "digraph {\nnode[shape=Mrecord]\n${nodes}\n${rels}\n} " 'aget 82 | `(("nodes" . ,(s-join "\n" (-map 'ob-cypher/node-to-dot nodes))) 83 | ("rels" . ,(s-join "\n" (-map 'ob-cypher/rel-to-dot rels))))))) 84 | 85 | (defun ob-cypher/json-to-table (output) 86 | (let* ((json-array-type 'list) 87 | (parsed (json-read-from-string output)) 88 | (results (cdr (assoc 'results parsed))) 89 | (data (if (> (length results) 0) 90 | (cdr (assoc 'data (elt results 0))))) 91 | (columns (if (> (length results) 0) 92 | (cdr (assoc 'columns (elt results 0))))) 93 | (rows (-map (lambda (row) (cdr (assoc 'row row))) data))) 94 | (cons columns (cons 'hline rows)))) 95 | 96 | 97 | (defun ob-cypher/query (statement host port authstring) 98 | (let* ((statement (s-replace "\"" "\\\"" statement)) 99 | (body (format "{\"statements\":[{\"statement\":\"%s\",\"resultDataContents\":[\"graph\",\"row\"]}]}" 100 | (s-join " " (s-lines statement)))) 101 | (url (format "http://%s:%d/db/data/transaction/commit" host port)) 102 | (tmp (org-babel-temp-file "cypher-curl-")) 103 | (cmd (format "curl -sH 'Accept: application/json; charset=UTF-8' -H 'Content-Type: application/json' -H 'Authorization: Basic %s' -d@'%s' '%s'" authstring tmp url))) 104 | (message cmd) 105 | (with-temp-file tmp 106 | (insert body)) 107 | (shell-command-to-string cmd))) 108 | 109 | (defun ob-cypher/dot (statement host port output authstring) 110 | (let* ((tmp (org-babel-temp-file "cypher-dot-")) 111 | (result (ob-cypher/query statement host port authstring)) 112 | (dot (ob-cypher/json-to-dot result)) 113 | (cmd (format "dot -T%s -o %s %s" (file-name-extension output) output tmp))) 114 | (message result) 115 | (message dot) 116 | (message cmd) 117 | (with-temp-file tmp 118 | (insert dot)) 119 | (org-babel-eval cmd "") 120 | nil)) 121 | 122 | (defun ob-cypher/rest (statement host port authstring) 123 | (let* ((tmp (org-babel-temp-file "cypher-rest-")) 124 | (result (ob-cypher/query statement host port authstring)) 125 | (tbl (ob-cypher/json-to-table result))) 126 | (message result) 127 | tbl)) 128 | 129 | (defun ob-cypher/shell (statement host port result-type) 130 | (let* ((tmp (org-babel-temp-file "cypher-shell-")) 131 | (cmd (s-format "neo4j-shell -host ${host} -port ${port} -file ${file}" 'aget 132 | `(("host" . ,host) 133 | ("port" . ,(int-to-string port)) 134 | ("file" . ,tmp)))) 135 | (result (progn (with-temp-file tmp (insert statement)) 136 | (shell-command-to-string cmd)))) 137 | (message cmd) 138 | (if (string= "output" result-type) result (ob-cypher/table result)))) 139 | 140 | (defun org-babel-execute:cypher (body params) 141 | (let* ((host (or (cdr (assoc :host params)) "127.0.0.1")) 142 | (port (or (cdr (assoc :port params)) 1337)) 143 | (username (or (cdr (assoc :username params)) "neo4j")) 144 | (password (or (cdr (assoc :password params)) "neo4j")) 145 | (authstring (base64-encode-string (concat username ":" password))) 146 | (http-port (or (cdr (assoc :http-port params)) 7474)) 147 | (result-type (cdr (assoc :result-type params))) 148 | (output (cdr (assoc :file params))) 149 | (body (if (s-ends-with? ";" body) body (s-append ";" body)))) 150 | (if output 151 | (ob-cypher/dot body host http-port output authstring) 152 | (ob-cypher/rest body host http-port authstring)))) 153 | 154 | (provide 'ob-cypher) 155 | ;;; ob-cypher.el ends here 156 | --------------------------------------------------------------------------------