diff --git a/parsil/src/executor.rs b/parsil/src/executor.rs index 18bb4262..e6431175 100644 --- a/parsil/src/executor.rs +++ b/parsil/src/executor.rs @@ -293,13 +293,13 @@ fn convert_funcalls(expr: &mut Expr) -> Result<()> { } // Build the subquery that will be used as the source of epochs and block numbers -// in the internal queries generated by the visitors implemented in this module. +// in the internal queries generated by the executor visitors implemented in this module. // More specifically, this method builds the following JOIN table: // {table} JOIN ( // SELECT {USER_EPOCH}, {INCREMENTAL_EPOCH} FROM {mapper_table} // WHERE {USER_EPOCH} >= $min_block AND {USER_EPOCH} <= $max_block // ) ON {VALID_FROM} <= {INCREMENTAL_EPOCH} AND {VALID_UNTIL} >= {INCREMENTAL_EPOCH} -fn block_range_table( +fn executor_range_table( settings: &ParsilSettings, table: &ZkTable, ) -> TableWithJoins { @@ -491,14 +491,173 @@ impl<'a, C: ContextProvider> KeyFetcher<'a, C> { Ok(()) } - // Internal implementation of `post_table_factor` for `KeyFetcher` visitor. - // The `EPOCH_IS_INCREMENTAL` parameter affects whether the EPOCH column - // being returned in the constructed query is an `INCREMENTAL_EPOCH` or a - // `USER_EPOCH`, which depends on the context in which the query is executed. - fn post_table_factor_internal( - &mut self, - table_factor: &mut TableFactor, - ) -> Result<()> { + const MIN_EPOCH_ALIAS: &'static str = "min_epoch"; + const MAX_EPOCH_ALIAS: &'static str = "max_epoch"; + + + fn expand_block_range() -> Expr { + funcall( + "generate_series", + vec![ + funcall( + "GREATEST", + vec![ + Expr::Identifier(Ident::new(VALID_FROM)), + Expr::Identifier(Ident::new(Self::MIN_EPOCH_ALIAS)), + ], + ), + funcall( + "LEAST", + vec![ + Expr::Identifier(Ident::new(VALID_UNTIL)), + Expr::Identifier(Ident::new(Self::MAX_EPOCH_ALIAS)), + ], + ), + ], + ) + } + + // Build the subquery that will be used as the source of epochs and block numbers + // in the internal queries generated by the executor visitors implemented in this module. + // More specifically, this method builds the following JOIN table: + // {table} JOIN ( + // SELECT MIN{INCREMENTAL_EPOCH} as {MIN_EPOCH_ALIAS}, MAX{INCREMENTAL_EPOCH} as {MAX_EPOCH_ALIAS} + // FROM {mapper_table} + // WHERE {USER_EPOCH} >= $min_block AND {USER_EPOCH} <= $max_block + // ) ON {VALID_FROM} <= {MAX_EPOCH_ALIAS} AND {VALID_UNTIL} >= {MIN_EPOCH_ALIAS} + fn range_table( + &self, + table: &ZkTable, + ) -> TableWithJoins { + let mapper_table_name = mapper_table_name(&table.zktable_name); + TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::new(table.zktable_name.clone())]), + alias: None, + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![], + }, + joins: vec![Join { + relation: TableFactor::Derived { + lateral: false, + subquery: Box::new(Query { + with: None, + body: Box::new(SetExpr::Select(Box::new(Select { + distinct: None, + top: None, + projection: vec![ + SelectItem::ExprWithAlias { + expr: funcall("MIN", vec![ + Expr::Identifier(Ident::new(INCREMENTAL_EPOCH)) + ]), + alias: Ident::new(Self::MIN_EPOCH_ALIAS), + }, + SelectItem::ExprWithAlias { + expr: funcall("MAX", vec![ + Expr::Identifier(Ident::new(INCREMENTAL_EPOCH)) + ]), + alias: Ident::new(Self::MAX_EPOCH_ALIAS), + }, + ], + into: None, + from: vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::new(mapper_table_name)]), + alias: None, + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![], + }, + joins: vec![], + }], + lateral_views: vec![], + prewhere: None, + selection: Some(Expr::BinaryOp { + left: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(USER_EPOCH))), + op: BinaryOperator::GtEq, + right: Box::new(Expr::Value(Value::Placeholder( + self.settings.placeholders.min_block_placeholder.to_owned(), + ))), + }), + op: BinaryOperator::And, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(USER_EPOCH))), + op: BinaryOperator::LtEq, + right: Box::new(Expr::Value(Value::Placeholder( + self.settings.placeholders.max_block_placeholder.to_owned(), + ))), + }), + }), + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + qualify: None, + window_before_qualify: false, + value_table_mode: None, + connect_by: None, + }))), + order_by: None, + limit: None, + limit_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + settings: None, + format_clause: None, + }), + // Subqueries *MUST* have an alias in PgSQL + alias: Some(TableAlias { + name: Ident::new("_mapper"), + columns: vec![], + }), + }, + join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { + left: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(VALID_FROM))), + op: BinaryOperator::LtEq, + right: Box::new(Expr::Identifier(Ident::new(Self::MAX_EPOCH_ALIAS))), + }), + op: BinaryOperator::And, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(VALID_UNTIL))), + op: BinaryOperator::GtEq, + right: Box::new(Expr::Identifier(Ident::new(Self::MIN_EPOCH_ALIAS))), + }), + })), + }], + } + } +} +impl AstMutator for KeyFetcher<'_, C> { + type Error = anyhow::Error; + + fn post_select(&mut self, select: &mut Select) -> Result<()> { + // When we meet a SELECT, insert a * to be sure to bubble up the key & + // block number + select.projection = vec![ + SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(KEY))), + SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(EPOCH))), + ]; + Ok(()) + } + + fn post_expr(&mut self, expr: &mut Expr) -> Result<()> { + convert_number_string(expr)?; + + Ok(()) + } + + fn post_table_factor(&mut self, table_factor: &mut TableFactor) -> Result<()> { if let Some(replacement) = match table_factor { TableFactor::Table { name, alias, .. } => { // The vTable being referenced @@ -529,11 +688,7 @@ impl<'a, C: ContextProvider> KeyFetcher<'a, C> { std::iter::once(SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(KEY)))) .chain(std::iter::once( SelectItem::ExprWithAlias { - expr: if EPOCH_IS_INCREMENTAL { - Expr::Identifier(Ident::new(INCREMENTAL_EPOCH)) - } else { - Expr::Identifier(Ident::new(USER_EPOCH)) - }, + expr: Self::expand_block_range(), alias: Ident::new(EPOCH) } )) @@ -546,9 +701,23 @@ impl<'a, C: ContextProvider> KeyFetcher<'a, C> { .unwrap_or(column.name.as_str()), ); match column.kind { - // primary index column := USER_EPOCH AS name + // primary index column := $MIN_BLOCK AS name. + // We return a constant value as a trick to avoid extracting USER_EPOCH from + // epoch mapper table, which would require a costly JOIN. + // Indeed, given that: + // - The filtering over the primary index have already been applied in + // the epoch mapper table + // - This column is later ignored in the overall query + // We just need to provide as block_number a column value that satisfies the + // filtering over the primary index specified in the existing query, + // which is `block_number >= $MIN_BLOCK AND block_number <= $MAX_BLOCK`, as + // any other predicate is removed from the query by the isolator + // ToDo: remove this column once we merge the new version of the isolator, + // which will remove the block_number range filtering ColumnKind::PrimaryIndex => SelectItem::ExprWithAlias { - expr: Expr::Identifier(Ident::new(USER_EPOCH)), + expr: Expr::Value(Value::Placeholder( + self.settings.placeholders.min_block_placeholder.to_owned(), + )), alias, }, // other columns := payload->'cells'->'id'->'value' AS name @@ -571,7 +740,7 @@ impl<'a, C: ContextProvider> KeyFetcher<'a, C> { top: None, projection: select_items, into: None, - from: vec![block_range_table(self.settings, &table)], + from: vec![self.range_table(&table)], lateral_views: vec![], prewhere: None, selection: None, @@ -611,30 +780,152 @@ impl<'a, C: ContextProvider> KeyFetcher<'a, C> { Ok(()) } } -impl AstMutator for KeyFetcher<'_, C> { - type Error = anyhow::Error; - fn post_select(&mut self, select: &mut Select) -> Result<()> { - // When we meet a SELECT, insert a * to be sure to bubble up the key & - // block number - select.projection = vec![ - SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(KEY))), - SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(EPOCH))), - ]; - Ok(()) - } +/// Implementation of `post_table_factor` shared both by `Executor` and by +/// `ExecutorWithKey`. If the flag `return_keys` is true, `key` and `epoch` +/// columns are returned as well as `SELECT` items in the constructed sub-query, +/// as required in the `ExecutorWithKey` implementation of `post_table_factor` +fn post_table_factor( + settings: &ParsilSettings, + table_factor: &mut TableFactor, + return_keys: bool, +) -> Result<()> { + if let Some(replacement) = match &table_factor { + TableFactor::Table { + name, alias, args, .. + } => { + // In this case, we handle + // + // ... FROM table [AS alias [(col1, // col2, ...)]] + // + // so both the table name and its columns may be aliased. + if args.is_some() { + unreachable!() + } else { + // The actual table being referenced + let concrete_table_name = &name.0[0].value; - fn post_expr(&mut self, expr: &mut Expr) -> Result<()> { - convert_number_string(expr)?; + // Fetch all the column declared in this table + let table = settings.context.fetch_table(concrete_table_name)?; + let table_columns = &table.columns; - Ok(()) - } + // Extract the apparent table name (either the concrete one + // or its alia), and, if they exist, the aliased column + // names. + let (apparent_table_name, column_aliases) = if let Some(table_alias) = alias { + ( + table_alias.name.value.to_owned(), + if table_alias.columns.is_empty() { + None + } else { + table_alias.columns.clone().into() + }, + ) + } else { + (concrete_table_name.to_owned(), None) + }; - fn post_table_factor(&mut self, table_factor: &mut TableFactor) -> Result<()> { - self.post_table_factor_internal::(table_factor) + // Create one `SelectItem` for each column of the table, as they have to be returned + // in `SELECT` in the constructed sub-query + let current_columns_select_items = table_columns + .iter() + .enumerate() + .map(|(i, column)| { + let alias = Ident::new( + column_aliases + .as_ref() + .map(|a| a[i].value.as_str()) + .unwrap_or(column.name.as_str()), + ); + match column.kind { + // primary index column := USER_EPOCH AS name + ColumnKind::PrimaryIndex => SelectItem::ExprWithAlias { + expr: Expr::Identifier(Ident::new(USER_EPOCH)), + alias, + }, + // other columns := PAYLOAD->'cells'->'id'->'value' AS name + ColumnKind::SecondaryIndex | ColumnKind::Standard => { + SelectItem::ExprWithAlias { + expr: fetch_from_payload(column.id), + alias, + } + } + } + }); + + let select_items = if return_keys { + // Insert the `key` and `epoch` columns in the selected values... + std::iter::once(SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(KEY)))) + .chain(std::iter::once( + SelectItem::ExprWithAlias { + expr: Expr::Identifier(Ident::new(USER_EPOCH)), + alias: Ident::new(EPOCH) + } + )).chain(current_columns_select_items) + .collect() + } else { + current_columns_select_items.collect() + }; + + Some(TableFactor::Derived { + lateral: false, + subquery: Box::new(Query { + with: None, + body: Box::new(SetExpr::Select(Box::new(Select { + distinct: None, + top: None, + projection: select_items, + into: None, + from: vec![executor_range_table(settings, &table)], + lateral_views: vec![], + prewhere: None, + selection: None, + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + qualify: None, + window_before_qualify: false, + value_table_mode: None, + connect_by: None, + }))), + order_by: None, + limit: None, + limit_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + settings: None, + format_clause: None, + }), + // Subqueries *MUST* have an alias in PgSQL + alias: Some(TableAlias { + name: Ident::new(apparent_table_name), + columns: vec![], + }), + }) + } + } + TableFactor::Derived { .. } => None, + TableFactor::TableFunction { .. } => todo!(), + TableFactor::Function { .. } => todo!(), + TableFactor::UNNEST { .. } => todo!(), + TableFactor::JsonTable { .. } => todo!(), + TableFactor::NestedJoin { .. } => todo!(), + TableFactor::Pivot { .. } => todo!(), + TableFactor::Unpivot { .. } => todo!(), + TableFactor::MatchRecognize { .. } => todo!(), + } { + *table_factor = replacement; } + + Ok(()) } + struct Executor<'a, C: ContextProvider> { settings: &'a ParsilSettings, } @@ -655,124 +946,7 @@ impl AstMutator for Executor<'_, C> { } fn post_table_factor(&mut self, table_factor: &mut TableFactor) -> Result<()> { - if let Some(replacement) = match &table_factor { - TableFactor::Table { - name, alias, args, .. - } => { - // In this case, we handle - // - // ... FROM table [AS alias [(col1, // col2, ...)]] - // - // so both the table name and its columns may be aliased. - if args.is_some() { - unreachable!() - } else { - // The actual table being referenced - let concrete_table_name = &name.0[0].value; - - // Fetch all the column declared in this table - let table = self.settings.context.fetch_table(concrete_table_name)?; - let table_columns = &table.columns; - - // Extract the apparent table name (either the concrete one - // or its alia), and, if they exist, the aliased column - // names. - let (apparent_table_name, column_aliases) = if let Some(table_alias) = alias { - ( - table_alias.name.value.to_owned(), - if table_alias.columns.is_empty() { - None - } else { - table_alias.columns.clone().into() - }, - ) - } else { - (concrete_table_name.to_owned(), None) - }; - - let select_items = table_columns - .iter() - .enumerate() - .map(|(i, column)| { - let alias = Ident::new( - column_aliases - .as_ref() - .map(|a| a[i].value.as_str()) - .unwrap_or(column.name.as_str()), - ); - match column.kind { - // primary index column := USER_EPOCH AS name - ColumnKind::PrimaryIndex => SelectItem::ExprWithAlias { - expr: Expr::Identifier(Ident::new(USER_EPOCH)), - alias, - }, - // other columns := PAYLOAD->'cells'->'id'->'value' AS name - ColumnKind::SecondaryIndex | ColumnKind::Standard => { - SelectItem::ExprWithAlias { - expr: fetch_from_payload(column.id), - alias, - } - } - } - }) - .collect(); - - Some(TableFactor::Derived { - lateral: false, - subquery: Box::new(Query { - with: None, - body: Box::new(SetExpr::Select(Box::new(Select { - distinct: None, - top: None, - projection: select_items, - into: None, - from: vec![block_range_table(self.settings, &table)], - lateral_views: vec![], - prewhere: None, - selection: None, - group_by: GroupByExpr::Expressions(vec![], vec![]), - cluster_by: vec![], - distribute_by: vec![], - sort_by: vec![], - having: None, - named_window: vec![], - qualify: None, - window_before_qualify: false, - value_table_mode: None, - connect_by: None, - }))), - order_by: None, - limit: None, - limit_by: vec![], - offset: None, - fetch: None, - locks: vec![], - for_clause: None, - settings: None, - format_clause: None, - }), - // Subqueries *MUST* have an alias in PgSQL - alias: Some(TableAlias { - name: Ident::new(apparent_table_name), - columns: vec![], - }), - }) - } - } - TableFactor::Derived { .. } => None, - TableFactor::TableFunction { .. } => todo!(), - TableFactor::Function { .. } => todo!(), - TableFactor::UNNEST { .. } => todo!(), - TableFactor::JsonTable { .. } => todo!(), - TableFactor::NestedJoin { .. } => todo!(), - TableFactor::Pivot { .. } => todo!(), - TableFactor::Unpivot { .. } => todo!(), - TableFactor::MatchRecognize { .. } => todo!(), - } { - *table_factor = replacement; - } - - Ok(()) + post_table_factor(self.settings, table_factor, false) } } @@ -799,10 +973,7 @@ impl AstMutator for ExecutorWithKey<'_, C> { } fn post_table_factor(&mut self, table_factor: &mut TableFactor) -> Result<()> { - let mut key_fetcher = KeyFetcher { - settings: self.settings, - }; - key_fetcher.post_table_factor_internal::(table_factor) + post_table_factor(self.settings, table_factor, true) } fn post_select(&mut self, select: &mut Select) -> Result<()> {