From 0b4fd6ecbf2e857f71f095709ef0d153725016a6 Mon Sep 17 00:00:00 2001 From: Govind Krishna Joshi Date: Thu, 19 Aug 2021 19:47:02 +0530 Subject: [PATCH 1/4] Fix test description for drop extension --- test/honeysql_postgres/postgres_test.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/honeysql_postgres/postgres_test.cljc b/test/honeysql_postgres/postgres_test.cljc index 2f073e1..87c8ef3 100644 --- a/test/honeysql_postgres/postgres_test.cljc +++ b/test/honeysql_postgres/postgres_test.cljc @@ -367,7 +367,7 @@ :quoting :ansi)))))) (deftest drop-extension-test - (testing "create extension" + (testing "drop extension" (is (= ["DROP EXTENSION \"uuid-ossp\""] (-> (drop-extension :uuid-ossp) (sql/format :allow-dashed-names? true From 287519b13f107febc945b9a8020cce970dccb22e Mon Sep 17 00:00:00 2001 From: Govind Krishna Joshi Date: Thu, 19 Aug 2021 21:11:22 +0530 Subject: [PATCH 2/4] Add support for explain clause https://www.postgresql.org/docs/current/sql-explain.html --- src/honeysql_postgres/format.cljc | 35 +++++++++++- src/honeysql_postgres/helpers.cljc | 3 ++ test/honeysql_postgres/postgres_test.cljc | 65 +++++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/honeysql_postgres/format.cljc b/src/honeysql_postgres/format.cljc index ee54ea8..7c83228 100644 --- a/src/honeysql_postgres/format.cljc +++ b/src/honeysql_postgres/format.cljc @@ -31,7 +31,8 @@ (def ^:private postgres-clause-priorities "Determines the order that clauses will be placed within generated SQL" - (merge {:with 30 + (merge {:explain 20 + :with 30 :with-recursive 40 :except 45 :except-all 45 @@ -297,3 +298,35 @@ (-> extension-name util/get-first sqlf/to-sql))) + +(defmethod format-clause :explain [[_ [params]] _] + (if (empty? params) + "EXPLAIN" + (let [params-string-map + {:costs "COSTS" + :settings "SETTINGS" + :buffers "BUFFERS" + :wal "WAL" + :timing "TIMING" + :summary "SUMMARY"} + + format-string-map + {:text "TEXT" + :xml "XML" + :json "JSON" + :yaml "YAML"} + + ->param-string + (fn [[k v]] + (when-let [param-name (params-string-map k)] + (sqlf/paren-wrap + (sqlf/space-join [param-name (if (true? v) "TRUE" "FALSE")]))))] + + (sqlf/space-join + (remove nil? + (concat ["EXPLAIN" + (when (:analyze params) "ANALYZE") + (when (:verbose params) "VERBOSE") + (when-let [format-type (:format params)] + (sqlf/paren-wrap (str "FORMAT" " " (format-string-map format-type))))] + (map ->param-string params))))))) diff --git a/src/honeysql_postgres/helpers.cljc b/src/honeysql_postgres/helpers.cljc index e4241fc..901f2eb 100644 --- a/src/honeysql_postgres/helpers.cljc +++ b/src/honeysql_postgres/helpers.cljc @@ -83,3 +83,6 @@ (defhelper drop-extension [m extension-name] (assoc m :drop-extension (sqlh/collify extension-name))) + +(defhelper explain [m args] + (assoc m :explain args)) diff --git a/test/honeysql_postgres/postgres_test.cljc b/test/honeysql_postgres/postgres_test.cljc index 87c8ef3..d15f0cd 100644 --- a/test/honeysql_postgres/postgres_test.cljc +++ b/test/honeysql_postgres/postgres_test.cljc @@ -18,6 +18,7 @@ drop-column drop-extension drop-table + explain filter insert-into-as on-conflict @@ -354,6 +355,7 @@ (within-group [(sql/call :percentile_disc (hsql-types/array [0.25 0.5 0.75])) (order-by :s.i) :alias]) (from (sql/raw "generate_series(1,10) AS s(i)")) (sql/format))))) + (deftest create-extension-test (testing "create extension" (is (= ["CREATE EXTENSION \"uuid-ossp\""] @@ -372,3 +374,66 @@ (-> (drop-extension :uuid-ossp) (sql/format :allow-dashed-names? true :quoting :ansi)))))) + +(deftest explain-test + (let [query (-> (select :*) + (from :products))] + (testing "EXPLAIN without any arguments" + (is (= ["EXPLAIN SELECT * FROM products"] + (-> query (explain) (sql/format))))) + + (testing "Explain with analyze" + (is (= ["EXPLAIN ANALYZE SELECT * FROM products"] + (-> query (explain {:analyze true}) (sql/format))))) + + (testing "Explain with analyze verbose" + (is (= ["EXPLAIN ANALYZE VERBOSE SELECT * FROM products"] + (-> query (explain {:analyze true :verbose true}) (sql/format)))) + (is (= ["EXPLAIN ANALYZE VERBOSE SELECT * FROM products"] + (-> query (explain {:verbose true :analyze true}) (sql/format))))) + + (testing "Explain with costs" + (is (= ["EXPLAIN (COSTS TRUE) SELECT * FROM products"] + (-> query (explain {:costs true}) (sql/format)))) + (is (= ["EXPLAIN (COSTS FALSE) SELECT * FROM products"] + (-> query (explain {:costs false}) (sql/format))))) + + (testing "Explain with settings" + (is (= ["EXPLAIN (SETTINGS TRUE) SELECT * FROM products"] + (-> query (explain {:settings true}) (sql/format)))) + (is (= ["EXPLAIN (SETTINGS FALSE) SELECT * FROM products"] + (-> query (explain {:settings false}) (sql/format))))) + + (testing "Explain with buffers" + (is (= ["EXPLAIN (BUFFERS TRUE) SELECT * FROM products"] + (-> query (explain {:buffers true}) (sql/format)))) + (is (= ["EXPLAIN (BUFFERS FALSE) SELECT * FROM products"] + (-> query (explain {:buffers false}) (sql/format))))) + + (testing "Explain with wal" + (is (= ["EXPLAIN (WAL TRUE) SELECT * FROM products"] + (-> query (explain {:wal true}) (sql/format)))) + (is (= ["EXPLAIN (WAL FALSE) SELECT * FROM products"] + (-> query (explain {:wal false}) (sql/format))))) + + (testing "Explain with timing" + (is (= ["EXPLAIN (TIMING TRUE) SELECT * FROM products"] + (-> query (explain {:timing true}) (sql/format)))) + (is (= ["EXPLAIN (TIMING FALSE) SELECT * FROM products"] + (-> query (explain {:timing false}) (sql/format))))) + + (testing "Explain with summary" + (is (= ["EXPLAIN (SUMMARY TRUE) SELECT * FROM products"] + (-> query (explain {:summary true}) (sql/format)))) + (is (= ["EXPLAIN (SUMMARY FALSE) SELECT * FROM products"] + (-> query (explain {:summary false}) (sql/format))))) + + (testing "Explain with format" + (is (= ["EXPLAIN (FORMAT TEXT) SELECT * FROM products"] + (-> query (explain {:format :text}) (sql/format)))) + (is (= ["EXPLAIN (FORMAT XML) SELECT * FROM products"] + (-> query (explain {:format :xml}) (sql/format)))) + (is (= ["EXPLAIN (FORMAT JSON) SELECT * FROM products"] + (-> query (explain {:format :json}) (sql/format)))) + (is (= ["EXPLAIN (FORMAT YAML) SELECT * FROM products"] + (-> query (explain {:format :yaml}) (sql/format))))))) From bb2e245928e64b1c603b81e9fc20603b5298a9b8 Mon Sep 17 00:00:00 2001 From: Govind Krishna Joshi Date: Fri, 20 Aug 2021 13:00:02 +0530 Subject: [PATCH 3/4] Avoid re-creating helpers for every invocation of explain --- src/honeysql_postgres/format.cljc | 55 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/honeysql_postgres/format.cljc b/src/honeysql_postgres/format.cljc index 7c83228..3f11c7a 100644 --- a/src/honeysql_postgres/format.cljc +++ b/src/honeysql_postgres/format.cljc @@ -299,34 +299,33 @@ util/get-first sqlf/to-sql))) +(def ^:private explain-params->string + {:costs "COSTS" + :settings "SETTINGS" + :buffers "BUFFERS" + :wal "WAL" + :timing "TIMING" + :summary "SUMMARY"}) + +(def ^:private explain-format->string + {:text "TEXT" + :xml "XML" + :json "JSON" + :yaml "YAML"}) + +(defn- ->explain-boolean-param-string [param value] + (when-let [param-string (explain-params->string param)] + (sqlf/paren-wrap + (sqlf/space-join [param-string (if (true? value) "TRUE" "FALSE")])))) + (defmethod format-clause :explain [[_ [params]] _] (if (empty? params) "EXPLAIN" - (let [params-string-map - {:costs "COSTS" - :settings "SETTINGS" - :buffers "BUFFERS" - :wal "WAL" - :timing "TIMING" - :summary "SUMMARY"} - - format-string-map - {:text "TEXT" - :xml "XML" - :json "JSON" - :yaml "YAML"} - - ->param-string - (fn [[k v]] - (when-let [param-name (params-string-map k)] - (sqlf/paren-wrap - (sqlf/space-join [param-name (if (true? v) "TRUE" "FALSE")]))))] - - (sqlf/space-join - (remove nil? - (concat ["EXPLAIN" - (when (:analyze params) "ANALYZE") - (when (:verbose params) "VERBOSE") - (when-let [format-type (:format params)] - (sqlf/paren-wrap (str "FORMAT" " " (format-string-map format-type))))] - (map ->param-string params))))))) + (sqlf/space-join + (remove nil? + (concat ["EXPLAIN" + (when (:analyze params) "ANALYZE") + (when (:verbose params) "VERBOSE") + (when-let [format-type (:format params)] + (sqlf/paren-wrap (str "FORMAT" " " (explain-format->string format-type))))] + (map (partial apply ->explain-boolean-param-string) params)))))) From 4b1101ec112b04f74b7d88defb2f3d0b035ae963 Mon Sep 17 00:00:00 2001 From: Govind Krishna Joshi Date: Fri, 20 Aug 2021 13:10:51 +0530 Subject: [PATCH 4/4] Add explain examples to the readme --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index a44a278..8f0cfd3 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,31 @@ The `ilike` and `not-ilike` operators can be used to query data using a pattern 0.25 0.50 0.75] ``` +### explain +`EXPLAIN` is used to show the execution plan of a statement + +``` clojure +(-> (select :*) + (from :products) + (explain) + (sql/format)) +=> ["EXPLAIN SELECT * FROM PRODUCTS"] + +(-> (select :*) + (from :products) + (explain {:analzye true :verbose true}) + (sql/format)) +=> ["EXPLAIN ANALZYE VERBOSE SELECT * FROM PRODUCTS"] + +(-> (select :*) + (from :products) + (explain {:format :json}) + (sql/format)) +=> ["EXPLAIN (FORMAT JSON) SELECT * FROM PRODUCTS"] +``` + + + ### SQL functions The following are the SQL functions added in `honeysql-postgres`