Skip to content

Commit

Permalink
feat: Add SELECT * EXCEPT capability
Browse files Browse the repository at this point in the history
  • Loading branch information
KingMob committed Apr 8, 2024
1 parent bf4a518 commit a602a0b
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 11 deletions.
8 changes: 6 additions & 2 deletions resources/inferenceql/query/base.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,16 @@ distinct-clause ::= #'(?i)DISTINCT'

select-clause ::= #'(?i)SELECT' (ws distinct-clause)? ws select-list

select-list ::= star
select-list ::= star-clause
/ selection (ws? ',' ws? selection)*
/ aggregation (ws? ',' ws? aggregation)*

star-clause ::= star (ws? except-clause)?

star ::= '*'

except-clause ::= #'(?i)EXCEPT' ws? '(' ws? identifier-list ws? ')'

selection ::= (scalar-expr | aggregation) (ws alias-clause)?

alias-clause ::= #'(?i)AS' ws identifier
Expand Down Expand Up @@ -149,7 +153,6 @@ insert-expr ::= #'(?i)INSERT' ws #'(?i)INTO' ws relation-expr ws relation-expr

relation-value ::= '(' ws? identifier-list ws? ')' ws #'(?i)VALUES' ws value-lists

identifier-list ::= identifier (ws? ',' ws identifier)*
value-list ::= '(' ws? value (ws? ',' ws? value)* ws? ')'
value-lists ::= value-lists-full | value-lists-sparse
value-lists-full ::= value-list (ws? ',' ws? value-list)*
Expand Down Expand Up @@ -241,6 +244,7 @@ bool ::= #'true|false'
float ::= #'-?\d+\.\d+(E-?\d+)?'
int ::= #'-?\d+'
nat ::= #'\d+'
identifier-list ::= identifier (ws? ',' ws? identifier)*
identifier ::= simple-symbol | delimited-symbol
simple-symbol ::= #'(?u)(?!G__)\p{L}[\p{L}\p{N}_\-\?\.]*'
delimited-symbol ::= <'"'> #'[^\"]+' <'"'>
Expand Down
4 changes: 2 additions & 2 deletions src/inferenceql/query/base.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
- parse - a parsing function"
[s db parse]
(let [node-or-failure (parse s)]
(tap> #:base.query{:node node-or-failure})
(cond (insta/failure? node-or-failure)
(throw (error/parse node-or-failure))

(plan/relation-node? node-or-failure)
(let [plan (plan/plan node-or-failure)
env (db/env @db)]
(tap> #:base.query{:node node-or-failure
:plan plan
(tap> #:base.query{:plan plan
:env env})
(plan/eval plan env {}))

Expand Down
2 changes: 1 addition & 1 deletion src/inferenceql/query/parser/tree.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
(string/blank? node))))

