Skip to content

Commit

Permalink
Malli types for the public API (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
crisptrutski authored Oct 26, 2024
1 parent 19628f5 commit af03556
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 13 deletions.
24 changes: 15 additions & 9 deletions src/macaw/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
[clojure.string :as str]
[clojure.walk :as walk]
[macaw.collect :as collect]
[macaw.rewrite :as rewrite])
[macaw.rewrite :as rewrite]
[macaw.types :as m.types]
[macaw.util.malli :as mu])
(:import
(com.metabase.macaw AstWalker$Scope BasicTableExtractor AnalysisError CompoundTableExtractor)
(com.metabase.macaw AnalysisError AstWalker$Scope BasicTableExtractor CompoundTableExtractor)
(java.util.function Consumer)
(net.sf.jsqlparser JSQLParserException)
(net.sf.jsqlparser.parser CCJSqlParser CCJSqlParserUtil)
(net.sf.jsqlparser.parser.feature Feature)
(net.sf.jsqlparser.schema Table)))
(net.sf.jsqlparser.schema Table)
(net.sf.jsqlparser.statement Statement)))

(set! *warn-on-reflection* true)

Expand Down Expand Up @@ -74,12 +77,13 @@
str/lower-case
(str/replace #"_" "-")))})

(defn query->components
(mu/defn query->components :- [:or m.types/error-result m.types/components-result]
"Given a parsed query (i.e., a [subclass of] `Statement`) return a map with the elements found within it.
(Specifically, it returns their fully-qualified names as strings, where 'fully-qualified' means 'as referred to in
the query'; this function doesn't do additional inference work to find out a table's schema.)"
[parsed & {:as opts}]
[parsed :- [:or m.types/error-result [:fn #(instance? Statement %)]]
& {:as opts} :- [:maybe m.types/options-map]]
;; By default, we will preserve identifiers verbatim, to be agnostic of casing and quoting.
;; This may result in duplicate components, which are left to the caller to deduplicate.
;; In Metabase's case, this is done during the stage where the database metadata is queried.
Expand Down Expand Up @@ -108,9 +112,9 @@
(defn- tables->identifiers [expr]
{:tables (set (map table->identifier expr))})

(defn query->tables
(mu/defn query->tables :- [:or m.types/error-result m.types/tables-result]
"Given a parsed query (i.e., a [subclass of] `Statement`) return a set of all the table identifiers found within it."
[sql & {:keys [mode] :as opts}]
[sql :- :string & {:keys [mode] :as opts} :- [:maybe m.types/options-map]]
(try
(let [parsed (parsed-query sql opts)]
(if (map? parsed)
Expand All @@ -122,7 +126,7 @@
(catch AnalysisError e
(->macaw-error e))))

(defn replace-names
(mu/defn replace-names :- :string
"Given an SQL query, apply the given table, column, and schema renames.
Supported options:
Expand All @@ -133,7 +137,9 @@
- :agnostic - case is ignored when comparing identifiers in code to replacement \"from\" strings.
- quotes-preserve-case: whether quoted identifiers should override the previous option."
[sql renames & {:as opts}]
[sql :- :string
renames :- :map
& {:as opts} :- [:maybe m.types/options-map]]
;; We need to pre-sanitize the SQL before its analyzed so that the AST token positions match up correctly.
;; Currently, we use a more complex and expensive sanitization method, so that it's reversible.
;; If we decide that it's OK to normalize whitespace etc. during replacement, then we can use the same helper.
Expand Down
64 changes: 64 additions & 0 deletions src/macaw/types.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
(ns macaw.types)

(def modes
"The different analyzer strategies that Macaw supports."
[:ast-walker-1
:basic-select
:compound-select])

(def options-map
"The shape of the options accepted by our API"
[:map
[:mode {:optional true} (into [:enum] modes)]
[:non-reserved-words {:optional true} [:seqable :keyword]]
[:allow-unused? {:optional true} :boolean]
[:case-insensitive {:optional true} [:enum :upper :lower :agnostic]]
[:quotes-preserve-case? {:optional true} :boolean]])

(def error-types
"The different types of errors that Macaw can return."
[:macaw.error/analysis-error
:macaw.error/illegal-expression
:macaw.error/invalid-query
:macaw.error/unable-to-parse
:macaw.error/unsupported-expression])

(def error-result
"A map indicating that we were not able to parse the query."
[:map
[:error (into [:enum] error-types)]])

(def ^:private table-ident
[:map
[:schema {:optional true} :string]
[:table :string]])

(def ^:private column-ident
[:map
[:schema {:optional true} :string]
[:table {:optional true} :string]
[:column :string]])

(defn- with-context [t]
[:map
[:component t]
[:context :any]])

(def components-result
"A map holding all the components that we were able to parse from a query"
[:map {:closed true}
[:tables [:set (with-context table-ident)]]
[:columns [:set (with-context column-ident)]]
[:source-columns [:set column-ident]]
;; TODO Unclear why we would want to wrap any of these.
[:table-wildcards [:set (with-context table-ident)]]
;; This :maybe would be a problem, if anything actually used this value.
[:tables-superset [:set (with-context [:maybe table-ident])]]
;; Unclear why we need a collection here
[:has-wildcard? [:set (with-context :boolean)]]
[:mutation-commands [:set (with-context :string)]]])

(def tables-result
"A map holding the tables that we were able to parse from a query"
[:map
[:tables [:set table-ident]]])
7 changes: 3 additions & 4 deletions test/macaw/acceptance_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
[clojure.string :as str]
[clojure.test :refer :all]
[macaw.core :as m]
[macaw.core-test :as ct])
[macaw.core-test :as ct]
[macaw.types])
(:import
(java.io File)))

Expand All @@ -27,9 +28,7 @@
(ct/raw-components (get cs k))))

(def ^:private test-modes
#{:ast-walker-1
:basic-select
:compound-select})
(set macaw.types/modes))

(def override-hierarchy
(-> (make-hierarchy)
Expand Down

0 comments on commit af03556

Please sign in to comment.