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` diff --git a/src/honeysql_postgres/format.cljc b/src/honeysql_postgres/format.cljc index ee54ea8..3f11c7a 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,34 @@ (-> extension-name 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" + (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)))))) 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 2f073e1..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\""] @@ -367,8 +369,71 @@ :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 :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)))))))