add ability to manually provide table name hints for unparseable sql

an important backup to allow SQL that JSqlParser cannot handle still
be used in the views system. JSqlParser is still actively maintained
and will get better over time, but it cannot handle everything thrown
at it.
This commit is contained in:
Gered 2016-05-22 22:07:26 -04:00
parent d82702d946
commit a86d4eef17
2 changed files with 83 additions and 29 deletions

View file

@ -81,11 +81,21 @@
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))))
[db [sql & params :as sqlvec] returning?]
(if returning?
(jdbc/query db sqlvec)
(jdbc/execute! db sqlvec)))
(defn- vexec!*
[db [sql & params :as sqlvec] {:keys [parse? namespace tables returning?] :as options}]
(let [tables (if parse? (query-tables sql) tables)
returning? (if parse? (:returning? (query-info sql)) returning?)
results (execute-sql! db sqlvec returning?)
hint (views/hint namespace tables hint-type)]
(if-let [tx-hints (:views-sql/hints db)]
(swap! tx-hints conj hint)
(views/put-hints! [hint]))
results))
(defn vexec!
"Used to run any SQL insert/update/delete query on the database while ensuring
@ -96,15 +106,30 @@
Arguments are:
- db: a clojure.java.jdbc database connection
- namespace (optional): a namespace that will be included in the hints sent out
- sqlvec: a JDBC-style vector containing a SQL query string followed by any
parameters for the query."
([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)))
parameters for the query.
Options are:
- namespace: a namespace that will be included in the hints sent out
NOTE:
If the SQL being run cannot be parsed (e.g. due to use of database-specific
extensions, or other limitations of JSqlParser), you will need to manually
specify the list of table names (as keywords) that the SQL query will affect
as the optional tables argument. In addition, if the SQL query includes a
RETURNING clause, you should specify :returning true in the options given
to vexec!, since automatic detection of this will not work if the SQL cannot
be parsed."
{:arglists '([db sqlvec & options]
[db sqlvec tables & options])}
[db sqlvec & options]
(let [[first-option & [other-options]] options]
(if (or (nil? options)
(map? first-option))
(vexec!* db sqlvec (-> first-option
(assoc :parse? true)
(dissoc :returning?)))
(vexec!* db sqlvec (assoc other-options
:tables (set first-option)
:parse? false)))))

View file

@ -23,21 +23,50 @@
(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)]
(let [[sql & _] (apply query-fn parameters)
tables (if (:parse? options)
(query-tables sql)
(:tables options))
nhints (filter #(and (= namespace (:namespace %))
(= hint-type (:type %))) hints)]
(boolean (some #(not-empty (intersection (:hint %) tables)) nhints)))))
(defn- view*
[id db-or-db-fn sql-fn {:keys [row-fn result-set-fn tables]
:or {row-fn identity
result-set-fn doall}
:as options}]
(SQLView. id db-or-db-fn sql-fn options))
(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 JDBC-style vector containing a SELECT query
followed by any parameters. this query will be run whenever this view
needs to be refreshed."
[id db-or-db-fn sql-fn & {:keys [row-fn result-set-fn] :as options
:or {row-fn identity
result-set-fn doall}}]
(SQLView. id db-or-db-fn sql-fn options))
Arguments are:
- id: an id for this view that is unique within the view system
- 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 JDBC-style vector containing a SELECT
query followed by any parameters. this query will be run whenever
this view needs to be refreshed.
Options are:
- row-fn: a function that if specified will be run against each row in the
view's result set before returning it.
- result-set-fn: a function that will be run against the entire view's result
set before returning it.
NOTE:
If the SQL being run cannot be parsed (e.g. due to use of database-specific
extensions, or other limitations of JSqlParser), you will need to manually
specify the list of table names (as keywords) that the SQL query will affect
as the optional tables argument."
{:arglists '([id db-or-db-fn sql-fn & options]
[id db-or-db-fn sql-fn tables & options])}
[id db-or-db-fn sql-fn & options]
(let [[first-option & [other-options]] options]
(if (or (nil? options)
(map? first-option))
(view* id db-or-db-fn sql-fn (assoc first-option :parse? true))
(view* id db-or-db-fn sql-fn (assoc other-options
:tables (set first-option)
:parse? false)))))