Safe Haskell | None |
---|---|
Language | Haskell2010 |
Translate from the DML to the BigQuery dialect.
Synopsis
- data Error
- = FromTypeUnsupported (SelectFromG 'BigQuery Expression)
- | NoOrderSpecifiedInOrderBy
- | MalformedAgg
- | FieldTypeUnsupportedForNow (AnnFieldG 'BigQuery Void Expression)
- | AggTypeUnsupportedForNow (TableAggregateFieldG 'BigQuery Void Expression)
- | NodesUnsupportedForNow (TableAggregateFieldG 'BigQuery Void Expression)
- | NoProjectionFields
- | NoAggregatesMustBeABug
- | UnsupportedArraySelect (ArraySelectG 'BigQuery Void Expression)
- | UnsupportedOpExpG (OpExpG 'BigQuery Expression)
- | UnsupportedSQLExp Expression
- | UnsupportedDistinctOn
- | UnexpectedEmptyList
- | InvalidIntegerishSql Expression
- | ConnectionsNotSupported
- | ActionsNotSupported
- | ComputedFieldsBooleanExpressionNotSupported
- | ComputedFieldsOrderByNotSupported
- | ScalarComputedFieldsNotSupported
- | NoParentEntityInternalError
- newtype FromIr a = FromIr {
- unFromIr :: ReaderT FromIrReader (StateT FromIrState (Validate (NonEmpty Error))) a
- data FromIrState = FromIrState {}
- data FromIrReader = FromIrReader {}
- data FromIrConfig = FromIrConfig {}
- defaultFromIrConfig :: FromIrConfig
- data ParentSelectFromEntity
- runFromIr :: FromIrConfig -> FromIr a -> Validate (NonEmpty Error) a
- bigQuerySourceConfigToFromIrConfig :: BigQuerySourceConfig -> FromIrConfig
- mkSQLSelect :: JsonAggSelect -> AnnSelectG 'BigQuery (AnnFieldG 'BigQuery Void) Expression -> FromIr Select
- fromRootField :: QueryDB 'BigQuery Void Expression -> FromIr Select
- fromUnnestedJSON :: Expression -> [(ColumnName, ScalarType)] -> [FieldName] -> FromIr From
- fromSelectRows :: ParentSelectFromEntity -> AnnSelectG 'BigQuery (AnnFieldG 'BigQuery Void) Expression -> FromIr PartitionableSelect
- simulateDistinctOn :: Select -> NonEmpty ColumnName -> Maybe (NonEmpty OrderBy) -> FromIr PartitionableSelect
- fromSelectAggregate :: Maybe (EntityAlias, HashMap ColumnName ColumnName) -> AnnSelectG 'BigQuery (TableAggregateFieldG 'BigQuery Void) Expression -> FromIr Select
- data Args = Args {
- argsWhere :: Where
- argsOrderBy :: Maybe (NonEmpty OrderBy)
- argsJoins :: [Join]
- argsTop :: Top
- argsOffset :: Maybe Int64
- argsDistinct :: Maybe (NonEmpty ColumnName)
- argsExistingJoins :: Map TableName EntityAlias
- data UnfurledJoin = UnfurledJoin {}
- fromSelectArgsG :: SelectArgsG 'BigQuery Expression -> ReaderT EntityAlias FromIr Args
- fromAnnotatedOrderByItemG :: AnnotatedOrderByItemG 'BigQuery Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) OrderBy
- unfurlAnnotatedOrderByElement :: AnnotatedOrderByElement 'BigQuery Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) FieldName
- tableNameText :: TableName -> Text
- fromQualifiedTable :: TableName -> FromIr From
- fromFunction :: ParentSelectFromEntity -> FunctionName -> [ArgumentExp Expression] -> HashMap Text (ArgumentExp Expression) -> FromIr From
- fromAnnBoolExp :: GBoolExp 'BigQuery (AnnBoolExpFld 'BigQuery Expression) -> ReaderT EntityAlias FromIr Expression
- fromAnnBoolExpFld :: AnnBoolExpFld 'BigQuery Expression -> ReaderT EntityAlias FromIr Expression
- fromColumnInfo :: ColumnInfo 'BigQuery -> ReaderT EntityAlias FromIr FieldName
- fromGExists :: GExists 'BigQuery Expression -> ReaderT EntityAlias FromIr Select
- data FieldSource
- fromTableAggregateFieldG :: Args -> Top -> (FieldName, TableAggregateFieldG 'BigQuery Void Expression) -> ReaderT EntityAlias FromIr FieldSource
- fromAggregateField :: AggregateField 'BigQuery -> ReaderT EntityAlias FromIr Aggregate
- fromAnnFieldsG :: Map TableName EntityAlias -> (FieldName, AnnFieldG 'BigQuery Void Expression) -> ReaderT EntityAlias FromIr FieldSource
- fromAnnColumnField :: AnnColumnField 'BigQuery Expression -> ReaderT EntityAlias FromIr Expression
- fromColumn :: ColumnName -> ReaderT EntityAlias FromIr FieldName
- fieldSourceProjections :: Bool -> FieldSource -> FromIr (NonEmpty Projection)
- fieldSourceJoins :: FieldSource -> Maybe [Join]
- fromObjectRelationSelectG :: Map TableName EntityAlias -> ObjectRelationSelectG 'BigQuery Void Expression -> ReaderT EntityAlias FromIr Join
- _lookupTableFrom :: Map TableName EntityAlias -> TableName -> FromIr (Either EntityAlias From)
- fromArraySelectG :: ArraySelectG 'BigQuery Void Expression -> ReaderT EntityAlias FromIr Join
- fromComputedFieldSelect :: ComputedFieldSelect 'BigQuery Void Expression -> ReaderT EntityAlias FromIr Expression
- fromArrayAggregateSelectG :: AnnRelationSelectG 'BigQuery (AnnAggregateSelectG 'BigQuery Void Expression) -> ReaderT EntityAlias FromIr Join
- fromArrayRelationSelectG :: ArrayRelationSelectG 'BigQuery Void Expression -> ReaderT EntityAlias FromIr Join
- aliasToFieldProjection :: EntityAlias -> Projection -> Projection
- fromRelName :: RelName -> FromIr Text
- fromMapping :: From -> HashMap ColumnName ColumnName -> ReaderT EntityAlias FromIr [Expression]
- fromMappingFieldNames :: EntityAlias -> HashMap ColumnName ColumnName -> ReaderT EntityAlias FromIr [(FieldName, FieldName)]
- fromOpExpG :: Expression -> OpExpG 'BigQuery Expression -> FromIr Expression
- fromBackendSpecificOpExpG :: Expression -> BooleanOperators Expression -> Expression
- nullableBoolEquality :: Expression -> Expression -> Expression
- nullableBoolInequality :: Expression -> Expression -> Expression
- fromGBoolExp :: GBoolExp 'BigQuery Expression -> ReaderT EntityAlias FromIr Expression
- toNonEmpty :: MonadValidate (NonEmpty Error) m => [x] -> m (NonEmpty x)
- prepareJoinFieldProjection :: (FieldName, FieldName) -> Projection
- selectProjectionsFromFieldSources :: Bool -> [FieldSource] -> FromIr (NonEmpty Projection)
- trueExpression :: Expression
- aggFieldName :: Text
- existsFieldName :: Text
- data NameTemplate
- = ArrayRelationTemplate Text
- | ArrayAggregateTemplate Text
- | ObjectRelationTemplate Text
- | TableTemplate Text
- | ForOrderAlias Text
- | IndexTemplate
- | UnnestTemplate
- | FunctionTemplate FunctionName
- generateEntityAlias :: NameTemplate -> FromIr EntityAlias
- fromAlias :: From -> EntityAlias
- fieldTextNames :: AnnFieldsG 'BigQuery Void Expression -> [Text]
- unEntityAlias :: EntityAlias -> Text
- getGlobalTop :: FromIr Top
Documentation
Most of these errors should be checked for legitimacy.
The base monad used throughout this module for all conversion functions.
It's a Validate, so it'll continue going when it encounters errors to accumulate as many as possible.
It also contains a mapping from entity prefixes to counters. So if my prefix is "table" then there'll be a counter that lets me generate table1, table2, etc. Same for any other prefix needed (e.g. names for joins).
A ReaderT is used around this in most of the module too, for
setting the current entity that a given field name refers to. See
fromColumn
.
FromIr | |
|
data FromIrConfig Source #
Config values for the from-IR translator.
FromIrConfig | |
|
defaultFromIrConfig :: FromIrConfig Source #
A default config.
data ParentSelectFromEntity Source #
Alias of parent SELECT FROM. Functions underlying computed fields requires column values from the table that is being used in FROM clause of parent SELECT.
Example SQL:
SELECT `t_author1`.`id` AS `id`, `t_author1`.`name` AS `name`, ARRAY( SELECT AS STRUCT `id`, `title`, `content` FROM UNNEST( ARRAY( SELECT AS STRUCT * FROM `hasura_test`.`fetch_articles`(`id` => `t_author1`.`id`) ) ) LIMIT 1000 ) AS `articles` FROM `hasura_test`.`author` AS `t_author1`
Where t_author1
is the @ParentSelectFromIdentity
NoParentEntity | There's no parent entity |
ParentEntityAlias EntityAlias | Alias of the parent SELECT FROM |
mkSQLSelect :: JsonAggSelect -> AnnSelectG 'BigQuery (AnnFieldG 'BigQuery Void) Expression -> FromIr Select Source #
Here is where we apply a top-level annotation to the select to indicate to the data loader that this select ought to produce a single object or an array.
fromRootField :: QueryDB 'BigQuery Void Expression -> FromIr Select Source #
Convert from the IR database query into a select.
fromUnnestedJSON :: Expression -> [(ColumnName, ScalarType)] -> [FieldName] -> FromIr From Source #
fromSelectRows :: ParentSelectFromEntity -> AnnSelectG 'BigQuery (AnnFieldG 'BigQuery Void) Expression -> FromIr PartitionableSelect Source #
simulateDistinctOn :: Select -> NonEmpty ColumnName -> Maybe (NonEmpty OrderBy) -> FromIr PartitionableSelect Source #
Simulates DISTINCT ON for BigQuery using ROW_NUMBER() partitioned over distinct fields
Example:
For a GraphQL query:
hasura_test_article(distinct_on: author_id, order_by: [{author_id: asc}, {created_at: asc}]) {
id
title
}
it should produce from a query without a distinct_on
clause:
SELECT id
, title
FROM hasura_test
.article
ORDER BY author_id
ASC, created_at
ASC
a query of the following form:
SELECT id
, title
FROM (SELECT *,
ROW_NUMBER() OVER (PARTITION BY author_id
ORDER BY created_at
ASC) as idx1
FROM hasura_test
.article
) as t_article1
WHERE (t_article1
.idx1
= 1)
ORDER BY t_article1
.author_id
ASC
Note: this method returns PartitionableSelect as it could be joined using an array relation which requires extra fields added to the PARTITION BY clause to return proper results
fromSelectAggregate :: Maybe (EntityAlias, HashMap ColumnName ColumnName) -> AnnSelectG 'BigQuery (TableAggregateFieldG 'BigQuery Void) Expression -> FromIr Select Source #
Args | |
|
data UnfurledJoin Source #
UnfurledJoin | |
|
fromAnnotatedOrderByItemG :: AnnotatedOrderByItemG 'BigQuery Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) OrderBy Source #
Produce a valid ORDER BY construct, telling about any joins needed on the side.
unfurlAnnotatedOrderByElement :: AnnotatedOrderByElement 'BigQuery Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) FieldName Source #
Unfurl the nested set of object relations (tell'd in the writer) that are terminated by field name (Ir.AOCColumn and Ir.AOCArrayAggregation).
tableNameText :: TableName -> Text Source #
fromQualifiedTable :: TableName -> FromIr From Source #
This is really the start where you query the base table, everything else is joins attached to it.
:: ParentSelectFromEntity | The parent's entity alias from which the column values for computed fields are referred |
-> FunctionName | The function |
-> [ArgumentExp Expression] | List of positional Arguments |
-> HashMap Text (ArgumentExp Expression) | List of named arguments |
-> FromIr From |
Build a @From
expression out of a function that returns a set of rows.
fromAnnBoolExp :: GBoolExp 'BigQuery (AnnBoolExpFld 'BigQuery Expression) -> ReaderT EntityAlias FromIr Expression Source #
fromAnnBoolExpFld :: AnnBoolExpFld 'BigQuery Expression -> ReaderT EntityAlias FromIr Expression Source #
data FieldSource Source #
fromTableAggregateFieldG :: Args -> Top -> (FieldName, TableAggregateFieldG 'BigQuery Void Expression) -> ReaderT EntityAlias FromIr FieldSource Source #
fromAnnFieldsG :: Map TableName EntityAlias -> (FieldName, AnnFieldG 'BigQuery Void Expression) -> ReaderT EntityAlias FromIr FieldSource Source #
The main sources of fields, either constants, fields or via joins.
fromAnnColumnField :: AnnColumnField 'BigQuery Expression -> ReaderT EntityAlias FromIr Expression Source #
Here is where we project a field as a column expression. If
number stringification is on, then we wrap it in a
ToStringExpression
so that it's casted when being projected.
fromColumn :: ColumnName -> ReaderT EntityAlias FromIr FieldName Source #
This is where a field name "foo" is resolved to a fully qualified field name [table].[foo]. The table name comes from EntityAlias in the ReaderT.
fieldSourceProjections :: Bool -> FieldSource -> FromIr (NonEmpty Projection) Source #
fieldSourceJoins :: FieldSource -> Maybe [Join] Source #
fromObjectRelationSelectG :: Map TableName EntityAlias -> ObjectRelationSelectG 'BigQuery Void Expression -> ReaderT EntityAlias FromIr Join Source #
Produce the join for an object relation. We produce a normal select, but then include join fields. Then downstream, the DataLoader will execute the lhs select and rhs join in separate server queries, then do a Haskell-native join on the join fields.
See also fromArrayRelationSelectG
for similar example.
_lookupTableFrom :: Map TableName EntityAlias -> TableName -> FromIr (Either EntityAlias From) Source #
fromArraySelectG :: ArraySelectG 'BigQuery Void Expression -> ReaderT EntityAlias FromIr Join Source #
fromComputedFieldSelect :: ComputedFieldSelect 'BigQuery Void Expression -> ReaderT EntityAlias FromIr Expression Source #
Generate a select field @Expression
for a computed field
ARRAY( SELECT AS STRUCT `column_1`, `column_2`, `column_3` FROM UNNEST( ARRAY( SELECT AS STRUCT * FROM `dataset`.`function_name`(`argument_name` => `parent_entity`.`column`) ) ) LIMIT 1000 -- global limit ) AS `field_name`
Using LIMIT
right after 'FROM function' expression raises query exception.
To avoid this problem, we are packing and unpacking the rows returned from the function
using ARRAY
and UNNEST
, then applying LIMIT. Somehow this is working with exact reason
being unknown. See https://github.com/hasura/graphql-engine/issues/8562 for more details.
fromArrayAggregateSelectG :: AnnRelationSelectG 'BigQuery (AnnAggregateSelectG 'BigQuery Void Expression) -> ReaderT EntityAlias FromIr Join Source #
Produce the join for an array aggregate relation. We produce a normal select, but then include join fields. Then downstream, the DataLoader will execute the lhs select and rhs join in separate server queries, then do a Haskell-native join on the join fields.
See also fromArrayRelationSelectG
for similar example.
fromArrayRelationSelectG :: ArrayRelationSelectG 'BigQuery Void Expression -> ReaderT EntityAlias FromIr Join Source #
Produce a join for an array relation.
Array relations in PG/MSSQL are expressed using LEFT OUTER JOIN LATERAL or OUTER APPLY, which are essentially producing for each row on the left an array of the result from the right. Which is absolutely what you want for the array relationship.
BigQuery doesn't support that. Therefore we are instead performing one big array aggregation, for ALL rows in the table - there is no join occurring on the left-hand-side table, grouped by join fields. The data-loader will perform the LHS query and the RHS query separately.
What we do have is a GROUP BY and make sure that the join fields are included in the output. Finally, in the DataLoader.Plan/DataLoader.Execute, we implement a Haskell-native join of the left-hand-side table and the right-hand-side table.
Data looks like:
join_field_a | join_field_b | aggFieldName (array type) 1 | 1 | [ { x: 1, y: 2 }, ... ] 1 | 2 | [ { x: 1, y: 2 }, ... ]
etc.
We want to produce a query that looks like:
SELECT artist_other_id, -- For joining.
array_agg(struct(album_self_id, title)) as aggFieldName
- - ^ Aggregating the actual data.
FROM (SELECT *, -- Get everything, plus the row number:
ROW_NUMBER() OVER(PARTITION BY artist_other_id) artist_album_index
FROM hasura.Album ORDER BY album_self_id ASC
- - ^ Order by here is important for stable results. Any order by clauses for the album should appear here, NOT IN THE ARRAY_AGG.
)
AS indexed_album
WHERE artist_album_index > 1 -- ^ Here is where offsetting occurs.
GROUP BY artist_other_id -- ^ Group by for joining.
ORDER BY artist_other_id; ^ Ordering for the artist table should appear here.
Note: if original select already uses a PARTITION BY internally (for distinct_on) join fields are added to partition expressions to give proper semantics of distinct_on combined with an array relation
aliasToFieldProjection :: EntityAlias -> Projection -> Projection Source #
For entity projections, convert any entity aliases to their field names. ArrayEntityProjection and ExpressionProjection get converted to aliases to fields with the same names as all the expressions have already aliases applied in select from ArrayAgg (created in Hasura.Backends.BigQuery.ToQuery.fromArrayAgg)
fromRelName :: RelName -> FromIr Text Source #
fromMapping :: From -> HashMap ColumnName ColumnName -> ReaderT EntityAlias FromIr [Expression] Source #
The context given by the reader is of the previous/parent
"remote" table. The WHERE that we're generating goes in the child,
"local" query. The From
passed in as argument is the local table.
We should hope to see e.g. "post.category = category.id" for a local table of post and a remote table of category.
The left/right columns in HashMap ColumnName ColumnName
corresponds
to the left/right of select ... join ...
. Therefore left=remote,
right=local in this context.
fromMappingFieldNames :: EntityAlias -> HashMap ColumnName ColumnName -> ReaderT EntityAlias FromIr [(FieldName, FieldName)] Source #
Given an alias for the remote table, and a map of local-to-remote column
name pairings, produce FieldName
pairings (column names paired with their
associated table names).
For example, we might convert the following:
[ ( ColumnName { columnName = "author_id" } , ColumnName { columnName = "id" } ) ]
... into something like this:
( FieldName { fieldName = "id" , fieldNameEntity = "t_author1" } , FieldName { fieldName = "author_id" , fieldNameEntity = "t_article1" } )
Note that the columns flip around for the output. The input map is
(local, remote)
.
fromOpExpG :: Expression -> OpExpG 'BigQuery Expression -> FromIr Expression Source #
toNonEmpty :: MonadValidate (NonEmpty Error) m => [x] -> m (NonEmpty x) Source #
Attempt to refine a list into a NonEmpty
. If the given list is empty,
this will refute
the computation with an UnexpectedEmptyList
error.
prepareJoinFieldProjection :: (FieldName, FieldName) -> Projection Source #
Get the remote field from a pair (see fromMappingFieldNames
for more
information) and produce a Projection
.
selectProjectionsFromFieldSources :: Bool -> [FieldSource] -> FromIr (NonEmpty Projection) Source #
aggFieldName :: Text Source #
existsFieldName :: Text Source #
data NameTemplate Source #
fromAlias :: From -> EntityAlias Source #
fieldTextNames :: AnnFieldsG 'BigQuery Void Expression -> [Text] Source #
unEntityAlias :: EntityAlias -> Text Source #
getGlobalTop :: FromIr Top Source #