def reduce_NodeName_OptShape(self, *kids): self.val = qlast.Shape(expr=qlast.Path(steps=[ qlast.ObjectRef(name=kids[0].val.name, module=kids[0].val.module, context=kids[0].context) ]), elements=kids[1].val)
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 make_free_object(els: Dict[str, qlast.Expr]) -> qlast.Shape: return qlast.Shape( expr=None, elements=[ qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr(ptr=qlast.ObjectRef(name=name))]), compexpr=expr ) for name, expr in els.items() ], )
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_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 visit_Set(self, node): if node.expr is not None: result = self.visit(node.expr) else: result = self._visit_path(node) if node.shape: result = qlast.Shape(expr=result, elements=[]) for el in node.shape: rptr = el.rptr ptrref = rptr.ptrref pn = ptrref.shortname pn = qlast.ShapeElement(expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=pn.name), direction=rptr.direction) ])) result.elements.append(pn) return result
def reduce_Expr_Shape(self, *kids): self.val = qlast.Shape(expr=kids[0].val, elements=kids[1].val)
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_objtypes.ObjectType, *, path_id: irast.PathId, path_id_namespace: Optional[irast.Namespace]=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 materialized = None # 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 = [] base_ptrcls_is_alias = False irexpr = None 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 elif plen == 2 and isinstance(steps[0], qlast.TypeIntersection): # Source type intersection: [IS Type].foo 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 or is_update) and shape_el.elements: # Short shape form in INSERT or UPDATE, 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_required = base_ptrcls.get_required(ctx.env.schema) 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() is_nontrivial = astutils.is_nontrivial_shape_element(shape_el) if ( is_nontrivial 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], partial=True) else: qlexpr = qlast.Path(steps=[ *source, lexpr, qlast.TypeIntersection(type=target_typexpr), ], partial=True) if shape_el.elements: qlexpr = qlast.Shape(expr=qlexpr, elements=shape_el.elements) 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 = qlast.SelectQuery(result=qlexpr, implicit=True) 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_required = base_required 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 we generated qlexpr for the element, we process the # subview by just compiling the qlexpr. This is so that we can # figure out if it needs materialization and also so that # `qlexpr is not None` always implies that we did the # compilation. (Except for mutations) if qlexpr and not is_mutation: irexpr, _ = _compile_qlexpr( qlexpr, view_scls, ptrcls=ptrcls, ptrsource=ptrsource, path_id=path_id, ptr_name=ptr_name, is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update, ctx=ctx) materialized = setgen.should_materialize( irexpr, ptrcls=ptrcls, materialize_visible=True, skipped_bindings={path_id}, ctx=ctx) ptr_target = inference.infer_type(irexpr, ctx.env) elif 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) if ctx.path_log is not None: ctx.path_log.append(sub_path_id) 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, parser_context=shape_el.context, is_update=is_update, 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 = qlast.SelectQuery(result=qlexpr, implicit=True) qlexpr.limit = qlast.IntegerConstant(value=str(ctx.implicit_limit)) irexpr, sub_view_rptr = _compile_qlexpr( qlexpr, view_scls, ptrcls=ptrcls, ptrsource=ptrsource, path_id=path_id, ptr_name=ptr_name, is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update, ctx=ctx) materialized = setgen.should_materialize( irexpr, ptrcls=ptrcls, materialize_visible=True, skipped_bindings={path_id}, ctx=ctx) ptr_target = inference.infer_type(irexpr, ctx.env) 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 = sub_view_rptr.base_ptrcls base_ptrcls_is_alias = sub_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_required = False 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) ptr_required = existing.get_required(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 materialized and not is_mutation and ctx.qlstmt: assert ptrcls not in ctx.env.materialized_sets ctx.env.materialized_sets[ptrcls] = ctx.qlstmt, materialized if not ctx.expr_exposed and irexpr: setgen.maybe_materialize(ptrcls, irexpr, ctx=ctx) 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, irexpr=irexpr, context=ctx, path_id=path_id, path_id_ns=path_id_namespace, shape_op=shape_el.operation.op, should_materialize=materialized or [], ) if compexpr is not None or is_polymorphic or materialized: 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) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'required', ptr_required) 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 try_group_rewrite( node: qlast.Query, aliases: AliasGenerator, ) -> Optional[qlast.Query]: """ Try to apply some syntactic rewrites of GROUP expressions so we can generate better code. The two key desugarings are: * Sink a shape into the internal group result SELECT (GROUP ...) <shape> [filter-clause] [order-clause] [other clauses] => SELECT ( FOR GROUP ... UNION <igroup-body> <shape> [filter-clause] [order-clause] ) [other clauses] * Convert a FOR over a group into just an internal group (and a trivial FOR) FOR g in (GROUP ...) UNION <body> => FOR GROUP ... UNION ( FOR g IN (<group-body>) UNION <body> ) """ # TODO: would Python's new pattern matching fit well here??? # Sink shapes into the GROUP if ( isinstance(node, qlast.SelectQuery) and isinstance(node.result, qlast.Shape) and isinstance(node.result.expr, qlast.GroupQuery) ): igroup = desugar_group(node.result.expr, aliases) igroup = igroup.replace(result=qlast.Shape( expr=igroup.result, elements=node.result.elements)) # FILTER gets sunk into the body of the FOR GROUP if node.where or node.orderby: igroup = igroup.replace( # We need to move the result_alias in case # the FILTER depends on it. result_alias=node.result_alias, where=node.where, orderby=node.orderby, ) return node.replace( result=igroup, result_alias=None, where=None, orderby=None) # Eliminate FORs over GROUPs if ( isinstance(node, qlast.ForQuery) and isinstance(node.iterator, qlast.GroupQuery) ): igroup = desugar_group(node.iterator, aliases) new_result = qlast.ForQuery( iterator_alias=node.iterator_alias, iterator=igroup.result, result=node.result, ) return igroup.replace(result=new_result, aliases=node.aliases) return None