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#)))) result#))))
(defn- execute-sql! (defn- execute-sql!
[db [sql & params :as sqlvec]] [db [sql & params :as sqlvec] returning?]
(let [info (query-info sql)] (if returning?
(if (:returning? info) (jdbc/query db sqlvec)
(jdbc/query db sqlvec) (jdbc/execute! 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! (defn vexec!
"Used to run any SQL insert/update/delete query on the database while ensuring "Used to run any SQL insert/update/delete query on the database while ensuring
@ -96,15 +106,30 @@
Arguments are: Arguments are:
- db: a clojure.java.jdbc database connection - 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 - sqlvec: a JDBC-style vector containing a SQL query string followed by any
parameters for the query." parameters for the query.
([db namespace [sql & params :as sqlvec]]
(let [results (execute-sql! db sqlvec) Options are:
hint (views/hint namespace (query-tables sql) hint-type)] - namespace: a namespace that will be included in the hints sent out
(if-let [tx-hints (:views-sql/hints db)]
(swap! tx-hints conj hint) NOTE:
(views/put-hints! [hint])) If the SQL being run cannot be parsed (e.g. due to use of database-specific
results)) extensions, or other limitations of JSqlParser), you will need to manually
([db [sql & params :as sqlvec]] specify the list of table names (as keywords) that the SQL query will affect
(vexec! db nil sqlvec))) 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")) (when (>= time 1000) (warn id "took" time "msecs"))
data)) data))
(relevant? [_ namespace parameters hints] (relevant? [_ namespace parameters hints]
(let [[sql & sqlparams] (apply query-fn parameters) (let [[sql & _] (apply query-fn parameters)
tables (query-tables sql) tables (if (:parse? options)
nhints (filter #(and (= namespace (:namespace %)) (query-tables sql)
(= hint-type (:type %))) hints)] (:tables options))
nhints (filter #(and (= namespace (:namespace %))
(= hint-type (:type %))) hints)]
(boolean (some #(not-empty (intersection (:hint %) tables)) nhints))))) (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 (defn view
"Creates a SQL view that uses a JDBC database configuration. "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 Arguments are:
a namespace and should return a database connection map - id: an id for this view that is unique within the view system
sql-fn - a function that returns a JDBC-style vector containing a SELECT query - db-or-db-fn: either a database connection map, or a function that will get
followed by any parameters. this query will be run whenever this view passed a namespace and should return a database connection map
needs to be refreshed." - sql-fn: a function that returns a JDBC-style vector containing a SELECT
[id db-or-db-fn sql-fn & {:keys [row-fn result-set-fn] :as options query followed by any parameters. this query will be run whenever
:or {row-fn identity this view needs to be refreshed.
result-set-fn doall}}]
(SQLView. id db-or-db-fn sql-fn options)) 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)))))