def reduce_Expr_CIRCUMFLEX_Expr(self, *kids): self.val = qlast.BinOp(left=kids[0].val, op=kids[1].val, right=kids[2].val)
def reduce_Expr_DOUBLESLASH_Expr(self, *kids): self.val = qlast.BinOp(left=kids[0].val, op=kids[1].val, right=kids[2].val)
def reduce_Expr_PERCENT_Expr(self, *kids): self.val = qlast.BinOp(left=kids[0].val, op=kids[1].val, right=kids[2].val)
def reduce_Expr_UNION_Expr(self, *kids): self.val = qlast.BinOp(left=kids[0].val, op='UNION', right=kids[2].val)
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 reduce_Expr_NOT_ILIKE_Expr(self, *kids): self.val = qlast.BinOp(left=kids[0].val, op='NOT ILIKE', right=kids[3].val)
def reduce_Expr_NOT_IN_Expr(self, *kids): inexpr = kids[3].val self.val = qlast.BinOp(left=kids[0].val, op='NOT IN', right=inexpr)
def reduce_Expr_OR_Expr(self, *kids): self.val = qlast.BinOp(left=kids[0].val, op=kids[1].val.upper(), right=kids[2].val)
def reduce_Expr_LIKE_Expr(self, *kids): self.val = qlast.BinOp(left=kids[0].val, op='LIKE', right=kids[2].val)
def reduce_Expr_RANGBRACKET_Expr(self, *kids): self.val = qlast.BinOp(left=kids[0].val, op=kids[1].val, right=kids[2].val)
]) ptr, ptr_cnstrs = constrs[ptrname] ptr_card = ptr.get_cardinality(ctx.env.schema) for cnstr in ptr_cnstrs: lhs: qlast.Expr = anchor rhs: qlast.Expr = ptr_val # If there is a subjectexpr, substitute our lhs and rhs in # for __subject__ in the subjectexpr and compare *that* if (subjectexpr := cnstr.get_subjectexpr(ctx.env.schema)): assert isinstance(subjectexpr.qlast, qlast.Expr) lhs = qlutils.subject_substitute(subjectexpr.qlast, lhs) rhs = qlutils.subject_substitute(subjectexpr.qlast, rhs) conds.append(qlast.BinOp( op='=' if ptr_card.is_single() else 'IN', left=lhs, right=rhs, )) for constr in obj_constrs: subjectexpr = constr.get_subjectexpr(ctx.env.schema) assert subjectexpr and isinstance(subjectexpr.qlast, qlast.Expr) lhs = qlutils.subject_paths_substitute(subjectexpr.qlast, ptr_anchors) 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(
def _process_view(*, stype: s_objtypes.ObjectType, path_id: irast.PathId, path_id_namespace: Optional[irast.WeakNamespace] = None, elements: List[qlast.ShapeElement], view_rptr: Optional[context.ViewRPtr] = None, view_name: Optional[sn.SchemaName] = None, is_insert: bool = False, is_update: bool = False, ctx: context.ContextLevel) -> s_objtypes.ObjectType: if (view_name is None and ctx.env.options.schema_view_mode and view_rptr is not None): # Make sure persistent schema expression aliases have properly formed # names as opposed to the usual mangled form of the ephemeral # aliases. This is needed for introspection readability, as well # as helps in maintaining proper type names for schema # representations that require alphanumeric names, such as # GraphQL. # # We use the name of the source together with the name # of the inbound link to form the name, so in e.g. # CREATE ALIAS V := (SELECT Foo { bar: { baz: { ... } }) # The name of the innermost alias would be "__V__bar__baz". source_name = view_rptr.source.get_name(ctx.env.schema).name if not source_name.startswith('__'): source_name = f'__{source_name}' if view_rptr.ptrcls_name is not None: ptr_name = view_rptr.ptrcls_name.name elif view_rptr.ptrcls is not None: ptr_name = view_rptr.ptrcls.get_shortname(ctx.env.schema).name else: raise errors.InternalServerError( '_process_view in schema mode received view_rptr with ' 'neither ptrcls_name, not ptrcls') name = f'{source_name}__{ptr_name}' view_name = sn.Name( module=ctx.derived_target_module or '__derived__', name=name, ) view_scls = schemactx.derive_view(stype, is_insert=is_insert, is_update=is_update, derived_name=view_name, ctx=ctx) assert isinstance(view_scls, s_objtypes.ObjectType) is_mutation = is_insert or is_update is_defining_shape = ctx.expr_exposed or is_mutation if view_rptr is not None and view_rptr.ptrcls is None: derive_ptrcls(view_rptr, target_scls=view_scls, transparent=True, ctx=ctx) pointers = [] for shape_el in elements: with ctx.newscope(fenced=True) as scopectx: pointer = _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 pointer in pointers: schema = ctx.env.schema vnp = pointer.get_verbosename(schema, with_parent=True) raise errors.QueryError(f'duplicate definition of {vnp}', context=shape_el.context) pointers.append(pointer) 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 (pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema)): continue default_expr = ptrcls.get_default(ctx.env.schema) if not default_expr: if ptrcls.get_required(ctx.env.schema): if ptrcls.is_property(ctx.env.schema): # If the target is a sequence, there's no need # for an explicit value. ptrcls_target = ptrcls.get_target(ctx.env.schema) assert ptrcls_target is not None if ptrcls_target.issubclass( ctx.env.schema, ctx.env.schema.get('std::sequence')): continue what = 'property' else: what = 'link' raise errors.MissingRequiredError( f'missing value for required {what} ' f'{stype.get_displayname(ctx.env.schema)}.' f'{ptrcls.get_displayname(ctx.env.schema)}') else: 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, ), ), ], ), compexpr=qlast.DetachedExpr(expr=default_expr.qlast, ), ) 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, from_default=True, view_rptr=view_rptr, ctx=scopectx, ), ) elif (stype.get_name(ctx.env.schema).module == 'schema' and ctx.env.options.introspection_schema_rewrites): 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 (pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema)): continue schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema) if schema_deflt is None: continue with ctx.newscope(fenced=True) as scopectx: implicit_ql = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=pn, ), ), ], ), compexpr=qlast.BinOp( left=qlast.Path( partial=True, steps=[ qlast.Ptr( ptr=qlast.ObjectRef(name=pn), direction=( s_pointers.PointerDirection.Outbound), ) ], ), right=qlparser.parse_fragment(schema_deflt), op='??', ), ) # Note: we only need to record the schema default # as a computable, but not include it in the type # shape, so we ignore the return value. _normalize_view_ptr_expr( implicit_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, ) for ptrcls in pointers: source: Union[s_types.Type, s_pointers.PointerLike] if ptrcls.is_link_property(ctx.env.schema): assert view_rptr is not None and view_rptr.ptrcls is not None source = view_rptr.ptrcls else: source = view_scls if is_defining_shape: cinfo = ctx.source_map.get(ptrcls) if cinfo is not None: shape_op = cinfo.shape_op else: shape_op = qlast.ShapeOp.ASSIGN ctx.env.view_shapes[source].append((ptrcls, shape_op)) if (view_rptr is not None and view_rptr.ptrcls is not None and view_scls is not stype): ctx.env.schema = view_scls.set_field_value(ctx.env.schema, 'rptr', view_rptr.ptrcls) return view_scls
def computable_ptr_set( rptr: irast.Pointer, *, unnest_fence: bool = False, same_computable_scope: bool = False, ctx: context.ContextLevel, ) -> irast.Set: """Return ir.Set for a pointer defined as a computable.""" ptrcls = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx) source_set = rptr.source source_scls = get_set_type(source_set, ctx=ctx) # process_view() may generate computable pointer expressions # in the form "self.linkname". To prevent infinite recursion, # self must resolve to the parent type of the view NOT the view # type itself. Similarly, when resolving computable link properties # make sure that we use the parent of derived ptrcls. if source_scls.is_view(ctx.env.schema): source_set_stype = source_scls.peel_view(ctx.env.schema) source_set = new_set_from_set(source_set, stype=source_set_stype, preserve_scope_ns=True, ctx=ctx) source_set.shape = [] if source_set.rptr is not None: source_rptrref = source_set.rptr.ptrref if source_rptrref.base_ptr is not None: source_rptrref = source_rptrref.base_ptr source_set.rptr = irast.Pointer( source=source_set.rptr.source, target=source_set, ptrref=source_rptrref, direction=source_set.rptr.direction, ) qlctx: Optional[context.ContextLevel] inner_source_path_id: Optional[irast.PathId] try: comp_info = ctx.source_map[ptrcls] qlexpr = comp_info.qlexpr qlctx = comp_info.context inner_source_path_id = comp_info.path_id path_id_ns = comp_info.path_id_ns except KeyError: comp_expr = ptrcls.get_expr(ctx.env.schema) schema_qlexpr: Optional[qlast.Expr] = None if comp_expr is None and ctx.env.options.introspection_schema_rewrites: schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema) if schema_deflt is not None: assert isinstance(ptrcls, s_pointers.Pointer) ptrcls_n = ptrcls.get_shortname(ctx.env.schema).name schema_qlexpr = qlast.BinOp( left=qlast.Path(steps=[ qlast.Source(), qlast.Ptr( ptr=qlast.ObjectRef(name=ptrcls_n), direction=s_pointers.PointerDirection.Outbound, type=('property' if ptrcls.is_link_property( ctx.env.schema) else None)) ], ), right=qlparser.parse_fragment(schema_deflt), op='??', ) if schema_qlexpr is None: if comp_expr is None: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) raise errors.InternalServerError( f'{ptrcls_sn!r} is not a computable pointer') schema_qlexpr = qlparser.parse(comp_expr.text) # NOTE: Validation of the expression type is not the concern # of this function. For any non-object pointer target type, # the default expression must be assignment-cast into that # type. target_scls = ptrcls.get_target(ctx.env.schema) assert target_scls is not None if not target_scls.is_object_type(): schema_qlexpr = qlast.TypeCast( type=typegen.type_to_ql_typeref(target_scls, ctx=ctx), expr=schema_qlexpr, ) qlexpr = astutils.ensure_qlstmt(schema_qlexpr) qlctx = None inner_source_path_id = None path_id_ns = None newctx: Callable[[], ContextManager[context.ContextLevel]] if qlctx is None: # Schema-level computable, completely detached context newctx = ctx.detached else: newctx = _get_computable_ctx(rptr=rptr, source=source_set, source_scls=source_scls, inner_source_path_id=inner_source_path_id, path_id_ns=path_id_ns, same_scope=same_computable_scope, qlctx=qlctx, ctx=ctx) if ptrcls.is_link_property(ctx.env.schema): source_path_id = rptr.source.path_id.ptr_path() else: src_path = rptr.target.path_id.src_path() assert src_path is not None source_path_id = src_path result_path_id = pathctx.extend_path_id( source_path_id, ptrcls=ptrcls, ns=ctx.path_id_namespace, ctx=ctx, ) result_stype = ptrcls.get_target(ctx.env.schema) with newctx() as subctx: subctx.disable_shadowing.add(ptrcls) subctx.view_scls = result_stype assert isinstance(source_scls, s_sources.Source) subctx.view_rptr = context.ViewRPtr(source_scls, ptrcls=ptrcls, rptr=rptr) subctx.anchors[qlast.Source().name] = source_set subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema) subctx.partial_path_prefix = source_set if isinstance(qlexpr, qlast.Statement): subctx.stmt_metadata[qlexpr] = context.StatementMetadata( is_unnest_fence=unnest_fence, iterator_target=True, ) comp_ir_set = ensure_set(dispatch.compile(qlexpr, ctx=subctx), ctx=subctx) comp_ir_set_copy = new_set_from_set(comp_ir_set, ctx=ctx) pending_cardinality = ctx.pending_cardinality.get(ptrcls) if pending_cardinality is not None: stmtctx.get_pointer_cardinality_later( ptrcls=ptrcls, irexpr=comp_ir_set_copy, specified_card=pending_cardinality.specified_cardinality, is_mut_assignment=pending_cardinality.is_mut_assignment, shape_op=pending_cardinality.shape_op, source_ctx=pending_cardinality.source_ctx, ctx=ctx, ) if (pending_cardinality is None or pending_cardinality.shape_op is not qlast.ShapeOp.SUBTRACT): # When doing subtraction in shapes, the cardinality of the # expression being subtracted does not matter. stmtctx.enforce_pointer_cardinality( ptrcls, comp_ir_set_copy, singletons={source_path_id}, ctx=ctx, ) comp_ir_set = new_set_from_set(comp_ir_set, path_id=result_path_id, rptr=rptr, ctx=ctx) rptr.target = comp_ir_set return comp_ir_set
def _get_general_offset_limit(self, after, before, first, last): # convert any static values to corresponding qlast if after is not None: if isinstance(after, qlast.Base): after = qlast.TypeCast(type=qlast.TypeName( maintype=qlast.ObjectRef(name='int64')), expr=after) else: after = qlast.BaseConstant.from_python(after) if before is not None: if isinstance(before, qlast.Base): before = qlast.TypeCast(type=qlast.TypeName( maintype=qlast.ObjectRef(name='int64')), expr=before) else: before = qlast.BaseConstant.from_python(before) if first is not None and not isinstance(first, qlast.Base): first = qlast.BaseConstant.from_python(first) if last is not None and not isinstance(last, qlast.Base): last = qlast.BaseConstant.from_python(last) offset = limit = None # convert before, after, first and last into offset and limit if after is not None: # The +1 is to make 'after' into an appropriate index. # # 0--a--1--b--2--c--3-- ... we call element at # index 0 (or "element 0" for short), the element # immediately after the mark 0. So after "element # 0" really means after "index 1". offset = qlast.BinOp(left=after, op='+', right=qlast.IntegerConstant(value='1')) if before is not None: # limit = before - (after or 0) if after: limit = qlast.BinOp(left=before, op='-', right=after) else: limit = before if first is not None: if limit is None: limit = first else: limit = qlast.IfElse(if_expr=first, condition=qlast.BinOp(left=first, op='<', right=limit), else_expr=limit) if last is not None: if limit is not None: if offset: offset = qlast.BinOp(left=offset, op='+', right=qlast.BinOp(left=limit, op='-', right=last)) else: offset = qlast.BinOp(left=limit, op='-', right=last) limit = qlast.IfElse(if_expr=last, condition=qlast.BinOp(left=last, op='<', right=limit), else_expr=limit) else: # FIXME: there wasn't any limit, so we can define last # in terms of offset alone without negative OFFSET # implementation raise g_errors.GraphQLTranslationError( f'last translates to a negative OFFSET in ' f'EdgeQL which is currently unsupported') return offset, limit