Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
M
Metabase
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Engineering Digital Service
Metabase
Commits
623c74e8
Unverified
Commit
623c74e8
authored
6 years ago
by
Cam Saul
Browse files
Options
Downloads
Patches
Plain Diff
BigQuery nested query support [ci drivers]
parent
255e3f50
Branches
Branches containing commit
Tags
Tags containing commit
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/metabase/driver/bigquery.clj
+28
-31
28 additions, 31 deletions
src/metabase/driver/bigquery.clj
src/metabase/driver/generic_sql/query_processor.clj
+54
-31
54 additions, 31 deletions
src/metabase/driver/generic_sql/query_processor.clj
with
82 additions
and
62 deletions
src/metabase/driver/bigquery.clj
+
28
−
31
View file @
623c74e8
...
...
@@ -341,7 +341,7 @@
{
:pre
[(
map?
honeysql-form
)]}
(
let
[[
sql
&
args
]
(
sql/honeysql-form->sql+args
bq-driver
honeysql-form
)]
(
when
(
seq
args
)
(
throw
(
Exception.
(
str
(
tru
"BigQuery statements can't be parameterized!"
)))))
(
throw
(
Exception.
(
str
(
tru
"BigQuery statements can'
'
t be parameterized!"
)))))
sql
))
;; From the dox: Fields must contain only letters, numbers, and underscores, start with a letter or underscore, and be
...
...
@@ -405,15 +405,6 @@
(
isa?
special-type
:type/UNIXTimestampMilliseconds
)
(
unix-timestamp->timestamp
field
:milliseconds
)
:else
field
)))
(
defn-
ag-ref->alias
[[
_
index
]]
(
let
[{{
aggregations
:aggregation
}
:query
}
sqlqp/*query*
[
ag-type
:as
ag
]
(
nth
aggregations
index
)]
(
mbql.u/match-one
ag
[
:distinct
_
]
"count"
[
:expression
operator
&
_
]
operator
[
:named
_
ag-name
]
ag-name
[
ag-type
&
_
]
ag-type
)))
(
defn-
field->identifier
"Generate appropriate identifier for a Field for SQL parameters. (NOTE: THIS IS ONLY USED FOR SQL PARAMETERS!)"
;; TODO - Making a DB call for each field to fetch its Table is inefficient and makes me cry, but this method is
...
...
@@ -425,27 +416,18 @@
details
(
:details
(
qp.store/database
))]
(
map->BigQueryIdentifier
{
:dataset-name
(
:dataset-id
details
)
,
:table-name
table-name,
:field-name
(
:name
field
)})))
(
defn-
field-clause->field
[
field-clause
]
(
when
field-clause
(
let
[
id-or-name
(
mbql.u/field-clause->id-or-literal
field-clause
)]
(
when
(
integer?
id-or-name
)
(
qp.store/field
id-or-name
)))))
(
defn-
field->breakout-identifier
[
driver
field-clause
]
(
let
[
alias
(
if
(
mbql.u/is-clause?
:aggregation
field-clause
)
(
ag-ref->alias
field-clause
)
(
sql/field->alias
driver
(
field-clause->field
field-clause
)))]
(
hsql/raw
(
str
\`
alias
\`
))))
(
defn-
apply-breakout
[
driver
honeysql-form
{
breakout-field-clauses
:breakout,
fields-field-clauses
:fields
}]
(
->
honeysql-form
;; Group by all the breakout fields
((
partial
apply
h/group
)
(
map
#
(
field->breakout-identifier
driver
%
)
breakout-field-clauses
))
;; Group by all the breakout fields.
;;
;; Unlike other SQL drivers, BigQuery requires that we refer to Fields using the alias we gave them in the
;; `SELECT` clause, rather than repeating their definitions.
((
partial
apply
h/group
)
(
map
(
partial
sqlqp/field-clause->alias
driver
)
breakout-field-clauses
))
;; Add fields form only for fields that weren't specified in :fields clause -- we don't want to include it
;; twice, or HoneySQL will barf
((
partial
apply
h/merge-select
)
(
for
[
field-clause
breakout-field-clauses
:when
(
not
(
contains?
(
set
fields-field-clauses
)
field-clause
))]
(
sqlqp/as
driver
(
sqlqp/->honeysql
driver
field-clause
)
field-clause
)))))
(
sqlqp/as
driver
field-clause
)))))
(
defn
apply-source-table
"Copy of the Generic SQL implementation of `apply-source-table` that prepends the current dataset ID to the table
...
...
@@ -474,14 +456,26 @@
(
recur
honeysql-form
more
)
honeysql-form
)))))
(
defn-
ag-ref->alias
[[
_
index
]]
(
let
[{{
aggregations
:aggregation
}
:query
}
sqlqp/*query*
[
ag-type
:as
ag
]
(
nth
aggregations
index
)]
(
mbql.u/match-one
ag
[
:distinct
_
]
:count
[
:expression
operator
&
_
]
operator
[
:named
_
ag-name
]
(
keyword
ag-name
)
[
ag-type
&
_
]
ag-type
)))
(
defn-
apply-order-by
[
driver
honeysql-form
{
subclauses
:order-by,
:as
query
}]
(
loop
[
honeysql-form
honeysql-form,
[[
direction
field-clause
]
&
more
]
subclauses
]
(
let
[
honeysql-form
(
h/merge-order-by
honeysql-form
[(
field->breakout-identifier
driver
field-clause
)
(
let
[
honeysql-form
(
h/merge-order-by
honeysql-form
[(
if
(
mbql.u/is-clause?
:aggregation
field-clause
)
(
ag-ref->alias
field-clause
)
(
sqlqp/field-clause->alias
driver
field-clause
))
direction
])]
(
if
(
seq
more
)
(
recur
honeysql-form
more
)
honeysql-form
))))
;;; +----------------------------------------------------------------------------------------------------------------+
;;; | Other Driver / SQLDriver Method Implementations |
;;; +----------------------------------------------------------------------------------------------------------------+
...
...
@@ -499,19 +493,21 @@
* Runs our customs `honeysql-form->sql` method
* Incldues `table-name` in the resulting map (do not remember why we are doing so, perhaps it is needed to run the
query)"
[{
database-id
:database
{
source-table-id
:source-table
}
:query
:as
outer-query
}]
[{
database-id
:database
{
source-table-id
:source-table
,
source-query
:source-query
}
:query
:as
outer-query
}]
{
:pre
[(
integer?
database-id
)]}
(
let
[
dataset-id
(
->
(
qp.store/database
)
:details
:dataset-id
)
aliased-query
(
pre-alias-aggregations
outer-query
)
{
table-name
:name
}
(
qp.store/table
source-
table
-id
)]
{
table-name
:name
}
(
some->
source-table-id
qp.store/
table
)]
(
assert
(
seq
dataset-id
))
(
binding
[
sqlqp/*query*
(
assoc
aliased-query
:dataset-id
dataset-id
)]
{
:query
(
->>
aliased-query
(
sqlqp/build-honeysql-form
bq-driver
)
honeysql-form->sql
)
:table-name
table-name
:table-name
(
or
table-name
(
when
source-query
sqlqp/source-query-alias
))
:mbql?
true
})))
(
defn-
effective-query-timezone
[
database
]
...
...
@@ -593,6 +589,7 @@
:native-parameters
:expression-aggregations
:binning
:nested-queries
:native-query-params
}
(
when-not
config/is-test?
;; during unit tests don't treat bigquery as having FK
...
...
This diff is collapsed.
Click to expand it.
src/metabase/driver/generic_sql/query_processor.clj
+
54
−
31
View file @
623c74e8
...
...
@@ -40,34 +40,6 @@
Each nested query increments this counter by 1."
0
)
;;; +----------------------------------------------------------------------------------------------------------------+
;;; | Other Formatting |
;;; +----------------------------------------------------------------------------------------------------------------+
(
s/defn
^
:private
qualified-alias
"Convert the given `FIELD` to a stringified alias, for use in a SQL `AS` clause."
[
driver,
field
:-
(
class
Field
)]
(
some->>
field
(
sql/field->alias
driver
)
hx/qualify-and-escape-dots
))
(
defn
as
"Generate a FORM `AS` FIELD alias using the name information of FIELD."
[
driver
form
field-clause
]
(
let
[
expression-name
(
when
(
mbql.u/is-clause?
:expression
field-clause
)
(
second
field-clause
))
field
(
when-not
expression-name
(
let
[
id-or-name
(
mbql.u/field-clause->id-or-literal
field-clause
)]
(
when
(
integer?
id-or-name
)
(
qp.store/field
id-or-name
))))]
(
if-let
[
alias
(
cond
expression-name
expression-name
field
(
qualified-alias
driver
field
))]
[
form
alias
]
form
)))
;;; +----------------------------------------------------------------------------------------------------------------+
;;; | ->honeysql multimethod def & low-level method impls |
;;; +----------------------------------------------------------------------------------------------------------------+
...
...
@@ -213,6 +185,52 @@
(
driver/date-interval
driver
unit
amount
))))
;;; +----------------------------------------------------------------------------------------------------------------+
;;; | Field Aliases (AS Forms) |
;;; +----------------------------------------------------------------------------------------------------------------+
(
s/defn
^
:private
qualified-alias
:-
s/Keyword
"Convert the given `field` to a stringified alias, for use in a SQL `AS` clause."
[
driver,
field
:-
(
class
Field
)]
(
some->>
field
(
sql/field->alias
driver
)
hx/qualify-and-escape-dots
))
(
s/defn
field-clause->alias
:-
s/Keyword
"Generate an approriate alias (e.g., for use with SQL `AN`) for a Field clause of any type."
[
driver,
field-clause
:-
mbql.s/Field
]
(
let
[
expression-name
(
when
(
mbql.u/is-clause?
:expression
field-clause
)
(
second
field-clause
))
id-or-name
(
when-not
expression-name
(
mbql.u/field-clause->id-or-literal
field-clause
))
field
(
when
(
integer?
id-or-name
)
(
qp.store/field
id-or-name
))]
(
cond
expression-name
(
keyword
(
hx/escape-dots
expression-name
))
field
(
qualified-alias
driver
field
)
(
string?
id-or-name
)
(
keyword
(
hx/escape-dots
id-or-name
)))))
(
defn
as
"Generate HoneySQL for an `AS` form (e.g. `<form> AS <field>`) using the name information of a `field-clause`. The
HoneySQL representation of on `AS` clause is a tuple like `[<form> <alias>]`.
In some cases where the alias would be redundant, such as unwrapped field literals, this returns the form as-is.
(as [:field-literal \"x\" :type/Text])
;; -> <compiled-form>
;; -> SELECT \"x\"
(as [:datetime-field [:field-literal \"x\" :type/Text] :month])
;; -> [<compiled-form> :x]
;; -> SELECT date_extract(\"x\", 'month') AS \"x\""
([
driver
field-clause
]
(
as
driver
(
->honeysql
driver
field-clause
)
field-clause
))
([
driver
form
field-clause
]
(
if
(
mbql.u/is-clause?
:field-literal
field-clause
)
form
[
form
(
field-clause->alias
driver
field-clause
)])))
;;; +----------------------------------------------------------------------------------------------------------------+
;;; | Clause Handlers |
;;; +----------------------------------------------------------------------------------------------------------------+
...
...
@@ -239,14 +257,14 @@
(
as->
honeysql-form
new-hsql
(
apply
h/merge-select
new-hsql
(
for
[
field-clause
breakout-fields
:when
(
not
(
contains?
(
set
fields-fields
)
field-clause
))]
(
as
driver
(
->honeysql
driver
field-clause
)
field-clause
)))
(
as
driver
field-clause
)))
(
apply
h/group
new-hsql
(
map
(
partial
->honeysql
driver
)
breakout-fields
))))
(
defn
apply-fields
"Apply a `fields` clause to HONEYSQL-FORM. Default implementation of `apply-fields` for SQL drivers."
[
driver
honeysql-form
{
fields
:fields
}]
(
apply
h/merge-select
honeysql-form
(
for
[
field
fields
]
(
as
driver
(
->honeysql
driver
field
)
field
))))
(
as
driver
field
))))
;;; ----------------------------------------------------- filter -----------------------------------------------------
...
...
@@ -425,7 +443,12 @@
;; TODO - it seems to me like we could actually properly handle nested nested queries by giving each level of nesting
;; a different alias
(
def
^
:private
source-query-alias
:source
)
(
def
source-query-alias
"Alias to use for source queries, e.g.:
SELECT source.*
FROM ( SELECT * FROM some_table ) source"
:source
)
(
defn-
apply-source-query
"Handle a `:source-query` clause by adding a recursive `SELECT` or native query. At the time of this writing, all
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment