def _float_to_path(self, token, context): from edb.lang.schema import pointers as s_pointers # make sure that the float is of the type 0.1 parts = token.val.split('.') if not (len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit()): raise EdgeQLSyntaxError( f"Unexpected {token.val!r}", context=token.context) # context for the AST is established manually here return [ qlast.Ptr( ptr=qlast.ObjectRef( name=parts[0], context=token.context, ), direction=s_pointers.PointerDirection.Outbound, context=context, ), qlast.Ptr( ptr=qlast.ObjectRef( name=parts[1], context=token.context, ), direction=s_pointers.PointerDirection.Outbound, context=token.context, ) ]
def visit_order(self, node): # if there is no specific ordering, then order by id if not node.value: return [ qlast.SortExpr( path=qlast.Path( steps=[qlast.Ptr(ptr=qlast.ObjectRef(name='id'))], partial=True, ), direction=qlast.SortAsc, ) ] # Ordering is handled by specifying a list of special Ordering objects. # Validation is already handled by this point. orderby = [] for enum in node.value: name, direction, nulls = self._visit_order_item(enum) orderby.append( qlast.SortExpr( path=qlast.Path( steps=[qlast.Ptr(ptr=qlast.ObjectRef(name=name))], partial=True, ), direction=direction, nones_order=nulls, )) return orderby
def visit_Set(self, node): if node.expr is not None: result = self.visit(node.expr) else: links = [] while node.rptr and (not node.show_as_anchor or self.context.inline_anchors): rptr = node.rptr ptrcls = rptr.ptrcls pname = ptrcls.shortname if isinstance(rptr.target.scls, s_objtypes.ObjectType): target = rptr.target.scls.shortname target = qlast.TypeName(maintype=qlast.ObjectRef( name=target.name, module=target.module)) else: target = None link = qlast.Ptr( ptr=qlast.ObjectRef(name=pname.name, ), direction=rptr.direction, target=target, ) if isinstance(ptrcls.source, s_links.Link): link.type = 'property' links.append(link) node = node.rptr.source result = qlast.Path() if node.show_as_anchor and not self.context.inline_anchors: if issubclass(node.show_as_anchor, qlast.Expr): step = node.show_as_anchor() else: step = qlast.ObjectRef(name=node.show_as_anchor) else: step = qlast.ObjectRef(name=node.scls.shortname.name, module=node.scls.shortname.module) result.steps.append(step) result.steps.extend(reversed(links)) if node.shape: result = qlast.Shape(expr=result, elements=[]) for el in node.shape: rptr = el.rptr ptrcls = rptr.ptrcls pn = ptrcls.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_DOTBW_PathStepName(self, *kids): from edb.lang.schema import pointers as s_pointers self.val = qlast.Ptr( ptr=kids[1].val, direction=s_pointers.PointerDirection.Inbound )
def reduce_PathStepName(self, *kids): from edb.lang.schema import pointers as s_pointers self.val = qlast.Path(steps=[ qlast.Ptr(ptr=kids[0].val, direction=s_pointers.PointerDirection.Outbound) ])
def reduce_AT_ShortNodeName(self, *kids): from edb.lang.schema import pointers as s_pointers self.val = qlast.Ptr( ptr=kids[1].val, direction=s_pointers.PointerDirection.Outbound, type='property' )
def reduce_DOT_ICONST(self, *kids): # this is a valid link-like syntax for accessing unnamed tuples from edb.lang.schema import pointers as s_pointers self.val = qlast.Ptr( ptr=qlast.ObjectRef(name=kids[1].val), direction=s_pointers.PointerDirection.Outbound )
def reduce_AT_ShortNodeName(self, *kids): self.val = qlast.Path( steps=[ qlast.Ptr( ptr=kids[1].val, type='property' ) ] )
def get_path_prefix(self, end_trim=None): # flatten the path path = [step for psteps in self._context.path for step in psteps] # find the first shadowed root for i, step in enumerate(path): base = step.type if isinstance(base, gt.GQLShadowType): break # trim the rest of the path path = path[i + 1:end_trim] prefix = [qlast.ObjectRef(module=base.module, name=base.short_name)] prefix.extend( qlast.Ptr(ptr=qlast.ObjectRef(name=step.name)) for step in path) return prefix
def _prepare_field(self, node): path = self._context.path[-1] include_base = self._context.include_base[-1] is_top = self._is_top_level_field(node) spath = self._context.path[-1] prevt, target = self._get_parent_and_current_type() # insert normal or specialized link steps = [] if include_base: base = spath[0].type steps.append( qlast.ObjectRef(module=base.module, name=base.short_name)) steps.append(qlast.Ptr(ptr=qlast.ObjectRef(name=node.name))) return is_top, path, prevt, target, steps
def visit_ObjectField(self, node): fname = node.name # handle boolean ops if fname == 'and': return self._visit_list_of_inputs(node.value.value, ast.ops.AND) elif fname == 'or': return self._visit_list_of_inputs(node.value.value, ast.ops.OR) elif fname == 'not': return qlast.UnaryOp(op=ast.ops.NOT, operand=self.visit(node.value)) # handle various scalar ops op = gt.GQL_TO_OPS_MAP.get(fname) if op: value = self.visit(node.value) return qlast.BinOp(left=self._context.filter, op=op, right=value) # we're at the beginning of a scalar op _, target = self._get_parent_and_current_type() name = self.get_path_prefix() name.append(qlast.Ptr(ptr=qlast.ObjectRef(name=fname))) name = qlast.Path(steps=name) # potentially need to cast the 'name' side into a <str>, so as # to be compatible with the 'value' typename = target.get_field_type(fname).short_name if (typename != 'str' and gt.EDB_TO_GQL_SCALARS_MAP[typename] in {GraphQLString, GraphQLID}): name = qlast.TypeCast( expr=name, type=qlast.TypeName(maintype=qlast.ObjectRef(name='str')), ) self._context.filter = name return self.visit(node.value)
def visit_Argument(self, node, *, get_path_prefix): op = ast.ops.EQ name_parts = node.name _, target = self._get_parent_and_current_type() name = get_path_prefix() name.append(qlast.Ptr(ptr=qlast.ObjectRef(name=name_parts))) name = qlast.Path(steps=name) value = self.visit(node.value) # potentially need to cast the 'name' side into a <str>, so as # to be compatible with the 'value' typename = target.get_field_type(name_parts).short_name if (typename != 'str' and gt.EDB_TO_GQL_SCALARS_MAP[typename] in {GraphQLString, GraphQLID}): name = qlast.TypeCast( expr=name, type=qlast.TypeName(maintype=qlast.ObjectRef(name='str')), ) return qlast.BinOp(left=name, op=op, right=value)
def visit_Field(self, node): if self._is_duplicate_field(node): return is_top, path, prevt, target, steps = \ self._prepare_field(node) json_mode = False is_shadowed = prevt.is_field_shadowed(node.name) # determine if there needs to be extra subqueries if not prevt.dummy and target.dummy: json_mode = True # this is a special introspection type eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( name=node.alias or node.name)) ]), compexpr=eql, ) elif is_shadowed and not node.alias: # shadowed field that doesn't need an alias spec = filterable = shape = qlast.ShapeElement( expr=qlast.Path(steps=steps), ) elif not node.selection_set or is_shadowed and node.alias: # this is either an unshadowed terminal field or an aliased # shadowed field prefix = qlast.Path(steps=self.get_path_prefix(-1)) eql, shape, filterable = prevt.get_field_template( node.name, parent=prefix, has_shape=bool(node.selection_set)) spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( # this is already a sub-query name=node.alias or node.name)) ]), compexpr=eql) else: # if the parent is NOT a shadowed type, we need an explicit SELECT eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( # this is already a sub-query name=node.alias or node.name)) ]), compexpr=eql) if node.selection_set is not None: if json_mode: pass else: # a single recursion target, so we can process # selection set now self._context.fields.append({}) vals = self.visit(node.selection_set) self._context.fields.pop() if shape: shape.elements = vals if filterable: where, orderby, offset, limit = \ self._visit_arguments(node.arguments) filterable.where = where filterable.orderby = orderby filterable.offset = offset filterable.limit = limit # just to make sure that limit doesn't change the # serialization from list to a single object, we # need to force multi-cardinality, while being careful # as to not set the cardinality qualifier on a # non-computable shape element. if (limit is not None and (isinstance(filterable, qlast.Statement) or filterable.compexpr is not None)): spec.cardinality = qlast.Cardinality.MANY path.pop() return spec
def visit_Field(self, node): if self._is_duplicate_field(node): return is_top, path, prevt, target, steps = \ self._prepare_field(node) json_mode = False # determine if there needs to be extra subqueries if not prevt.dummy and target.dummy: json_mode = True # this is a special introspection type eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( name=node.alias or node.name)) ]), compexpr=eql, ) elif prevt.is_field_shadowed(node.name): if prevt.has_native_field(node.name) and not node.alias: spec = filterable = shape = qlast.ShapeElement( expr=qlast.Path(steps=steps), ) else: prefix = qlast.Path(steps=self.get_path_prefix(-1)) eql, shape, filterable = prevt.get_field_template( node.name, parent=prefix, has_shape=bool(node.selection_set)) spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( # this is already a sub-query name=node.alias or node.name)) ]), compexpr=eql) else: # if the parent is NOT a shadowed type, we need an explicit SELECT eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( # this is already a sub-query name=node.alias or node.name)) ]), compexpr=eql) if node.selection_set is not None: if json_mode: pass else: # a single recursion target, so we can process # selection set now self._context.fields.append({}) vals = self.visit(node.selection_set) self._context.fields.pop() if shape: shape.elements = vals if filterable: where, orderby, offset, limit = \ self._visit_arguments(node.arguments) filterable.where = where filterable.orderby = orderby filterable.offset = offset filterable.limit = limit path.pop() return spec
def _process_view(*, scls: s_nodes.Node, path_id: irast.PathId, elements: typing.List[qlast.ShapeElement], view_rptr: typing.Optional[context.ViewRPtr] = None, view_name: typing.Optional[sn.SchemaName] = None, is_insert: bool = False, is_update: bool = False, ctx: context.CompilerContext) -> s_nodes.Node: view_scls = schemactx.derive_view(scls, is_insert=is_insert, is_update=is_update, derived_name=view_name, ctx=ctx) is_mutation = is_insert or is_update is_defining_shape = ctx.expr_exposed or is_mutation pointers = [] for shape_el in elements: with ctx.newscope(fenced=True) as scopectx: pointers.append( _normalize_view_ptr_expr(shape_el, view_scls, path_id=path_id, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) if is_insert: explicit_ptrs = {ptrcls.shortname for ptrcls in pointers} for pn, ptrcls in scls.pointers.items(): if (not ptrcls.default or pn in explicit_ptrs or ptrcls.is_pure_computable()): continue default_ql = qlast.ShapeElement(expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=ptrcls.shortname.name, module=ptrcls.shortname.module)) ])) with ctx.newscope(fenced=True) as scopectx: scopectx.singletons = ctx.singletons.copy() scopectx.singletons.add(path_id) pointers.append( _normalize_view_ptr_expr(default_ql, view_scls, path_id=path_id, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) # Check if the view shape includes _only_ the link properties. # If so, we do not need to derive a new target view. lprops_only = True for ptrcls in pointers: if not ptrcls.is_link_property(): lprops_only = False break if lprops_only: view_scls = scls for ptrcls in pointers: if ptrcls.is_link_property(): source = view_rptr.derived_ptrcls else: source = view_scls if ptrcls.source is source and isinstance(source, s_sources.Source): # source may be an ScalarType in shapes that reference __type__, # hence the isinstance check. source.add_pointer(ptrcls, replace=True) if is_defining_shape: if source is None: # The nested shape is merely selecting the pointer, # so the link class has not been derived. But for # the purposes of shape tracking, we must derive it # still. source = derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx) ctx.class_shapes[source].append(ptrcls) if view_rptr is not None and view_rptr.derived_ptrcls is not None: view_scls.rptr = view_rptr.derived_ptrcls return view_scls
def _process_view( *, stype: s_nodes.Node, path_id: irast.PathId, path_id_namespace: typing.Optional[irast.WeakNamespace]=None, elements: typing.List[qlast.ShapeElement], view_rptr: typing.Optional[context.ViewRPtr]=None, view_name: typing.Optional[sn.SchemaName]=None, is_insert: bool=False, is_update: bool=False, ctx: context.CompilerContext) -> s_nodes.Node: view_scls = schemactx.derive_view( stype, is_insert=is_insert, is_update=is_update, derived_name=view_name, ctx=ctx) is_mutation = is_insert or is_update is_defining_shape = ctx.expr_exposed or is_mutation pointers = [] for shape_el in elements: with ctx.newscope(fenced=True) as scopectx: pointers.append(_normalize_view_ptr_expr( shape_el, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) if is_insert: explicit_ptrs = {ptrcls.get_shortname(ctx.env.schema).name for ptrcls in pointers} scls_pointers = stype.get_pointers(ctx.env.schema) for pn, ptrcls in scls_pointers.items(ctx.env.schema): if (not ptrcls.get_default(ctx.env.schema) or pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema)): continue ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) default_ql = qlast.ShapeElement(expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=ptrcls_sn.name, module=ptrcls_sn.module)) ])) with ctx.newscope(fenced=True) as scopectx: pointers.append(_normalize_view_ptr_expr( default_ql, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) # Check if the view shape includes _only_ the link properties. # If so, we do not need to derive a new target view. lprops_only = True for ptrcls in pointers: if not ptrcls.is_link_property(ctx.env.schema): lprops_only = False break if lprops_only: view_scls = stype for ptrcls in pointers: if ptrcls.is_link_property(ctx.env.schema): source = view_rptr.derived_ptrcls else: source = view_scls if (ptrcls.get_source(ctx.env.schema) is source and isinstance(source, s_sources.Source)): # source may be an ScalarType in shapes that reference __type__, # hence the isinstance check. ctx.env.schema = source.add_pointer( ctx.env.schema, ptrcls, replace=True) if is_defining_shape: if source is None: # The nested shape is merely selecting the pointer, # so the link class has not been derived. But for # the purposes of shape tracking, we must derive it # still. The derived pointer must be treated the same # as the original, as this is not a new computable, # and both `Foo.ptr` and `Foo { ptr }` are the same path, # hence the `transparent` modifier. source = derive_ptrcls( view_rptr, target_scls=view_scls, transparent=True, ctx=ctx) ctx.class_shapes[source].append(ptrcls) if (view_rptr is not None and view_rptr.derived_ptrcls is not None and view_scls is not stype): ctx.env.schema = view_scls.set_field_value( ctx.env.schema, 'rptr', view_rptr.derived_ptrcls) return view_scls