(defn children
"Returns `node`'s children."
"Returns `node`'s children. Removes whitespace nodes."
[node]
(into []
(clojure/remove whitespace?)
Expand Down
73 changes: 68 additions & 5 deletions src/inferenceql/query/plan.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,32 @@
[node]
(tree/match [node]
[[:selection child]] (star? child)
[[:star-clause & _]] true
[[:star & _]] true
:else false))

;;; plan

(defn lookup
"A plan to retrieve a relation/table from the environment by name."
[id]
{::type :inferenceql.query.plan.type/lookup
::env/name id})

(defn select
"A plan to filters the tuples/rows that match the pred fn.
NB: Used for the WHERE clause; unrelated to the SELECT clause."
[op sexpr]
{::type :inferenceql.query.plan.type/select
::sexpr sexpr
::plan op})

(defn extended-project
"An (extended) projection plan. Used for simple selections of existing
columns as well as derived columns."
[op coll]
;; `coll` is a sequence of (s-expression, attribute) pairs.
;; `coll` is a sequence of (fn-to-eval-s-expression, attribute-name) pairs.
(let [terms (mapv #(zipmap [::sexpr ::relation/attribute] %)
coll)]
{::type :inferenceql.query.plan.type/extended-project
Expand Down Expand Up @@ -335,16 +342,66 @@
[[:scalar-expr & _]] true
:else false))

(defn ^:private select-star-plan
"Returns a plan for SELECT * and SELECT * EXCEPT ... clauses."
[node op]
(tap> {:star-node node})
(tree/match [node]
[[:star-clause & children]]
(select-star-plan children op) ; trying recur breaks with CLJ-2808

;; plain SELECT *
[[[:star _]]]
op

;; SELECT * EXCEPT (col1, col2, ...)
[[[:star & _] [:except-clause _except _lparen [:identifier-list & id-list] _rparen]]]
(loop [exclusions []
[id & rest-ids] (filterv (tree/tag-pred :identifier) id-list)]
(if id
(recur (conj exclusions (literal/read id)) rest-ids)
{::type :inferenceql.query.plan.type/star-except
::exclusions exclusions
::plan op}))

:else
(throw (ex-info "Can't generate selection plan - unknown * pattern"
{:star-node node
:partial-plan op}))))

(comment

(let [op :op
star-node [:star-clause [:star "*"]]
star-except-node [:star-clause
[:star "*"]
[:ws " "]
[:except-clause
"EXCEPT"
[:ws " "]
"("
[:identifier-list
[:identifier [:simple-symbol "bob"]]
","
[:ws " "]
[:identifier [:delimited-symbol "philh@rmonic"]]]
")"]]]
(select-star-plan star-except-node op))

)

(defn select-plan
[select-node group-by-node op]
(assert (= :select-clause (tree/tag select-node)))
(assert (or (nil? group-by-node) (= :group-by-clause (tree/tag group-by-node))))
(let [selections (selections select-node)
(let [selections (vec (selections select-node))
_ (tap> {:select-node select-node})
_ (tap> {:selections selections})
distinct-clause (tree/get-node select-node :distinct-clause)
plan (cond (and (= 1 (count selections))
(star? (first selections)))
op
plan (cond (star? (first selections))
(select-star-plan (first selections) op)

;; FIXME?: Can't currently combine GROUP BY and SELECT *, which some dbs allow
(or group-by-node (some aggregation? selections))
(aggregation-plan select-node group-by-node op)

Expand Down Expand Up @@ -505,6 +562,12 @@
pred #(scalar/eval sexpr env bindings %)]
(relation/select rel pred)))

(defmethod eval :inferenceql.query.plan.type/star-except
[plan env bindings]
(let [{::keys [exclusions plan]} plan
rel (eval plan env bindings)]
(relation/remove-attributes rel exclusions)))

(defmethod eval :inferenceql.query.plan.type/extended-project
[plan env bindings]
(let [{::keys [terms plan]} plan
Expand Down
9 changes: 9 additions & 0 deletions src/inferenceql/query/relation.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
:attrs (mapv second coll))))

(defn select
"Selects/filters the tuples/rows that match the pred fn"
[rel pred]
(with-meta (filter pred (tuples rel))
(meta rel)))
Expand Down Expand Up @@ -112,6 +113,14 @@
attr))]
(relation rel :attrs attributes)))

(defn remove-attributes
"Remove attributes from a relation."
[rel attrs]
(let [attributes' (remove (set attrs)
(attributes rel))]
(-> (map #(apply dissoc % attrs) rel)
(relation :attrs attributes'))))

(defn group-by
[rel f]
(->> (tuples rel)
Expand Down
9 changes: 8 additions & 1 deletion src/inferenceql/query/tuple.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@
(defn select-attrs
"Retrives tuple `tup` with only the attributes in `attrs`."
[tup attrs]
(let [names (into #{} (map name) attrs)]
(let [names (into #{} (map name) attrs)] ; FIXME: should this be clojure.core/name
(medley/filter-keys (comp names name)
tup)))
;;
;;(defn remove-attrs
;; "Removes attributes in `attrs` from tuple `tup`."
;; [tup attrs]
;; (let [names (into #{} (map name) attrs)]
;; (medley/filter-keys (complement (comp names name))
;; tup)))

(defn ->map
"Converts tuple `tup` to an immutable hash map."
Expand Down

0 comments on commit a602a0b

Please sign in to comment.