graphql-engine-1.0.0: GraphQL API over Postgres
Safe HaskellNone
LanguageHaskell2010

Hasura.Backends.BigQuery.FromIr

Description

Translate from the DML to the BigQuery dialect.

Synopsis

Documentation

newtype FromIr a Source #

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.

Constructors

FromIr 

Instances

Instances details
Monad FromIr Source # 
Instance details

Defined in Hasura.Backends.BigQuery.FromIr

Methods

(>>=) :: FromIr a -> (a -> FromIr b) -> FromIr b #

(>>) :: FromIr a -> FromIr b -> FromIr b #

return :: a -> FromIr a #

Functor FromIr Source # 
Instance details

Defined in Hasura.Backends.BigQuery.FromIr

Methods

fmap :: (a -> b) -> FromIr a -> FromIr b #

(<$) :: a -> FromIr b -> FromIr a #

Applicative FromIr Source # 
Instance details

Defined in Hasura.Backends.BigQuery.FromIr

Methods

pure :: a -> FromIr a #

(<*>) :: FromIr (a -> b) -> FromIr a -> FromIr b #

liftA2 :: (a -> b -> c) -> FromIr a -> FromIr b -> FromIr c #

(*>) :: FromIr a -> FromIr b -> FromIr b #

(<*) :: FromIr a -> FromIr b -> FromIr a #

MonadValidate (NonEmpty Error) FromIr Source # 
Instance details

Defined in Hasura.Backends.BigQuery.FromIr

data FromIrState Source #

Constructors

FromIrState 

Fields

data FromIrReader Source #

Constructors

FromIrReader 

Fields

data FromIrConfig Source #

Config values for the from-IR translator.

Constructors

FromIrConfig 

Fields

  • globalSelectLimit :: Top

    Applies globally to all selects, and may be reduced to something even smaller by permission/user args.

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

Constructors

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.

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

data UnfurledJoin Source #

Constructors

UnfurledJoin 

Fields

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).

fromQualifiedTable :: TableName -> FromIr From Source #

This is really the start where you query the base table, everything else is joins attached to it.

fromFunction Source #

Arguments

:: 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.

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.

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.

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)

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).

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.