(
scx: &StatementContext,
select: SelectStatement<Aug>,
params: &Params,
copy_to: Option<CopyFormat>,
)
| 212 | } |
| 213 | |
| 214 | fn plan_select_inner( |
| 215 | scx: &StatementContext, |
| 216 | select: SelectStatement<Aug>, |
| 217 | params: &Params, |
| 218 | copy_to: Option<CopyFormat>, |
| 219 | ) -> Result<(SelectPlan, RelationDesc), PlanError> { |
| 220 | let when = query::plan_as_of(scx, select.as_of.clone())?; |
| 221 | let lifetime = QueryLifetime::OneShot; |
| 222 | let query::PlannedRootQuery { |
| 223 | mut expr, |
| 224 | desc, |
| 225 | finishing, |
| 226 | scope: _, |
| 227 | } = query::plan_root_query(scx, select.query.clone(), lifetime)?; |
| 228 | expr.bind_parameters_and_simplify_offset(scx, lifetime, params)?; |
| 229 | |
| 230 | // We need to concretize the `limit` and `offset` of the RowSetFinishing, so that we go from |
| 231 | // `RowSetFinishing<HirScalarExpr, HirScalarExpr>` to `RowSetFinishing`. |
| 232 | // This involves binding parameters and evaluating each expression to a number. |
| 233 | // (This should be possible even for `limit` here, because we are at the top level of a SELECT, |
| 234 | // so this `limit` has to be a constant.) |
| 235 | let limit = match finishing.limit { |
| 236 | None => None, |
| 237 | Some(mut limit) => { |
| 238 | limit.bind_parameters_and_simplify_offset(scx, lifetime, params)?; |
| 239 | // TODO: Call `try_into_literal_int64` instead of `as_literal`. |
| 240 | let Some(limit) = limit.as_literal() else { |
| 241 | sql_bail!( |
| 242 | "Top-level LIMIT must be a constant expression, got {}", |
| 243 | limit |
| 244 | ) |
| 245 | }; |
| 246 | match limit { |
| 247 | Datum::Null => None, |
| 248 | Datum::Int64(v) if v >= 0 => NonNeg::<i64>::try_from(v).ok(), |
| 249 | _ => { |
| 250 | soft_panic_or_log!("Valid literal limit must be asserted in `plan_select`"); |
| 251 | sql_bail!("LIMIT must be a non-negative INT or NULL") |
| 252 | } |
| 253 | } |
| 254 | } |
| 255 | }; |
| 256 | let offset = { |
| 257 | let mut offset = finishing.offset.clone(); |
| 258 | offset.bind_parameters_and_simplify_offset(scx, lifetime, params)?; |
| 259 | let offset = offset_into_value(offset.take())?; |
| 260 | offset.try_into().map_err(|_| { |
| 261 | // We already checked in bind_parameters_and_simplify_offset / offset_into_value. |
| 262 | soft_panic_or_log!("unexpectedly negative OFFSET"); |
| 263 | negative_offset_error(offset) |
| 264 | })? |
| 265 | }; |
| 266 | |
| 267 | // Unmaterializable functions are evaluated based on various information (e.g., the catalog) |
| 268 | // during sequencing, without taking AS OF into account. This would be hard to fix, so for now |
| 269 | // we just disallow AS OF when there is an unmaterializable function in a query (except mz_now). |
| 270 | if scx.is_feature_flag_enabled(&DISALLOW_UNMATERIALIZABLE_FUNCTIONS_AS_OF) |
| 271 | && select.as_of.is_some() |
no test coverage detected