initial commit
This commit is contained in:
commit
79b70390a1
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
.DS_Store
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
/out
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
.lein-*
|
||||||
|
.nrepl-port
|
||||||
|
/*.project
|
||||||
|
/*.classpath
|
||||||
|
/.settings/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Gered King
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
21
README.md
Normal file
21
README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# views-sql
|
||||||
|
|
||||||
|
SQL plugin for the [views][1] library. Allows for plain SQL strings to
|
||||||
|
be used.
|
||||||
|
|
||||||
|
[1]: https://github.com/gered/views
|
||||||
|
|
||||||
|
Implementation is largely based on the views-honeysql library.
|
||||||
|
|
||||||
|
Note that this library leverages [JSqlParser][2] for parsing SQL
|
||||||
|
queries and extracting the view system hint information needed.
|
||||||
|
JSqlParser is not perfect and will not be able to parse some more
|
||||||
|
complex queries and/or queries using some vendor-specific extensions.
|
||||||
|
|
||||||
|
[2]: https://github.com/JSQLParser/JSqlParser
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2016 Gered King
|
||||||
|
|
||||||
|
Distributed under the MIT License.
|
15
project.clj
Normal file
15
project.clj
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
(defproject gered/views-sql "0.1.0-SNAPSHOT"
|
||||||
|
:description "Plain SQL view implementation for views"
|
||||||
|
:url "https://github.com/gered/views-honeysql"
|
||||||
|
|
||||||
|
:license {:name "MIT License"
|
||||||
|
:url "http://opensource.org/licenses/MIT"}
|
||||||
|
|
||||||
|
:dependencies [[org.clojure/tools.logging "0.3.1"]
|
||||||
|
[com.github.jsqlparser/jsqlparser "0.9.5"]]
|
||||||
|
|
||||||
|
:profiles {:provided
|
||||||
|
{:dependencies
|
||||||
|
[[org.clojure/clojure "1.8.0"]
|
||||||
|
[org.clojure/java.jdbc "0.6.1"]
|
||||||
|
[gered/views "1.5-SNAPSHOT"]]}})
|
95
src/views/sql/core.clj
Normal file
95
src/views/sql/core.clj
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
(ns views.sql.core
|
||||||
|
(:import
|
||||||
|
(net.sf.jsqlparser.parser CCJSqlParserUtil)
|
||||||
|
(net.sf.jsqlparser.util TablesNamesFinder)
|
||||||
|
(net.sf.jsqlparser.statement Statement)
|
||||||
|
(net.sf.jsqlparser.statement.update Update)
|
||||||
|
(net.sf.jsqlparser.statement.delete Delete)
|
||||||
|
(net.sf.jsqlparser.statement.insert Insert)
|
||||||
|
(net.sf.jsqlparser.statement.select Select))
|
||||||
|
(:require
|
||||||
|
[clojure.java.jdbc :as jdbc]
|
||||||
|
[views.core :as views]))
|
||||||
|
|
||||||
|
(def hint-type :sql-table-name)
|
||||||
|
|
||||||
|
(def ^:private query-types
|
||||||
|
{Select :select
|
||||||
|
Insert :insert
|
||||||
|
Update :update
|
||||||
|
Delete :delete})
|
||||||
|
|
||||||
|
(defn- get-query-tables-set
|
||||||
|
[^Statement stmt]
|
||||||
|
(as-> (TablesNamesFinder.) x
|
||||||
|
(.getTableList x stmt)
|
||||||
|
(map keyword x)
|
||||||
|
(set x)))
|
||||||
|
|
||||||
|
(defn- sql-stmt-returning?
|
||||||
|
[^Statement stmt stmt-type]
|
||||||
|
(condp = stmt-type
|
||||||
|
:select true
|
||||||
|
:insert (let [return-expr (.getReturningExpressionList ^Insert stmt)
|
||||||
|
returning-all? (.isReturningAllColumns ^Insert stmt)]
|
||||||
|
(or returning-all?
|
||||||
|
(and return-expr
|
||||||
|
(not (.isEmpty return-expr)))))
|
||||||
|
; TODO: JSqlParser doesn't currently support PostgreSQL's RETURNING clause
|
||||||
|
; support in UPDATE and DELETE queries
|
||||||
|
:update false
|
||||||
|
:delete false))
|
||||||
|
|
||||||
|
(defn- get-query-info
|
||||||
|
[^String sql]
|
||||||
|
(let [stmt (CCJSqlParserUtil/parse sql)
|
||||||
|
stmt-type (get query-types (type stmt))]
|
||||||
|
(if-not stmt-type
|
||||||
|
(throw (new Exception "Unsupported SQL query. Only SELECT, INSERT, UPDATE and DELETE queries are supported!"))
|
||||||
|
{:type stmt-type
|
||||||
|
:returning? (sql-stmt-returning? stmt stmt-type)
|
||||||
|
:tables (get-query-tables-set stmt)})))
|
||||||
|
|
||||||
|
(defn query-info
|
||||||
|
[^String sql]
|
||||||
|
(if-let [info (get-in @views/view-system [:views-sql :cache sql])]
|
||||||
|
info
|
||||||
|
(let [info (get-query-info sql)]
|
||||||
|
(swap! views/view-system assoc-in [:views-sql :cache sql] info)
|
||||||
|
info)))
|
||||||
|
|
||||||
|
(defn query-tables
|
||||||
|
[^String sql]
|
||||||
|
(:tables (query-info sql)))
|
||||||
|
|
||||||
|
(defmacro with-view-transaction
|
||||||
|
[binding & forms]
|
||||||
|
(let [tvar (first binding)
|
||||||
|
db (second binding)
|
||||||
|
args (drop 2 binding)]
|
||||||
|
`(if (:views-sql/hints ~db) ;; check if we are in a nested transaction
|
||||||
|
(let [~tvar ~db] ~@forms)
|
||||||
|
(let [hints# (atom [])
|
||||||
|
result# (jdbc/with-db-transaction [t# ~db ~@args]
|
||||||
|
(let [~tvar (assoc ~db :views-sql/hints hints#)]
|
||||||
|
~@forms))]
|
||||||
|
(views/put-hints! @hints#)
|
||||||
|
result#))))
|
||||||
|
|
||||||
|
(defn- execute-sql!
|
||||||
|
[db [sql & params :as sqlvec]]
|
||||||
|
(let [info (query-info sql)]
|
||||||
|
(if (:returning? info)
|
||||||
|
(jdbc/query db sqlvec)
|
||||||
|
(jdbc/execute! db sqlvec))))
|
||||||
|
|
||||||
|
(defn vexec!
|
||||||
|
([db namespace [sql & params :as sqlvec]]
|
||||||
|
(let [results (execute-sql! db sqlvec)
|
||||||
|
hint (views/hint namespace (query-tables sql) hint-type)]
|
||||||
|
(if-let [tx-hints (:views-sql/hints db)]
|
||||||
|
(swap! tx-hints conj hint)
|
||||||
|
(views/put-hints! [hint]))
|
||||||
|
results))
|
||||||
|
([db [sql & params :as sqlvec]]
|
||||||
|
(vexec! db nil sqlvec)))
|
38
src/views/sql/view.clj
Normal file
38
src/views/sql/view.clj
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
(ns views.sql.view
|
||||||
|
(:require
|
||||||
|
[views.protocols :refer [IView]]
|
||||||
|
[views.sql.core :refer [hint-type query-tables]]
|
||||||
|
[clojure.set :refer [intersection]]
|
||||||
|
[clojure.java.jdbc :as jdbc]
|
||||||
|
[clojure.tools.logging :refer [warn]]))
|
||||||
|
|
||||||
|
; this implementation based on views-honeysql
|
||||||
|
|
||||||
|
(defrecord SQLView [id db-or-db-fn query-fn row-fn]
|
||||||
|
IView
|
||||||
|
(id [_] id)
|
||||||
|
(data [_ namespace parameters]
|
||||||
|
(let [db (if (fn? db-or-db-fn)
|
||||||
|
(db-or-db-fn namespace)
|
||||||
|
db-or-db-fn)
|
||||||
|
start (System/currentTimeMillis)
|
||||||
|
data (jdbc/query db (apply query-fn parameters) {:row-fn row-fn})
|
||||||
|
time (- (System/currentTimeMillis) start)]
|
||||||
|
(when (>= time 1000) (warn id "took" time "msecs"))
|
||||||
|
data))
|
||||||
|
(relevant? [_ namespace parameters hints]
|
||||||
|
(let [[sql & sqlparams] (apply query-fn parameters)
|
||||||
|
tables (query-tables sql)
|
||||||
|
nhints (filter #(and (= namespace (:namespace %))
|
||||||
|
(= hint-type (:type %))) hints)]
|
||||||
|
(boolean (some #(not-empty (intersection (:hint %) tables)) nhints)))))
|
||||||
|
|
||||||
|
(defn view
|
||||||
|
"Creates a SQL view that uses a JDBC database configuration.
|
||||||
|
|
||||||
|
db-or-db-fn - either a database connection map, or a function that will get passed
|
||||||
|
a namespace and should return a database connection map
|
||||||
|
sql-fn - a function that returns a sqlvec format SELECT query to be run when
|
||||||
|
this view is refreshed"
|
||||||
|
[id db-or-db-fn sql-fn & {:keys [row-fn]}]
|
||||||
|
(SQLView. id db-or-db-fn sql-fn (or row-fn identity)))
|
Loading…
Reference in a new issue