From a86d4eef178f89499622487dfa76574d27aef3c4 Mon Sep 17 00:00:00 2001 From: gered Date: Sun, 22 May 2016 22:07:26 -0400 Subject: [PATCH] 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. --- src/views/sql/core.clj | 57 ++++++++++++++++++++++++++++++------------ src/views/sql/view.clj | 55 ++++++++++++++++++++++++++++++---------- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/src/views/sql/core.clj b/src/views/sql/core.clj index 583b2bc..7ec7dad 100644 --- a/src/views/sql/core.clj +++ b/src/views/sql/core.clj @@ -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))))) + diff --git a/src/views/sql/view.clj b/src/views/sql/view.clj index 361077c..90d715b 100644 --- a/src/views/sql/view.clj +++ b/src/views/sql/view.clj @@ -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)) \ No newline at end of file + 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)))))