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