def finalize_args( bound_call: polyres.BoundCall, *, ctx: context.ContextLevel, ) -> typing.Tuple[typing.List[irast.CallArg], typing.List[ft.TypeModifier]]: args: typing.List[irast.CallArg] = [] typemods = [] for barg in bound_call.args: param = barg.param arg = barg.val if param is None: # defaults bitmask args.append(irast.CallArg(expr=arg)) typemods.append(ft.TypeModifier.SINGLETON) continue param_mod = param.get_typemod(ctx.env.schema) typemods.append(param_mod) if param_mod is not ft.TypeModifier.SET_OF: arg_scope = pathctx.get_set_scope(arg, ctx=ctx) param_shortname = param.get_shortname(ctx.env.schema) # Arg was wrapped for scope fencing purposes, # but that fence has been removed above, so unwrap it. orig_arg = arg arg = irutils.unwrap_set(arg) if (param_mod is ft.TypeModifier.OPTIONAL or param_shortname in bound_call.null_args): if arg_scope is not None: # Due to the construction of relgen, the (unfenced) # subscope is necessary to shield LHS paths from the outer # query to prevent path binding which may break OPTIONAL. branch = arg_scope.unfence() pathctx.register_set_in_scope(arg, ctx=ctx) pathctx.mark_path_as_optional(arg.path_id, ctx=ctx) if arg_scope is not None: pathctx.assign_set_scope(arg, branch, ctx=ctx) elif arg_scope is not None: arg_scope.collapse() if arg is orig_arg: pathctx.assign_set_scope(arg, None, ctx=ctx) paramtype = barg.param_type param_kind = param.get_kind(ctx.env.schema) if param_kind is ft.ParameterKind.VARIADIC: # For variadic params, paramtype would be array<T>, # and we need T to cast the arguments. assert isinstance(paramtype, s_types.Array) paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0] val_material_type = barg.valtype.material_type(ctx.env.schema) param_material_type = paramtype.material_type(ctx.env.schema) # Check if we need to cast the argument value before passing # it to the callable. For tuples, we also check that the element # names match. compatible = ( val_material_type.issubclass(ctx.env.schema, param_material_type) and (not param_material_type.is_tuple() or (param_material_type.get_element_names(ctx.env.schema) == val_material_type.get_element_names(ctx.env.schema)))) if not compatible: # The callable form was chosen via an implicit cast, # cast the arguments so that the backend has no # wiggle room to apply its own (potentially different) # casting. arg = cast.compile_cast(arg, paramtype, srcctx=None, ctx=ctx) if param_mod is not ft.TypeModifier.SET_OF: call_arg = irast.CallArg(expr=arg, cardinality=ft.Cardinality.ONE) else: call_arg = irast.CallArg(expr=arg, cardinality=None) stmtctx.get_expr_cardinality_later(target=call_arg, field='cardinality', irexpr=arg, ctx=ctx) args.append(call_arg) return args, typemods
def finalize_args( bound_call: polyres.BoundCall, *, actual_typemods: Sequence[ft.TypeModifier] = (), is_polymorphic: bool = False, ctx: context.ContextLevel, ) -> Tuple[List[irast.CallArg], List[ft.TypeModifier]]: args: List[irast.CallArg] = [] typemods = [] for i, barg in enumerate(bound_call.args): param = barg.param arg = barg.val if param is None: # defaults bitmask args.append(irast.CallArg(expr=arg)) typemods.append(ft.TypeModifier.SINGLETON) continue if actual_typemods: param_mod = actual_typemods[i] else: param_mod = param.get_typemod(ctx.env.schema) typemods.append(param_mod) if param_mod is not ft.TypeModifier.SET_OF: arg_scope = pathctx.get_set_scope(arg, ctx=ctx) param_shortname = param.get_parameter_name(ctx.env.schema) # Arg was wrapped for scope fencing purposes, # but that fence has been removed above, so unwrap it. orig_arg = arg arg = irutils.unwrap_set(arg) if (param_mod is ft.TypeModifier.OPTIONAL or param_shortname in bound_call.null_args): if arg_scope is not None: # Due to the construction of relgen, the (unfenced) # subscope is necessary to shield LHS paths from the outer # query to prevent path binding which may break OPTIONAL. branch = arg_scope.unfence() pathctx.register_set_in_scope(arg, ctx=ctx) pathctx.mark_path_as_optional(arg.path_id, ctx=ctx) if arg_scope is not None: pathctx.assign_set_scope(arg, branch, ctx=ctx) elif arg_scope is not None: arg_scope.collapse() if arg is orig_arg: pathctx.assign_set_scope(arg, None, ctx=ctx) else: if (is_polymorphic and ctx.expr_exposed and ctx.implicit_limit and isinstance(arg.expr, irast.SelectStmt) and arg.expr.limit is None): arg.expr.limit = setgen.ensure_set( dispatch.compile( qlast.IntegerConstant(value=str(ctx.implicit_limit)), ctx=ctx, ), ctx=ctx, ) paramtype = barg.param_type param_kind = param.get_kind(ctx.env.schema) if param_kind is ft.ParameterKind.VARIADIC: # For variadic params, paramtype would be array<T>, # and we need T to cast the arguments. assert isinstance(paramtype, s_types.Array) paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0] # Check if we need to cast the argument value before passing # it to the callable. compatible = schemactx.is_type_compatible( paramtype, barg.valtype, ctx=ctx, ) if not compatible: # The callable form was chosen via an implicit cast, # cast the arguments so that the backend has no # wiggle room to apply its own (potentially different) # casting. arg = casts.compile_cast( arg, paramtype, srcctx=None, ctx=ctx) call_arg = irast.CallArg(expr=arg, cardinality=None) stmtctx.get_expr_cardinality_later( target=call_arg, field='cardinality', irexpr=arg, ctx=ctx) args.append(call_arg) return args, typemods
def finalize_args( bound_call: polyres.BoundCall, *, arg_ctxs: Dict[irast.Set, context.ContextLevel], actual_typemods: Sequence[ft.TypeModifier] = (), is_polymorphic: bool = False, ctx: context.ContextLevel, ) -> Tuple[List[irast.CallArg], List[ft.TypeModifier]]: args: List[irast.CallArg] = [] typemods = [] for i, barg in enumerate(bound_call.args): param = barg.param arg = barg.val if param is None: # defaults bitmask args.append(irast.CallArg(expr=arg)) typemods.append(ft.TypeModifier.SingletonType) continue if actual_typemods: param_mod = actual_typemods[i] else: param_mod = param.get_typemod(ctx.env.schema) typemods.append(param_mod) orig_arg = arg arg_ctx = arg_ctxs.get(orig_arg) arg_scope = pathctx.get_set_scope(arg, ctx=ctx) if param_mod is not ft.TypeModifier.SetOfType: param_shortname = param.get_parameter_name(ctx.env.schema) # Arg was wrapped for scope fencing purposes, # but that fence has been removed above, so unwrap it. arg = irutils.unwrap_set(arg) if (param_mod is ft.TypeModifier.OptionalType or param_shortname in bound_call.null_args): process_path_log(arg_ctx, arg_scope) if arg_scope is not None: # Due to the construction of relgen, the (unfenced) # subscope is necessary to shield LHS paths from the outer # query to prevent path binding which may break OPTIONAL. arg_scope.mark_as_optional() branch = arg_scope.unfence() pathctx.register_set_in_scope(arg, optional=True, ctx=ctx) if arg_scope is not None: pathctx.assign_set_scope(arg, branch, ctx=ctx) elif arg_scope is not None: arg_scope.collapse() if arg is orig_arg: pathctx.assign_set_scope(arg, None, ctx=ctx) else: process_path_log(arg_ctx, arg_scope) is_array_agg = ( isinstance(bound_call.func, s_func.Function) and ( bound_call.func.get_shortname(ctx.env.schema) == sn.QualName('std', 'array_agg') ) ) if ( # Ideally, we should implicitly slice all array values, # but in practice, the vast majority of large arrays # will come from array_agg, and so we only care about # that. is_array_agg and ctx.expr_exposed and ctx.implicit_limit and isinstance(arg.expr, irast.SelectStmt) and arg.expr.limit is None and not ctx.inhibit_implicit_limit ): arg.expr.limit = setgen.ensure_set( dispatch.compile( qlast.IntegerConstant(value=str(ctx.implicit_limit)), ctx=ctx, ), ctx=ctx, ) paramtype = barg.param_type param_kind = param.get_kind(ctx.env.schema) if param_kind is ft.ParameterKind.VariadicParam: # For variadic params, paramtype would be array<T>, # and we need T to cast the arguments. assert isinstance(paramtype, s_types.Array) paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0] # Check if we need to cast the argument value before passing # it to the callable. compatible = s_types.is_type_compatible( paramtype, barg.valtype, schema=ctx.env.schema, ) if not compatible: # The callable form was chosen via an implicit cast, # cast the arguments so that the backend has no # wiggle room to apply its own (potentially different) # casting. arg = casts.compile_cast( arg, paramtype, srcctx=None, ctx=ctx) args.append(irast.CallArg(expr=arg, cardinality=None)) # If we have any logged paths left over and our enclosing # context is logging paths, propagate them up. if arg_ctx and arg_ctx.path_log and ctx.path_log is not None: ctx.path_log.extend(arg_ctx.path_log) return args, typemods
def handle_conditional_insert( expr: qlast.InsertQuery, rhs: irast.InsertStmt, lhs_set: Union[irast.Expr, irast.Set], *, ctx: context.ContextLevel, ) -> irast.ConstraintRef: def error(s: str) -> NoReturn: raise errors.QueryError( f'Invalid conditional INSERT statement: {s}', context=expr.context, ) schema = ctx.env.schema if not isinstance(lhs_set, irast.Set): error("left hand side is not SELECT") lhs_set = irutils.unwrap_set(lhs_set) if not isinstance(lhs_set.expr, irast.SelectStmt): error("left hand side is not SELECT") lhs = lhs_set.expr if lhs.result.path_id != rhs.subject.path_id: error("types do not match") if lhs.where: filtered_ptrs = inference.cardinality.extract_filters( lhs.result, lhs.where, scope_tree=ctx.path_scope, singletons=(), env=ctx.env, strict=True) else: filtered_ptrs = None # TODO: Can we support some >1 cases? if not filtered_ptrs or len(filtered_ptrs) > 1: error("does not contain exactly one FILTER clause") return None exclusive_constr: s_constr.Constraint = schema.get('std::exclusive') shape_props = {} for shape_set, _ in rhs.subject.shape: # We need to go through to the base_ptr to get at the # underlying type (instead of the shape's subtype) base_ptr = shape_set.rptr.ptrref.base_ptr if (not isinstance(base_ptr, irast.PointerRef) or not isinstance(shape_set.expr, irast.SelectStmt)): continue schema, pptr = typeutils.ptrcls_from_ptrref(base_ptr, schema=schema) shape_props[pptr] = shape_set.expr.result, base_ptr ptr, ptr_set = filtered_ptrs[0] ptr = ptr.get_nearest_non_derived_parent(schema) if ptr not in shape_props: error("property in FILTER clause does not match INSERT") result, rptr = shape_props[ptr] if not simple_stmt_eq(ptr_set.expr, result.expr, schema): error("value in FILTER clause does not match INSERT") ex_cnstrs = [ c for c in ptr.get_constraints(schema).objects(schema) if c.issubclass(schema, exclusive_constr) and not c.get_subjectexpr(schema) ] if len(ex_cnstrs) != 1 or not ptr.is_property(schema): error("FILTER is not on an exclusive property") ex_cnstr = ex_cnstrs[0] module_id = schema.get_global(s_mod.Module, ptr.get_name(schema).module).id ctx.env.schema = schema return irast.ConstraintRef(id=ex_cnstr.id, module_id=module_id)