def decompile_ir(ir_tree, inline_anchors=False, return_statement=False, *, schema): decompiler = IRDecompiler() qltree = decompiler.transform(ir_tree, inline_anchors=inline_anchors, schema=schema) if return_statement and not isinstance(qltree, qlast.Statement): qltree = qlast.SelectQuery(result=qltree) return qltree
def reduce_Select(self, *kids): r"%reduce SELECT OptionallyAliasedExpr \ OptFilterClause OptSortClause OptSelectLimit" self.val = qlast.SelectQuery( result=kids[1].val.expr, result_alias=kids[1].val.alias, where=kids[2].val, orderby=kids[3].val, offset=kids[4].val[0], limit=kids[4].val[1], )
def _encode_default(self, schema, context, node, op): if op.new_value: expr = op.new_value if not isinstance(expr, s_expr.Expression): expr_t = qlast.SelectQuery( result=qlast.BaseConstant.from_python(expr)) op.new_value = s_expr.Expression.from_ast( expr_t, schema, context.modaliases, ) super()._apply_field_ast(schema, context, node, op)
def generate_config_query(schema) -> str: cfg = schema.get('cfg::Config') ref = qlast.ObjectRef(name='Config', module='cfg') query = qlast.SelectQuery( result=qlast.Shape( expr=qlast.Path(steps=[ref]), elements=qlcompiler.get_config_type_shape(schema, cfg, path=[ref]), ), limit=qlast.IntegerConstant(value='1', ), ) return qlcodegen.generate_source(query)
def _compile_view_expr(cls, expr, classname, schema, context): from edb.edgeql import compiler as qlcompiler ir = context.get_cached((expr, classname)) if ir is None: if not isinstance(expr, qlast.Statement): expr = qlast.SelectQuery(result=expr) ir = qlcompiler.compile_ast_to_ir( expr, schema, derived_target_module=classname.module, result_view_name=classname, modaliases=context.modaliases, schema_view_mode=True) context.cache_value((expr, classname), ir) return ir
def compile_call_arg( arg_ql: qlast.Expr, *, ctx: context.ContextLevel) -> Tuple[irast.Set, context.ContextLevel]: with ctx.new() as argctx: argctx.path_log = [] # We put on a SET OF fence preemptively in case this is # a SET OF arg, which we don't know yet due to polymorphic # matching. We will remove it if necessary in `finalize_args()`. # Similarly, delay the decision to inject the implicit limit to # `finalize_args()`. arg_ql = qlast.SelectQuery(result=arg_ql, context=arg_ql.context, implicit=True) argctx.inhibit_implicit_limit = True return dispatch.compile(arg_ql, ctx=argctx), argctx
def compile_ConfigReset( expr: qlast.ConfigReset, *, ctx: context.ContextLevel, ) -> irast.Set: info = _validate_op(expr, ctx=ctx) filter_expr = expr.where select_ir = None if not info.param_type.is_object_type() and filter_expr is not None: raise errors.QueryError( 'RESET of a primitive configuration parameter ' 'must not have a FILTER clause', context=expr.context, ) elif isinstance(info.param_type, s_objtypes.ObjectType): param_type_name = info.param_type.get_name(ctx.env.schema) param_type_ref = qlast.ObjectRef( name=param_type_name.name, module=param_type_name.module, ) select = qlast.SelectQuery( result=qlast.Shape( expr=qlast.Path(steps=[param_type_ref]), elements=s_utils.get_config_type_shape(ctx.env.schema, info.param_type, path=[param_type_ref]), ), where=filter_expr, ) ctx.modaliases[None] = 'cfg' select_ir = setgen.ensure_set(dispatch.compile(select, ctx=ctx), ctx=ctx) config_reset = irast.ConfigReset( name=info.param_name, cardinality=info.cardinality, scope=expr.scope, requires_restart=info.requires_restart, backend_setting=info.backend_setting, context=expr.context, selector=select_ir, ) return setgen.ensure_set(config_reset, ctx=ctx)
def eval_ShapeElement(el: qlast.ShapeElement, ctx: EvalContext) -> Result: if el.compexpr: result = el.compexpr else: result = qlast.Path(partial=True, steps=el.expr.steps) if el.elements: result = qlast.Shape(expr=result, elements=el.elements) fake_select = qlast.SelectQuery( result=result, orderby=el.orderby, where=el.where, limit=el.limit, offset=el.offset, ) return eval(fake_select, ctx=ctx)
def _compile_alias_expr( self, expr: qlast.Base, classname: sn.QualName, schema: s_schema.Schema, context: sd.CommandContext, ) -> irast.Statement: cached: Optional[irast.Statement] = ( context.get_cached((expr, classname))) if cached is not None: return cached if not isinstance(expr, qlast.Statement): expr = qlast.SelectQuery(result=expr) existing = schema.get(classname, type=s_types.Type, default=None) if existing is not None: drop_cmd = existing.init_delta_command(schema, sd.DeleteObject) with context.suspend_dep_verification(): schema = drop_cmd.apply(schema, context) ir = qlcompiler.compile_ast_to_ir( expr, schema, options=qlcompiler.CompilerOptions( derived_target_module=classname.module, result_view_name=classname, modaliases=context.modaliases, schema_view_mode=True, in_ddl_context_name='alias definition', ), ) if ir.volatility == qltypes.Volatility.Volatile: srcctx = self.get_attribute_source_context('expr') raise errors.SchemaDefinitionError( f'volatile functions are not permitted in schema-defined ' f'computables', context=srcctx ) context.cache_value((expr, classname), ir) return ir # type: ignore
def visit_SelectStmt(self, node): result = qlast.SelectQuery() if node.where is not None: result.where = self.visit(node.where) if node.orderby: result.orderby = self.visit(node.orderby) if node.offset is not None: result.offset = self.visit(node.offset) if node.limit is not None: result.limit = self.visit(node.limit) if node.result is not None: result.result = self.visit(node.result) return result
def _compile_and_apply_delta_command(self, ctx: CompileContext, cmd) -> dbstate.BaseQuery: current_tx = ctx.state.current_tx() schema = current_tx.get_schema() context = self._new_delta_context(ctx) if current_tx.is_implicit(): if isinstance(cmd, s_deltas.CreateDelta): command = 'CREATE MIGRATION' elif isinstance(cmd, s_deltas.GetDelta): command = 'GET MIGRATION' else: command = 'COMMIT MIGRATION' raise errors.QueryError( f'{command} must be executed in a transaction block') if isinstance(cmd, s_deltas.CreateDelta): delta = None else: delta = schema.get(cmd.classname) with context(s_deltas.DeltaCommandContext(schema, cmd, delta)): if isinstance(cmd, s_deltas.CommitDelta): ddl_plan = s_delta.DeltaRoot(canonical=True) ddl_plan.update(delta.get_commands(schema)) return self._compile_and_apply_ddl_command(ctx, ddl_plan) elif isinstance(cmd, s_deltas.GetDelta): delta_ql = s_ddl.ddl_text_from_delta(schema, delta) query_ql = qlast.SelectQuery(result=qlast.StringConstant( quote="'", value=ql_quote.escape_string(delta_ql))) return self._compile_ql_query(ctx, query_ql) elif isinstance(cmd, s_deltas.CreateDelta): schema, _ = cmd.apply(schema, context) current_tx.update_schema(schema) # We must return *some* SQL; return a no-op command. return dbstate.DDLQuery(sql=(b'SELECT NULL LIMIT 0;', )) else: raise errors.InternalServerError( f'unexpected delta command: {cmd!r}') # pragma: no cover
def imprint_expr_context( qltree: qlast_.Base, modaliases: Mapping[Optional[str], str], ) -> qlast_.Base: # Imprint current module aliases as explicit # alias declarations in the expression. if (isinstance(qltree, qlast_.BaseConstant) or qltree is None or (isinstance(qltree, qlast_.Set) and not qltree.elements) or (isinstance(qltree, qlast_.Array) and all(isinstance(el, qlast_.BaseConstant) for el in qltree.elements))): # Leave constants alone. return qltree if not isinstance(qltree, qlast_.Command): assert isinstance(qltree, qlast_.Expr) qltree = qlast_.SelectQuery(result=qltree, implicit=True) else: qltree = copy.copy(qltree) qltree.aliases = ( list(qltree.aliases) if qltree.aliases is not None else None) existing_aliases: Dict[Optional[str], str] = {} for alias in (qltree.aliases or ()): if isinstance(alias, qlast_.ModuleAliasDecl): existing_aliases[alias.alias] = alias.module aliases_to_add = set(modaliases) - set(existing_aliases) for alias_name in aliases_to_add: if qltree.aliases is None: qltree.aliases = [] qltree.aliases.append( qlast_.ModuleAliasDecl( alias=alias_name, module=modaliases[alias_name], ) ) return qltree
def _compile_alias_expr( self, expr: qlast.Base, classname: sn.SchemaName, schema: s_schema.Schema, context: sd.CommandContext, ) -> irast.Statement: cached: Optional[irast.Statement] = (context.get_cached( (expr, classname))) if cached is not None: return cached if not isinstance(expr, qlast.Statement): expr = qlast.SelectQuery(result=expr) existing = schema.get(classname, type=s_types.Type, default=None) if existing is not None: drop_cmd_cls = sd.ObjectCommandMeta.get_command_class_or_die( sd.DeleteObject, type(existing), ) drop_cmd = drop_cmd_cls(classname=classname) with context.suspend_dep_verification(): schema = drop_cmd.apply(schema, context) ir = qlcompiler.compile_ast_to_ir( expr, schema, options=qlcompiler.CompilerOptions( derived_target_module=classname.module, result_view_name=classname, modaliases=context.modaliases, schema_view_mode=True, ), ) context.cache_value((expr, classname), ir) return ir # type: ignore
def _visit_query(self, node): # populate input variables with defaults, where applicable if node.variable_definitions: self.visit(node.variable_definitions) # base Query needs to be configured specially base = self._context.gqlcore.get('Query') # special treatment of the selection_set, different from inner # recursion query = qlast.SelectQuery(result=qlast.Shape(expr=qlast.Path( steps=[qlast.ObjectRef(name='Query', module='stdgraphql')]), elements=[]), limit=qlast.IntegerConstant(value='1')) self._context.fields.append({}) self._context.path.append([Step(None, base)]) query.result.elements = self.visit(node.selection_set) self._context.fields.pop() self._context.path.pop() return query
def new_set( *, stype: s_types.Type, ctx: context.ContextLevel, ircls: Type[irast.Set] = irast.Set, **kwargs: Any, ) -> irast.Set: """Create a new ir.Set instance with given attributes. Absolutely all ir.Set instances must be created using this constructor. """ if ( stype not in ctx.type_rewrites and isinstance(stype, s_objtypes.ObjectType) and ctx.env.options.apply_query_rewrites and (filters := stype.get_access_policy_filters(ctx.env.schema)) ): qry = qlast.SelectQuery( result=qlast.Path( steps=[s_utils.name_to_ast_ref(stype.get_name(ctx.env.schema))] ), ) for f in filters: assert isinstance(f.qlast, qlast.Expr) qry.where = astutils.extend_binop(qry.where, f.qlast) with ctx.detached() as subctx: subctx.expr_exposed = False # This is a global rewrite operation that is done once # per type, and so we don't really care if we're in a # temporary scope or not. subctx.path_scope = subctx.env.path_scope.root subctx.in_temp_scope = False # Put a placeholder to prevent recursion. subctx.type_rewrites[stype] = irast.Set() filtered_set = dispatch.compile(qry, ctx=subctx) assert isinstance(filtered_set, irast.Set) subctx.type_rewrites[stype] = filtered_set
def compile_arg(arg_ql: qlast.Expr, typemod: ft.TypeModifier, *, in_conditional: bool = False, ctx: context.ContextLevel) -> irast.Set: fenced = typemod is ft.TypeModifier.SetOfType optional = typemod is ft.TypeModifier.OptionalType # Create a a branch for OPTIONAL ones. The OPTIONAL branch is to # have a place to mark as optional in the scope tree. # For fenced arguments we instead wrap it in a SELECT below. new = ctx.newscope(fenced=False) if optional else ctx.new() with new as argctx: if in_conditional: argctx.in_conditional = arg_ql.context if optional: argctx.path_scope.mark_as_optional() if fenced: arg_ql = qlast.SelectQuery(result=arg_ql, context=arg_ql.context, implicit=True, rptr_passthrough=True) argctx.inhibit_implicit_limit = True arg_ir = dispatch.compile(arg_ql, ctx=argctx) if optional: pathctx.register_set_in_scope(arg_ir, optional=True, ctx=ctx) if arg_ir.path_scope_id is None: pathctx.assign_set_scope(arg_ir, argctx.path_scope, ctx=argctx) return arg_ir
def compile_DeleteQuery( expr: qlast.DeleteQuery, *, ctx: context.ContextLevel) -> irast.Set: if ctx.in_conditional is not None: raise errors.QueryError( 'DELETE statements cannot be used inside conditional expressions', context=expr.context, ) with ctx.subquery() as ictx: stmt = irast.DeleteStmt() # Expand the DELETE from sugar into full DELETE (SELECT ...) # form, if there's any additional clauses. if any([expr.where, expr.orderby, expr.offset, expr.limit]): if expr.offset or expr.limit: subjql = qlast.SelectQuery( result=qlast.SelectQuery( result=expr.subject, result_alias=expr.subject_alias, where=expr.where, orderby=expr.orderby, context=expr.context, implicit=True, ), limit=expr.limit, offset=expr.offset, context=expr.context, ) else: subjql = qlast.SelectQuery( result=expr.subject, result_alias=expr.subject_alias, where=expr.where, orderby=expr.orderby, offset=expr.offset, limit=expr.limit, context=expr.context, ) expr = qlast.DeleteQuery( aliases=expr.aliases, context=expr.context, subject=subjql, ) init_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx) # DELETE Expr is a delete(SET OF X), so we need a scope fence. with ictx.newscope(fenced=True) as scopectx: scopectx.implicit_limit = 0 subject = setgen.scoped_set( dispatch.compile(expr.subject, ctx=scopectx), ctx=scopectx) subj_type = inference.infer_type(subject, ictx.env) if not isinstance(subj_type, s_objtypes.ObjectType): raise errors.QueryError( f'cannot delete non-ObjectType objects', context=expr.subject.context ) with ictx.new() as bodyctx: bodyctx.implicit_id_in_shapes = False bodyctx.implicit_tid_in_shapes = False stmt.subject = compile_query_subject( subject, shape=None, ctx=bodyctx) stmt_subject_stype = setgen.get_set_type(subject, ctx=ictx) result = setgen.class_set( schemactx.get_material_type(stmt_subject_stype, ctx=ctx), path_id=stmt.subject.path_id, ctx=ctx, ) with ictx.new() as resultctx: if ictx.stmt is ctx.toplevel_stmt: resultctx.expr_exposed = True stmt.result = compile_query_subject( result, view_scls=ctx.view_scls, view_name=ctx.toplevel_result_view_name, compile_views=ictx.stmt is ictx.toplevel_stmt, ctx=resultctx, ) result = fini_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx) return result
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_objtypes.ObjectType, *, path_id: irast.PathId, path_id_namespace: Optional[irast.WeakNamespace]=None, is_insert: bool=False, is_update: bool=False, from_default: bool=False, view_rptr: Optional[context.ViewRPtr]=None, ctx: context.ContextLevel) -> s_pointers.Pointer: steps = shape_el.expr.steps is_linkprop = False is_polymorphic = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. plen = len(steps) ptrsource: s_sources.Source = view_scls qlexpr: Optional[qlast.Expr] = None target_typexpr = None source: qlast.Base base_ptrcls_is_alias = False if plen >= 2 and isinstance(steps[-1], qlast.TypeIntersection): # Target type intersection: foo: Type target_typexpr = steps[-1].type plen -= 1 steps = steps[:-1] if plen == 1: # regular shape lexpr = steps[0] assert isinstance(lexpr, qlast.Ptr) is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None or view_rptr.ptrcls is None: raise errors.QueryError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) assert isinstance(view_rptr.ptrcls, s_links.Link) ptrsource = view_rptr.ptrcls source = qlast.Source() elif plen == 2 and isinstance(steps[0], qlast.TypeIntersection): # Source type intersection: [IS Type].foo source = qlast.Path(steps=[ qlast.Source(), steps[0], ]) lexpr = steps[1] ptype = steps[0].type if not isinstance(ptype, qlast.TypeName): raise errors.QueryError( 'complex type expressions are not supported here', context=ptype.context, ) source_spec = schemactx.get_schema_type(ptype.maintype, ctx=ctx) if not isinstance(source_spec, s_objtypes.ObjectType): raise errors.QueryError( f'expected object type, got ' f'{source_spec.get_verbosename(ctx.env.schema)}', context=ptype.context, ) ptrsource = source_spec is_polymorphic = True else: # pragma: no cover raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') assert isinstance(lexpr, qlast.Ptr) ptrname = lexpr.ptr.name compexpr: Optional[qlast.Expr] = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Short shape form in INSERT, e.g # INSERT Foo { bar: Spam { name := 'name' }} # is prohibited. raise errors.EdgeQLSyntaxError( "unexpected ':'", context=steps[-1].context) ptrcls: Optional[s_pointers.Pointer] if compexpr is None: ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=lexpr, ctx=ctx) if is_polymorphic: ptrcls = schemactx.derive_ptr( ptrcls, view_scls, is_insert=is_insert, is_update=is_update, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema) base_ptr_is_computable = base_ptrcls in ctx.source_map ptr_name = sn.QualName( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx) base_is_singleton = False if base_cardinality is not None and base_cardinality.is_known(): base_is_singleton = base_cardinality.is_single() if ( shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic or target_typexpr is not None or (ctx.implicit_limit and not base_is_singleton) ): if target_typexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) else: qlexpr = qlast.Path(steps=[ source, lexpr, qlast.TypeIntersection(type=target_typexpr), ]) qlexpr = astutils.ensure_qlstmt(qlexpr) assert isinstance(qlexpr, qlast.SelectQuery) qlexpr.where = shape_el.where qlexpr.orderby = shape_el.orderby if shape_el.offset or shape_el.limit: qlexpr = qlast.SelectQuery(result=qlexpr, implicit=True) qlexpr.offset = shape_el.offset qlexpr.limit = shape_el.limit if ( (ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt) and not qlexpr.limit and ctx.implicit_limit and not base_is_singleton ): qlexpr.limit = qlast.IntegerConstant( value=str(ctx.implicit_limit), ) if target_typexpr is not None: assert isinstance(target_typexpr, qlast.TypeName) intersector_type = schemactx.get_schema_type( target_typexpr.maintype, ctx=ctx) int_result = schemactx.apply_intersection( ptrcls.get_target(ctx.env.schema), # type: ignore intersector_type, ctx=ctx, ) ptr_target = int_result.stype else: _ptr_target = ptrcls.get_target(ctx.env.schema) assert _ptr_target ptr_target = _ptr_target ptr_cardinality = base_cardinality if ptr_cardinality is None or not ptr_cardinality.is_known(): # We do not know the parent's pointer cardinality yet. ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls) ctx.env.pointer_specified_info[ptrcls] = ( shape_el.cardinality, shape_el.required, shape_el.context) implicit_tid = has_implicit_type_computables( ptr_target, is_mutation=is_mutation, ctx=ctx, ) if shape_el.elements or implicit_tid: sub_view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, is_insert=is_insert, is_update=is_update) sub_path_id = pathctx.extend_path_id( path_id, ptrcls=base_ptrcls, ns=ctx.path_id_namespace, ctx=ctx) ctx.path_scope.attach_path(sub_path_id, context=shape_el.context) if not isinstance(ptr_target, s_objtypes.ObjectType): raise errors.QueryError( f'shapes cannot be applied to ' f'{ptr_target.get_verbosename(ctx.env.schema)}', context=shape_el.context, ) if is_update: for subel in shape_el.elements or []: is_prop = ( isinstance(subel.expr.steps[0], qlast.Ptr) and subel.expr.steps[0].type == 'property' ) if not is_prop: raise errors.QueryError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view( stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, parser_context=shape_el.context, ctx=ctx) else: ptr_target = _process_view( stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, parser_context=shape_el.context, ctx=ctx) else: base_ptrcls = ptrcls = None if (is_mutation and ptrname not in ctx.special_computables_in_mutation_shape): # If this is a mutation, the pointer must exist. ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=lexpr, ctx=ctx) base_ptrcls = ptrcls.get_bases( ctx.env.schema).first(ctx.env.schema) ptr_name = sn.QualName( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) else: ptr_name = sn.QualName( module='__', name=ptrname, ) try: ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=False, ctx=ctx, ) base_ptrcls = ptrcls.get_bases( ctx.env.schema).first(ctx.env.schema) except errors.InvalidReferenceError: # This is a NEW computable pointer, it's fine. pass qlexpr = astutils.ensure_qlstmt(compexpr) if ((ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt) and ctx.implicit_limit and isinstance(qlexpr, qlast.OffsetLimitMixin) and not qlexpr.limit): qlexpr.limit = qlast.IntegerConstant(value=str(ctx.implicit_limit)) with ctx.newscope(fenced=True) as shape_expr_ctx: # Put current pointer class in context, so # that references to link properties in sub-SELECT # can be resolved. This is necessary for proper # evaluation of link properties on computable links, # most importantly, in INSERT/UPDATE context. shape_expr_ctx.view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, ptrcls_name=ptr_name, ptrcls_is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update, ) shape_expr_ctx.defining_view = view_scls shape_expr_ctx.path_scope.unnest_fence = True shape_expr_ctx.partial_path_prefix = setgen.class_set( view_scls.get_bases(ctx.env.schema).first(ctx.env.schema), path_id=path_id, ctx=shape_expr_ctx) prefix_rptrref = path_id.rptr() if prefix_rptrref is not None: # Source path seems to contain multiple steps, # so set up a rptr for abbreviated link property # paths. src_path_id = path_id.src_path() assert src_path_id is not None ctx.env.schema, src_t = irtyputils.ir_typeref_to_type( shape_expr_ctx.env.schema, src_path_id.target, ) prefix_rptr = irast.Pointer( source=setgen.class_set( src_t, path_id=src_path_id, ctx=shape_expr_ctx, ), target=shape_expr_ctx.partial_path_prefix, ptrref=prefix_rptrref, direction=s_pointers.PointerDirection.Outbound, ) shape_expr_ctx.partial_path_prefix.rptr = prefix_rptr if is_mutation and ptrcls is not None: shape_expr_ctx.expr_exposed = True shape_expr_ctx.empty_result_type_hint = \ ptrcls.get_target(ctx.env.schema) shape_expr_ctx.stmt_metadata[qlexpr] = context.StatementMetadata( iterator_target=True, ) irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) if ( shape_el.operation.op is qlast.ShapeOp.APPEND or shape_el.operation.op is qlast.ShapeOp.SUBTRACT ): if not is_update: op = ( '+=' if shape_el.operation.op is qlast.ShapeOp.APPEND else '-=' ) raise errors.EdgeQLSyntaxError( f"unexpected '{op}'", context=shape_el.operation.context, ) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = shape_expr_ctx.view_rptr.base_ptrcls base_ptrcls_is_alias = shape_expr_ctx.view_rptr.ptrcls_is_alias if ptrcls is not None: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'owned', True) ptr_cardinality = None ptr_target = inference.infer_type(irexpr, ctx.env) if ( isinstance(ptr_target, s_types.Collection) and not ctx.env.orig_schema.get_by_id(ptr_target.id, default=None) ): # Record references to implicitly defined collection types, # so that the alias delta machinery can pick them up. ctx.env.created_schema_objects.add(ptr_target) anytype = ptr_target.find_any(ctx.env.schema) if anytype is not None: raise errors.QueryError( 'expression returns value of indeterminate type', context=ctx.env.type_origins.get(anytype), ) # Validate that the insert/update expression is # of the correct class. if is_mutation and ptrcls is not None: base_target = ptrcls.get_target(ctx.env.schema) assert base_target is not None if ptr_target.assignment_castable_to( base_target, schema=ctx.env.schema): # Force assignment casts if the target type is not a # subclass of the base type and the cast is not to an # object type. if not ( base_target.is_object_type() or s_types.is_type_compatible( base_target, ptr_target, schema=ctx.env.schema ) ): qlexpr = astutils.ensure_qlstmt(qlast.TypeCast( type=typegen.type_to_ql_typeref(base_target, ctx=ctx), expr=compexpr, )) ptr_target = base_target else: expected = [ repr(str(base_target.get_displayname(ctx.env.schema))) ] ercls: Type[errors.EdgeDBError] if ptrcls.is_property(ctx.env.schema): ercls = errors.InvalidPropertyTargetError else: ercls = errors.InvalidLinkTargetError ptr_vn = ptrcls.get_verbosename(ctx.env.schema, with_parent=True) raise ercls( f'invalid target for {ptr_vn}: ' f'{str(ptr_target.get_displayname(ctx.env.schema))!r} ' f'(expecting {" or ".join(expected)})' ) if qlexpr is not None or ptrcls is None: src_scls: s_sources.Source if is_linkprop: # Proper checking was done when is_linkprop is defined. assert view_rptr is not None assert isinstance(view_rptr.ptrcls, s_links.Link) src_scls = view_rptr.ptrcls else: src_scls = view_scls if ptr_target.is_object_type(): base = ctx.env.get_track_schema_object( sn.QualName('std', 'link'), expr=None) else: base = ctx.env.get_track_schema_object( sn.QualName('std', 'property'), expr=None) if base_ptrcls is not None: derive_from = base_ptrcls else: derive_from = base derived_name = schemactx.derive_view_name( base_ptrcls, derived_name_base=ptr_name, derived_name_quals=[str(src_scls.get_name(ctx.env.schema))], ctx=ctx, ) existing = ctx.env.schema.get( derived_name, default=None, type=s_pointers.Pointer) if existing is not None: existing_target = existing.get_target(ctx.env.schema) assert existing_target is not None if ctx.recompiling_schema_alias: ptr_cardinality = existing.get_cardinality(ctx.env.schema) if ptr_target == existing_target: ptrcls = existing elif ptr_target.implicitly_castable_to( existing_target, ctx.env.schema): ctx.env.schema = existing.set_target( ctx.env.schema, ptr_target) ptrcls = existing else: vnp = existing.get_verbosename( ctx.env.schema, with_parent=True) t1_vn = existing_target.get_verbosename(ctx.env.schema) t2_vn = ptr_target.get_verbosename(ctx.env.schema) if compexpr is not None: source_context = compexpr.context else: source_context = shape_el.expr.steps[-1].context raise errors.SchemaError( f'cannot redefine {vnp} as {t2_vn}', details=f'{vnp} is defined as {t1_vn}', context=source_context, ) else: ptrcls = schemactx.derive_ptr( derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, ctx=ctx) elif ptrcls.get_target(ctx.env.schema) != ptr_target: ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target) assert ptrcls is not None if qlexpr is None: # This is not a computable, just a pointer # to a nested shape. Have it reuse the original # pointer name so that in `Foo.ptr.name` and # `Foo { ptr: {name}}` are the same path. path_id_name = base_ptrcls.get_name(ctx.env.schema) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'path_id_name', path_id_name ) if qlexpr is not None: ctx.source_map[ptrcls] = irast.ComputableInfo( qlexpr=qlexpr, context=ctx, path_id=path_id, path_id_ns=path_id_namespace, shape_op=shape_el.operation.op, ) if compexpr is not None or is_polymorphic: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'computable', True, ) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'owned', True, ) if ptr_cardinality is not None: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'cardinality', ptr_cardinality) else: if qlexpr is None and ptrcls is not base_ptrcls: ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls) base_cardinality = None base_required = False if base_ptrcls is not None and not base_ptrcls_is_alias: base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx) base_required = base_ptrcls.get_required(ctx.env.schema) if base_cardinality is None or not base_cardinality.is_known(): specified_cardinality = shape_el.cardinality specified_required = shape_el.required else: specified_cardinality = base_cardinality specified_required = base_required if (shape_el.cardinality is not None and base_ptrcls is not None and shape_el.cardinality != base_cardinality): base_src = base_ptrcls.get_source(ctx.env.schema) assert base_src is not None base_src_name = base_src.get_verbosename(ctx.env.schema) raise errors.SchemaError( f'cannot redefine the cardinality of ' f'{ptrcls.get_verbosename(ctx.env.schema)}: ' f'it is defined as {base_cardinality.as_ptr_qual()!r} ' f'in the base {base_src_name}', context=compexpr and compexpr.context, ) # The required flag may be inherited from the base specified_required = shape_el.required or base_required ctx.env.pointer_specified_info[ptrcls] = ( specified_cardinality, specified_required, shape_el.context) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'cardinality', qltypes.SchemaCardinality.Unknown) if ( ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None and not from_default and not ctx.env.options.allow_writing_protected_pointers ): ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) if is_polymorphic: msg = (f'cannot access {ptrcls_sn.name} on a polymorphic ' f'shape element') else: msg = f'cannot assign to {ptrcls_sn.name}' raise errors.QueryError(msg, context=shape_el.context) if is_update and ptrcls.get_readonly(ctx.env.schema): raise errors.QueryError( f'cannot update {ptrcls.get_verbosename(ctx.env.schema)}: ' f'it is declared as read-only', context=compexpr and compexpr.context, ) return ptrcls
def _cast_array(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: assert isinstance(orig_stype, s_types.Array) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) if direct_cast is None: if not new_stype.is_array(): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}', context=srcctx) assert isinstance(new_stype, s_types.Array) el_type = new_stype.get_subtypes(ctx.env.schema)[0] else: el_type = new_stype orig_el_type = orig_stype.get_subtypes(ctx.env.schema)[0] el_cast = _find_cast(orig_el_type, el_type, srcctx=srcctx, ctx=ctx) if el_cast is not None and el_cast.get_from_cast(ctx.env.schema): # Simple cast return _cast_to_ir(ir_set, el_cast, orig_stype, new_stype, ctx=ctx) else: pathctx.register_set_in_scope(ir_set, ctx=ctx) with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() source_alias = subctx.aliases.get('a') subctx.anchors[source_alias] = ir_set unpacked = qlast.FunctionCall( func=('__std__', 'array_unpack'), args=[ qlast.Path(steps=[qlast.ObjectRef(name=source_alias)], ), ], ) enumerated = setgen.ensure_set( dispatch.compile( qlast.FunctionCall( func=('__std__', 'enumerate'), args=[unpacked], ), ctx=subctx, ), ctx=subctx, ) enumerated_alias = subctx.aliases.get('e') subctx.anchors[enumerated_alias] = enumerated enumerated_ref = qlast.Path( steps=[qlast.ObjectRef(name=enumerated_alias)], ) elements = qlast.FunctionCall( func=('__std__', 'array_agg'), args=[ qlast.SelectQuery( result=qlast.TypeCast( expr=qlast.Path(steps=[ enumerated_ref, qlast.Ptr(ptr=qlast.ObjectRef( name='1', direction='>', ), ), ], ), type=typegen.type_to_ql_typeref( el_type, ctx=subctx, ), cardinality_mod=qlast.CardinalityModifier.Required, ), orderby=[ qlast.SortExpr( path=qlast.Path(steps=[ enumerated_ref, qlast.Ptr(ptr=qlast.ObjectRef( name='0', direction='>', ), ), ], ), direction=qlast.SortOrder.Asc, ), ], ), ], ) array_ir = dispatch.compile(elements, ctx=subctx) assert isinstance(array_ir, irast.Set) if direct_cast is not None: ctx.env.schema, array_stype = s_types.Array.from_subtypes( ctx.env.schema, [el_type]) return _cast_to_ir(array_ir, direct_cast, array_stype, new_stype, ctx=ctx) else: return array_ir
rhs = qlutils.subject_substitute(subjectexpr.qlast, insert_subject) conds.append(qlast.BinOp(op='=', left=lhs, right=rhs)) # We use `any` to compute the disjunction here because some might # be empty. if len(conds) == 1: cond = conds[0] else: cond = qlast.FunctionCall( func='any', args=[qlast.Set(elements=conds)], ) # Produce a query that finds the conflicting objects select_ast = qlast.SelectQuery( result=insert_subject, where=cond, ) select_ir = dispatch.compile(select_ast, ctx=ctx) select_ir = setgen.scoped_set( select_ir, force_reassign=True, ctx=ctx) assert isinstance(select_ir, irast.Set) return select_ir def compile_insert_unless_conflict( stmt: irast.InsertStmt, insert_subject: qlast.Path, *, ctx: context.ContextLevel, ) -> irast.OnConflictClause:
def _handle_view_op(cls, schema, cmd, astnode, context): view_expr = cls._maybe_get_view_expr(astnode) if view_expr is not None: if not isinstance(view_expr, qlast.Statement): view_expr = qlast.SelectQuery(result=view_expr) existing_aliases = {} for alias in view_expr.aliases: if isinstance(alias, qlast.ModuleAliasDecl): existing_aliases[alias.alias] = alias.module aliases_to_add = set(context.modaliases) - set(existing_aliases) for alias in aliases_to_add: view_expr.aliases.append( qlast.ModuleAliasDecl( alias=alias, module=context.modaliases[alias], )) expr_ql = qlcodegen.generate_source(view_expr, pretty=False) cmd.set_attribute_value('expr', s_expr.Expression(text=expr_ql)) ir = cls._compile_view_expr(view_expr, cmd.classname, schema, context) view_types = ir.views.values() if isinstance(astnode, qlast.AlterObjectType): prev = schema.get(cmd.classname) prev_ir = cls._compile_view_expr(prev.expr, cmd.classname, schema, context) prev_view_types = prev_ir.views.values() else: prev_ir = None prev_view_types = [] derived_delta = sd.DeltaRoot() new_schema = ir.schema old_schema = prev_ir.schema if prev_ir is not None else None adds_mods, dels = so.Object._delta_sets(prev_view_types, view_types, old_schema=old_schema, new_schema=new_schema) derived_delta.update(adds_mods) derived_delta.update(dels) if ir.stype.is_view(ir.schema): for op in list(derived_delta.get_subcommands()): if op.classname == cmd.classname: for subop in op.get_subcommands(): if isinstance(subop, sd.AlterObjectProperty): cmd.discard_attribute(subop.property) cmd.add(subop) derived_delta.discard(op) cmd.update(derived_delta.get_subcommands()) cmd.discard_attribute('view_type') cmd.add( sd.AlterObjectProperty(property='view_type', new_value=s_types.ViewType.Select)) return cmd
# For the result filtering we need to *ignore* the same object if fake_dml_set: anchor = qlutils.subject_paths_substitute( ptr_anchors['id'], ptr_anchors) ptr_val = qlast.Path(partial=True, steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name='id')) ]) cond = qlast.BinOp( op='AND', left=cond, right=qlast.BinOp(op='!=', left=anchor, right=ptr_val), ) # Produce a query that finds the conflicting objects select_ast = qlast.DetachedExpr( expr=qlast.SelectQuery(result=insert_subject, where=cond) ) return select_ast def _constr_matters( constr: s_constr.Constraint, ctx: context.ContextLevel, ) -> bool: schema = ctx.env.schema return ( not constr.generic(schema) and not constr.get_delegated(schema) and ( constr.get_owned(schema) or all(anc.get_delegated(schema) or anc.generic(schema) for anc
def compile_insert_unless_conflict( stmt: irast.InsertStmt, insert_subject: qlast.Path, constraint_spec: qlast.Expr, else_branch: Optional[qlast.Expr], *, ctx: context.ContextLevel, ) -> irast.OnConflictClause: with ctx.new() as constraint_ctx: constraint_ctx.partial_path_prefix = stmt.subject # We compile the name here so we can analyze it, but we don't do # anything else with it. cspec_res = setgen.ensure_set(dispatch.compile(constraint_spec, ctx=constraint_ctx), ctx=constraint_ctx) if not cspec_res.rptr: raise errors.QueryError( 'ON CONFLICT argument must be a property', context=constraint_spec.context, ) if cspec_res.rptr.source.path_id != stmt.subject.path_id: raise errors.QueryError( 'ON CONFLICT argument must be a property of the ' 'type being inserted', context=constraint_spec.context, ) schema = ctx.env.schema schema, ptr = (typeutils.ptrcls_from_ptrref(cspec_res.rptr.ptrref, schema=schema)) if not isinstance(ptr, s_pointers.Pointer): raise errors.QueryError( 'ON CONFLICT property must be a property', context=constraint_spec.context, ) ptr = ptr.get_nearest_non_derived_parent(schema) if ptr.get_cardinality(schema) != qltypes.SchemaCardinality.ONE: raise errors.QueryError( 'ON CONFLICT property must be a SINGLE property', context=constraint_spec.context, ) exclusive_constr: s_constr.Constraint = schema.get('std::exclusive') ex_cnstrs = [ c for c in ptr.get_constraints(schema).objects(schema) if c.issubclass(schema, exclusive_constr) ] if len(ex_cnstrs) != 1: raise errors.QueryError( 'ON CONFLICT property must have a single exclusive constraint', context=constraint_spec.context, ) module_id = schema.get_global(s_mod.Module, ptr.get_name(schema).module).id field_name = cspec_res.rptr.ptrref.shortname # Find the IR corresponding to our field # FIXME: Is there a better way to do this? for elem, _ in stmt.subject.shape: if elem.rptr.ptrref.shortname == field_name: key = elem.expr break else: raise errors.QueryError( 'INSERT ON CONFLICT property requires matching shape', context=constraint_spec.context, ) # FIXME: This reuse of the source ctx.anchors = ctx.anchors.copy() source_alias = ctx.aliases.get('a') ctx.anchors[source_alias] = setgen.ensure_set(key, ctx=ctx) anchor = qlast.Path(steps=[qlast.ObjectRef(name=source_alias)]) ctx.env.schema = schema # Compile an else branch else_info = None if else_branch: # Produce a query that finds the conflicting objects nobe = qlast.SelectQuery( result=insert_subject, where=qlast.BinOp(op='=', left=constraint_spec, right=anchor), ) select_ir = dispatch.compile(nobe, ctx=ctx) select_ir = setgen.scoped_set(select_ir, force_reassign=True, ctx=ctx) assert isinstance(select_ir, irast.Set) # The ELSE needs to be able to reference the subject in an # UPDATE, even though that would normally be prohibited. ctx.path_scope.factoring_allowlist.add(stmt.subject.path_id) # Compile else else_ir = dispatch.compile(astutils.ensure_qlstmt(else_branch), ctx=ctx) assert isinstance(else_ir, irast.Set) else_info = irast.OnConflictElse(select_ir, else_ir) return irast.OnConflictClause( irast.ConstraintRef(id=ex_cnstrs[0].id, module_id=module_id), else_info)
def _normalize_view_ptr_expr(shape_el: qlast.ShapeElement, view_scls: s_types.Type, *, path_id: irast.PathId, path_id_namespace: Optional[ irast.WeakNamespace] = None, is_insert: bool = False, is_update: bool = False, view_rptr: Optional[context.ViewRPtr] = None, ctx: context.ContextLevel) -> s_pointers.Pointer: steps = shape_el.expr.steps is_linkprop = False is_polymorphic = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. plen = len(steps) ptrsource = view_scls qlexpr = None target_typexpr = None source: qlast.Base if plen >= 2 and isinstance(steps[-1], qlast.TypeIndirection): # Target type indirection: foo: Type target_typexpr = steps[-1].type plen -= 1 steps = steps[:-1] if plen == 1: # regular shape lexpr = steps[0] assert isinstance(lexpr, qlast.Ptr) is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None: raise errors.QueryError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) ptrsource = view_rptr.ptrcls source = qlast.Source() elif plen == 2 and isinstance(steps[0], qlast.TypeIndirection): # Source type indirection: [IS Type].foo source = qlast.Path(steps=[ qlast.Source(), steps[0], ]) lexpr = steps[1] ptype = steps[0].type if not isinstance(ptype, qlast.TypeName): raise errors.QueryError( 'complex type expressions are not supported here', context=ptype.context, ) ptrsource = schemactx.get_schema_type(ptype.maintype, ctx=ctx) is_polymorphic = True else: # pragma: no cover raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') assert isinstance(lexpr, qlast.Ptr) ptrname = lexpr.ptr.name compexpr = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Short shape form in INSERT, e.g # INSERT Foo { bar: Spam { name := 'name' }} # is prohibited. raise errors.EdgeQLSyntaxError("unexpected ':'", context=steps[-1].context) if compexpr is None: ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx) if is_polymorphic: ptrcls = schemactx.derive_ptr(ptrcls, view_scls, is_insert=is_insert, is_update=is_update, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema) base_ptr_is_computable = base_ptrcls in ctx.source_map ptr_name = sn.Name( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) if (shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic or target_typexpr is not None): if target_typexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) else: qlexpr = qlast.Path(steps=[ source, lexpr, qlast.TypeIndirection(type=target_typexpr), ]) qlexpr = astutils.ensure_qlstmt(qlexpr) qlexpr.where = shape_el.where qlexpr.orderby = shape_el.orderby if shape_el.offset or shape_el.limit: qlexpr = qlast.SelectQuery(result=qlexpr, implicit=True) qlexpr.offset = shape_el.offset qlexpr.limit = shape_el.limit if target_typexpr is not None: ptr_target = schemactx.get_schema_type(target_typexpr.maintype, ctx=ctx) else: ptr_target = ptrcls.get_target(ctx.env.schema) if base_ptrcls in ctx.pending_cardinality: # We do not know the parent's pointer cardinality yet. ptr_cardinality = None ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) stmtctx.pend_pointer_cardinality_inference( ptrcls=ptrcls, specified_card=shape_el.cardinality, source_ctx=shape_el.context, ctx=ctx) else: ptr_cardinality = base_ptrcls.get_cardinality(ctx.env.schema) implicit_tid = has_implicit_tid( ptr_target, is_mutation=is_mutation, ctx=ctx, ) if shape_el.elements or implicit_tid: sub_view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, is_insert=is_insert, is_update=is_update) sub_path_id = pathctx.extend_path_id(path_id, ptrcls=base_ptrcls, target=ptrcls.get_target( ctx.env.schema), ns=ctx.path_id_namespace, ctx=ctx) ctx.path_scope.attach_path(sub_path_id) if is_update: for subel in shape_el.elements or []: is_prop = (isinstance(subel.expr.steps[0], qlast.Ptr) and subel.expr.steps[0].type == 'property') if not is_prop: raise errors.QueryError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view(stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, ctx=ctx) else: ptr_target = _process_view(stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, ctx=ctx) else: base_ptrcls = ptrcls = None if (is_mutation and ptrname not in ctx.special_computables_in_mutation_shape): # If this is a mutation, the pointer must exist. ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first( ctx.env.schema) ptr_name = sn.Name( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) else: # Otherwise, assume no pointer inheritance. # Every computable is a new pointer derived from # std::link or std::property. There is one exception: # pointer aliases (Foo {some := Foo.other}), where `foo` # gets derived from `Foo.other`. This logic is applied # in compile_query_subject() by populating the base_ptrcls. ptr_name = sn.Name( module='__', name=ptrname, ) qlexpr = astutils.ensure_qlstmt(compexpr) with ctx.newscope(fenced=True) as shape_expr_ctx: # Put current pointer class in context, so # that references to link properties in sub-SELECT # can be resolved. This is necessary for proper # evaluation of link properties on computable links, # most importantly, in INSERT/UPDATE context. shape_expr_ctx.view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, ptrcls_name=ptr_name, ptrcls_is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update) shape_expr_ctx.defining_view = True shape_expr_ctx.path_scope.unnest_fence = True shape_expr_ctx.partial_path_prefix = setgen.class_set( view_scls, path_id=path_id, ctx=shape_expr_ctx) prefix_rptrref = path_id.rptr() if prefix_rptrref is not None: # Source path seems to contain multiple steps, # so set up a rptr for abbreviated link property # paths. src_path_id = path_id.src_path() prefix_rptr = irast.Pointer( source=setgen.class_set( irtyputils.ir_typeref_to_type( shape_expr_ctx.env.schema, src_path_id.target, ), path_id=src_path_id, ctx=shape_expr_ctx, ), target=shape_expr_ctx.partial_path_prefix, ptrref=prefix_rptrref, direction=s_pointers.PointerDirection.Outbound, ) shape_expr_ctx.partial_path_prefix.rptr = prefix_rptr if is_mutation and ptrcls is not None: shape_expr_ctx.expr_exposed = True shape_expr_ctx.empty_result_type_hint = \ ptrcls.get_target(ctx.env.schema) irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = shape_expr_ctx.view_rptr.base_ptrcls ptr_cardinality = None ptr_target = inference.infer_type(irexpr, ctx.env) anytype = ptr_target.find_any(ctx.env.schema) if anytype is not None: raise errors.QueryError( 'expression returns value of indeterminate type', context=ctx.env.type_origins.get(anytype), ) # Validate that the insert/update expression is # of the correct class. if is_mutation and ptrcls is not None: base_target = ptrcls.get_target(ctx.env.schema) assert base_target is not None if ptr_target.assignment_castable_to(base_target, schema=ctx.env.schema): # Force assignment casts if the target type is not a # subclass of the base type and the cast is not to an # object type. if not (base_target.is_object_type() or ptr_target.issubclass(ctx.env.schema, base_target)): qlexpr = astutils.ensure_qlstmt( qlast.TypeCast( type=astutils.type_to_ql_typeref( base_target, schema=ctx.env.schema), expr=compexpr, )) ptr_target = base_target else: expected = [ repr(str(base_target.get_displayname(ctx.env.schema))) ] ercls: Type[errors.EdgeDBError] if ptrcls.is_property(ctx.env.schema): ercls = errors.InvalidPropertyTargetError else: ercls = errors.InvalidLinkTargetError ptr_vn = ptrcls.get_verbosename(ctx.env.schema, with_parent=True) raise ercls( f'invalid target for {ptr_vn}: ' f'{str(ptr_target.get_displayname(ctx.env.schema))!r} ' f'(expecting {" or ".join(expected)})') if qlexpr is not None or ptrcls is None: if is_linkprop: # Proper checking was done when is_linkprop is defined. assert view_rptr is not None src_scls = view_rptr.ptrcls else: src_scls = view_scls if ptr_target.is_object_type(): base = ctx.env.get_track_schema_object('std::link') else: base = ctx.env.get_track_schema_object('std::property') if base_ptrcls is not None: derive_from = base_ptrcls else: derive_from = base derived_name = schemactx.derive_view_name( base_ptrcls, derived_name_base=ptr_name, derived_name_quals=[src_scls.get_name(ctx.env.schema)], ctx=ctx) existing = ctx.env.schema.get(derived_name, None) if existing is not None: existing_target = existing.get_target(ctx.env.schema) if ptr_target == existing_target: ptrcls = existing elif ptr_target.implicitly_castable_to(existing_target, ctx.env.schema): ctx.env.schema = existing.set_target(ctx.env.schema, ptr_target) ptrcls = existing else: target_rptr_set = (ptr_target.get_rptr(ctx.env.schema) is not None) if target_rptr_set: ctx.env.schema = ptr_target.set_field_value( ctx.env.schema, 'rptr', None, ) ctx.env.schema = existing.delete(ctx.env.schema) ptrcls = schemactx.derive_ptr(derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, inheritance_merge=False, ctx=ctx) if target_rptr_set: ctx.env.schema = ptr_target.set_field_value( ctx.env.schema, 'rptr', ptrcls, ) else: ptrcls = schemactx.derive_ptr(derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, ctx=ctx) elif ptrcls.get_target(ctx.env.schema) != ptr_target: ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target) assert ptrcls is not None if qlexpr is None: # This is not a computable, just a pointer # to a nested shape. Have it reuse the original # pointer name so that in `Foo.ptr.name` and # `Foo { ptr: {name}}` are the same path. path_id_name = base_ptrcls.get_name(ctx.env.schema) ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'path_id_name', path_id_name) if qlexpr is not None: ctx.source_map[ptrcls] = (qlexpr, ctx, path_id, path_id_namespace) if not is_mutation: if ptr_cardinality is None: if qlexpr is None and ptrcls is not base_ptrcls: ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) stmtctx.pend_pointer_cardinality_inference( ptrcls=ptrcls, specified_card=shape_el.cardinality, source_ctx=shape_el.context, ctx=ctx) ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'cardinality', None) else: ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'cardinality', ptr_cardinality) if ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) if is_polymorphic: msg = (f'cannot access {ptrcls_sn.name} on a polymorphic ' f'shape element') else: msg = f'cannot assign to {ptrcls_sn.name}' raise errors.QueryError(msg, context=shape_el.context) return ptrcls
def _cast_array( ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: assert isinstance(orig_stype, s_types.Array) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) if direct_cast is None: if not new_stype.is_array(): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}', context=srcctx) assert isinstance(new_stype, s_types.Array) el_type = new_stype.get_subtypes(ctx.env.schema)[0] else: el_type = new_stype orig_el_type = orig_stype.get_subtypes(ctx.env.schema)[0] el_cast = _find_cast(orig_el_type, el_type, srcctx=srcctx, ctx=ctx) if el_cast is not None and el_cast.get_from_cast(ctx.env.schema): # Simple cast return _cast_to_ir( ir_set, el_cast, orig_stype, new_stype, ctx=ctx) else: with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() source_path = subctx.create_anchor(ir_set, 'a') unpacked = qlast.FunctionCall( func=('__std__', 'array_unpack'), args=[source_path], ) enumerated = dispatch.compile( qlast.FunctionCall( func=('__std__', 'enumerate'), args=[unpacked], ), ctx=subctx, ) enumerated_ref = subctx.create_anchor(enumerated, 'e') elements = qlast.FunctionCall( func=('__std__', 'array_agg'), args=[ qlast.SelectQuery( result=qlast.TypeCast( expr=astutils.extend_path(enumerated_ref, '1'), type=typegen.type_to_ql_typeref( el_type, ctx=subctx, ), cardinality_mod=qlast.CardinalityModifier.Required, ), orderby=[ qlast.SortExpr( path=astutils.extend_path(enumerated_ref, '0'), direction=qlast.SortOrder.Asc, ), ], ), ], ) # Force the elements to be correlated with whatever the # anchor was. (Doing it this way ensures a NULL check, # and just registering it in the scope would not.) correlated_elements = astutils.extend_path( qlast.Tuple(elements=[source_path, elements]), '1' ) if el_type.contains_json(subctx.env.schema): subctx.inhibit_implicit_limit = True array_ir = dispatch.compile(correlated_elements, ctx=subctx) assert isinstance(array_ir, irast.Set) if direct_cast is not None: ctx.env.schema, array_stype = s_types.Array.from_subtypes( ctx.env.schema, [el_type]) return _cast_to_ir( array_ir, direct_cast, array_stype, new_stype, ctx=ctx ) else: return array_ir