Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Malli types for the public API #111

Merged
merged 5 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading