diff --git a/README.md b/README.md index 0bc72c1..92cda72 100644 --- a/README.md +++ b/README.md @@ -23,24 +23,26 @@ Currently honeysql-postgres supports the following postgres specific clauses: - rename column - insert-into-as - pattern matching (ILIKE and NOT ILIKE) +- except (and except-all) ## Index -- [Usage](https://github.com/nilenso/honeysql-postgres#usage) - - [Leiningen](https://github.com/nilenso/honeysql-postgres#leiningen) - - [Maven](https://github.com/nilenso/honeysql-postgres#maven) - - [repl](https://github.com/nilenso/honeysql-postgres#repl) - - [Breaking Change](https://github.com/nilenso/honeysql-postgres#breaking-change) - - [upsert](https://github.com/nilenso/honeysql-postgres#upsert) - - [insert into with alias](https://github.com/nilenso/honeysql-postgres#insert-into-with-alias) - - [over](https://github.com/nilenso/honeysql-postgres#over) - - [create view](https://github.com/nilenso/honeysql-postgres#create-view) - - [create table](https://github.com/nilenso/honeysql-postgres#create-table) - - [drop table](https://github.com/nilenso/honeysql-postgres#drop-table) - - [alter table](https://github.com/nilenso/honeysql-postgres#alter-table) - - [pattern matching](https://github.com/nilenso/honeysql-postgres#pattern-matching) - - [SQL functions](https://github.com/nilenso/honeysql-postgres#sql-functions) -- [License](https://github.com/nilenso/honeysql-postgres#license) +- [Usage](#usage) + - [Leiningen](#leiningen) + - [Maven](#maven) + - [repl](#repl) + - [Breaking Change](#breaking-change) + - [upsert](#upsert) + - [insert into with alias](#insert-into-with-alias) + - [over](#over) + - [create view](#create-view) + - [create table](#create-table) + - [drop table](#drop-table) + - [alter table](#alter-table) + - [pattern matching](#pattern-matching) + - [except](#except) + - [SQL functions](#sql-functions) +- [License](#license) ## Usage @@ -181,6 +183,17 @@ The `ilike` and `not-ilike` operators can be used to query data using a pattern sql/format) => ["SELECT * FROM products WHERE name NOT ILIKE ?" "%name%"] ``` +### except + +```clj + +(sql/format + {:except + [{:select [:ip]} + {:select [:ip] :from [:ip_location]}]}) +=> ["SELECT ip EXCEPT SELECT ip FROM ip_location"] +``` +`except-all` works the same way as `except`. ### SQL functions The following are the SQL functions added in `honeysql-postgres` diff --git a/project.clj b/project.clj index e96ce32..0a5e4e2 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject nilenso/honeysql-postgres "0.2.5" +(defproject nilenso/honeysql-postgres "0.2.6" :description "PostgreSQL extension for honeysql" :url "https://github.com/nilenso/honeysql-postgres" :license {:name "Eclipse Public License" diff --git a/src/honeysql_postgres/format.cljc b/src/honeysql_postgres/format.cljc index 3a278fb..5949bac 100644 --- a/src/honeysql_postgres/format.cljc +++ b/src/honeysql_postgres/format.cljc @@ -1,7 +1,8 @@ (ns ^{:doc "Extension of the honeysql format functions specifically for postgreSQL"} honeysql-postgres.format (:require [honeysql.format :as sqlf :refer [fn-handler format-clause]] ;; multi-methods - [honeysql-postgres.util :as util])) + [honeysql-postgres.util :as util] + [clojure.string :as string])) (def ^:private custom-additions {:create-table 10 @@ -26,6 +27,8 @@ "Determines the order that clauses will be placed within generated SQL" (merge {:with 30 :with-recursive 40 + :except 45 + :except-all 45 :select 50 :insert-into 60 :update 70 @@ -208,4 +211,12 @@ (defmethod format-clause :insert-into-as [[_ [table-name table-alias]] _] (str "INSERT INTO " (sqlf/to-sql table-name) " AS " (sqlf/to-sql table-alias))) +(defmethod format-clause :except [[_ maps] _] + (binding [sqlf/*subquery?* false] + (string/join " EXCEPT " (map sqlf/to-sql maps)))) + +(defmethod format-clause :except-all [[_ maps] _] + (binding [sqlf/*subquery?* false] + (string/join " EXCEPT ALL " (map sqlf/to-sql maps)))) + (override-default-clause-priority) diff --git a/test/honeysql_postgres/postgres_test.cljc b/test/honeysql_postgres/postgres_test.cljc index 321a79b..7f201d3 100644 --- a/test/honeysql_postgres/postgres_test.cljc +++ b/test/honeysql_postgres/postgres_test.cljc @@ -219,3 +219,38 @@ (from :products) (where [:not-ilike :name "%name%"]) sql/format))))) + +(deftest values-except-select + (testing "select which values are not not present in a table" + (is (= ["VALUES (?), (?), (?) EXCEPT SELECT id FROM images" 4 5 6] + (sql/format + {:except + [{:values [[4] [5] [6]]} + {:select [:id] :from [:images]}]}))))) + + +(deftest select-except-select + (testing "select which rows are not present in another table" + (is (= ["SELECT ip EXCEPT SELECT ip FROM ip_location"] + (sql/format + {:except + [{:select [:ip]} + {:select [:ip] :from [:ip_location]}]}))))) + + +(deftest values-except-all-select + (testing "select which values are not not present in a table" + (is (= ["VALUES (?), (?), (?) EXCEPT ALL SELECT id FROM images" 4 5 6] + (sql/format + {:except-all + [{:values [[4] [5] [6]]} + {:select [:id] :from [:images]}]}))))) + + +(deftest select-except-all-select + (testing "select which rows are not present in another table" + (is (= ["SELECT ip EXCEPT ALL SELECT ip FROM ip_location"] + (sql/format + {:except-all + [{:select [:ip]} + {:select [:ip] :from [:ip_location]}]})))))