initial commit

This commit is contained in:
Gered 2016-05-22 15:26:20 -04:00
commit 79b70390a1
6 changed files with 208 additions and 0 deletions

18
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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)))