def get_params_symtable( params: FuncParameterList, schema: s_schema.Schema, *, inlined_defaults: bool, ) -> Dict[str, qlast.Expr]: anchors: Dict[str, qlast.Expr] = {} defaults_mask = qlast.TypeCast( expr=qlast.Parameter(name='__defaults_mask__', optional=False), type=qlast.TypeName( maintype=qlast.ObjectRef( module='std', name='bytes', ), ), ) for pi, p in enumerate(params.get_in_canonical_order(schema)): p_shortname = p.get_parameter_name(schema) p_is_optional = p.get_typemod(schema) is not ft.TypeModifier.SINGLETON anchors[p_shortname] = qlast.TypeCast( expr=qlast.Parameter( name=p_shortname, optional=p_is_optional, ), type=utils.typeref_to_ast(schema, p.get_type(schema)), ) p_default = p.get_default(schema) if p_default is None: continue if not inlined_defaults: continue anchors[p_shortname] = qlast.IfElse( condition=qlast.BinOp( left=qlast.FunctionCall( func=('std', 'bytes_get_bit'), args=[ defaults_mask, qlast.IntegerConstant(value=str(pi)), ]), op='=', right=qlast.IntegerConstant(value='0'), ), if_expr=anchors[p_shortname], else_expr=qlast._Optional(expr=p_default.qlast), ) return anchors
def get_param_anchors_for_callable( params: s_func.ParameterLikeList, schema: s_schema.Schema, *, inlined_defaults: bool, ) -> Tuple[Dict[str, irast.Parameter], List[qlast.AliasedExpr], ]: anchors = {} aliases = [] if inlined_defaults: anchors['__defaults_mask__'] = irast.Parameter( name='__defaults_mask__', typeref=irtyputils.type_to_typeref( # note: no cache schema, cast(s_scalars.ScalarType, schema.get('std::bytes')), ), ) pg_params = s_func.PgParams.from_params(schema, params) for pi, p in enumerate(pg_params.params): p_shortname = p.get_shortname(schema) anchors[p_shortname] = irast.Parameter( name=p_shortname, typeref=irtyputils.type_to_typeref(schema, p.get_type(schema))) if p.get_default(schema) is None: continue if not inlined_defaults: continue aliases.append( qlast.AliasedExpr( alias=p_shortname, expr=qlast. IfElse(condition=qlast.BinOp(left=qlast.FunctionCall( func=('std', 'bytes_get_bit'), args=[ qlast.Path( steps=[qlast.ObjectRef(name='__defaults_mask__')]), qlast.IntegerConstant(value=str(pi)), ]), right=qlast.IntegerConstant( value='0'), op='='), if_expr=qlast.Path( steps=[qlast.ObjectRef(name=p_shortname)]), else_expr=qlast._Optional( expr=p.get_ql_default(schema))))) return anchors, aliases
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 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 get_param_anchors_for_callable(params, schema): anchors = {} aliases = [] anchors['__defaults_mask__'] = irast.Parameter( name='__defaults_mask__', typeref=irtyputils.type_to_typeref(schema, schema.get('std::bytes'))) pg_params = s_func.PgParams.from_params(schema, params) for pi, p in enumerate(pg_params.params): p_shortname = p.get_shortname(schema) anchors[p_shortname] = irast.Parameter( name=p_shortname, typeref=irtyputils.type_to_typeref(schema, p.get_type(schema))) if p.get_default(schema) is None: continue aliases.append( qlast.AliasedExpr( alias=p_shortname, expr=qlast. IfElse(condition=qlast.BinOp(left=qlast.FunctionCall( func=('std', 'bytes_get_bit'), args=[ qlast.Path( steps=[qlast.ObjectRef(name='__defaults_mask__')]), qlast.IntegerConstant(value=str(pi)), ]), right=qlast.IntegerConstant( value='0'), op='='), if_expr=qlast.Path( steps=[qlast.ObjectRef(name=p_shortname)]), else_expr=qlast._Optional( expr=p.get_ql_default(schema))))) return anchors, aliases
def const_ast_from_python(val: Any) -> qlast.BaseConstant: if isinstance(val, str): return qlast.StringConstant.from_python(val) elif isinstance(val, bool): return qlast.BooleanConstant(value='true' if val else 'false') elif isinstance(val, int): if MIN_INT64 <= val <= MAX_INT64: return qlast.IntegerConstant(value=str(val)) else: raise ValueError(f'int64 value out of range: {val}') elif isinstance(val, decimal.Decimal): return qlast.DecimalConstant(value=f'{val}n') elif isinstance(val, float): return qlast.FloatConstant(value=str(val)) elif isinstance(val, bytes): return qlast.BytesConstant.from_python(value=val) else: raise ValueError(f'unexpected constant type: {type(val)!r}')
def compile_SelectQuery( expr: qlast.SelectQuery, *, ctx: context.ContextLevel) -> irast.Set: with ctx.subquery() as sctx: stmt = irast.SelectStmt() init_stmt(stmt, expr, ctx=sctx, parent_ctx=ctx) if expr.implicit: # Make sure path prefix does not get blown away by # implicit subqueries. sctx.partial_path_prefix = ctx.partial_path_prefix stmt.implicit_wrapper = True if ( (ctx.expr_exposed or sctx.stmt is ctx.toplevel_stmt) and ctx.implicit_limit and expr.limit is None and not ctx.inhibit_implicit_limit ): expr.limit = qlast.IntegerConstant(value=str(ctx.implicit_limit)) stmt.result = compile_result_clause( expr.result, view_scls=ctx.view_scls, view_rptr=ctx.view_rptr, result_alias=expr.result_alias, view_name=ctx.toplevel_result_view_name, ctx=sctx) clauses.compile_where_clause( stmt, expr.where, ctx=sctx) stmt.orderby = clauses.compile_orderby_clause( expr.orderby, ctx=sctx) stmt.offset = clauses.compile_limit_offset_clause( expr.offset, ctx=sctx) stmt.limit = clauses.compile_limit_offset_clause( expr.limit, ctx=sctx) result = fini_stmt(stmt, expr, ctx=sctx, parent_ctx=ctx) return result
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
def compile_ForQuery( qlstmt: qlast.ForQuery, *, ctx: context.ContextLevel) -> irast.Set: with ctx.subquery() as sctx: stmt = irast.SelectStmt() init_stmt(stmt, qlstmt, ctx=sctx, parent_ctx=ctx) with sctx.newscope(fenced=True) as scopectx: iterator_ctx = None if (ctx.expr_exposed and ctx.iterator_ctx is not None and ctx.iterator_ctx is not sctx): iterator_ctx = ctx.iterator_ctx if iterator_ctx is not None: iterator_scope_parent = iterator_ctx.path_scope path_id_ns = iterator_ctx.path_id_namespace else: iterator_scope_parent = sctx.path_scope path_id_ns = sctx.path_id_namespace iterator = qlstmt.iterator if isinstance(iterator, qlast.Set) and len(iterator.elements) == 1: iterator = iterator.elements[0] iterator_view = stmtctx.declare_view( iterator, qlstmt.iterator_alias, path_id_namespace=path_id_ns, ctx=scopectx) iterator_stmt = setgen.new_set_from_set( iterator_view, preserve_scope_ns=True, ctx=scopectx) if iterator_ctx is not None and iterator_ctx.stmt is not None: iterator_ctx.stmt.hoisted_iterators.append(iterator_stmt) stmt.iterator_stmt = iterator_stmt view_scope_info = scopectx.path_scope_map[iterator_view] iterator_scope = view_scope_info.path_scope pathctx.register_set_in_scope( iterator_stmt, path_scope=iterator_scope_parent, ctx=sctx, ) # Iterator symbol is, by construction, outside of the scope # of the UNION argument, but is perfectly legal to be referenced # inside a factoring fence that is an immediate child of this # scope. iterator_scope_parent.factoring_whitelist.add( stmt.iterator_stmt.path_id) node = iterator_scope_parent.find_descendant(iterator_stmt.path_id) if node is not None: node.attach_subtree(iterator_scope) stmt.result = compile_result_clause( qlstmt.result, view_scls=ctx.view_scls, view_rptr=ctx.view_rptr, result_alias=qlstmt.result_alias, view_name=ctx.toplevel_result_view_name, forward_rptr=True, ctx=sctx) if ((ctx.expr_exposed or sctx.stmt is ctx.toplevel_stmt) and ctx.implicit_limit): stmt.limit = setgen.ensure_set( dispatch.compile( qlast.IntegerConstant(value=str(ctx.implicit_limit)), ctx=sctx, ), ctx=sctx, ) result = fini_stmt(stmt, qlstmt, ctx=sctx, 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 compile_func_to_ir(func, schema, *, anchors=None, security_context=None, modaliases=None, implicit_id_in_shapes=False, implicit_tid_in_shapes=False): """Compile an EdgeQL function into EdgeDB IR.""" if debug.flags.edgeql_compile: debug.header('EdgeQL Function') debug.print(func.get_code(schema)) trees = ql_parser.parse_block(func.get_code(schema) + ';') if len(trees) != 1: raise errors.InvalidFunctionDefinitionError( 'functions can only contain one statement') tree = trees[0] if modaliases: ql_parser.append_module_aliases(tree, modaliases) if anchors is None: anchors = {} anchors['__defaults_mask__'] = irast.Parameter( name='__defaults_mask__', typeref=irtyputils.type_to_typeref(schema, schema.get('std::bytes'))) func_params = func.get_params(schema) pg_params = s_func.PgParams.from_params(schema, func_params) for pi, p in enumerate(pg_params.params): p_shortname = p.get_shortname(schema) anchors[p_shortname] = irast.Parameter( name=p_shortname, typeref=irtyputils.type_to_typeref(schema, p.get_type(schema))) if p.get_default(schema) is None: continue tree.aliases.append( qlast.AliasedExpr( alias=p_shortname, expr=qlast.IfElse( condition=qlast.BinOp( left=qlast.FunctionCall( func=('std', 'bytes_get_bit'), args=[ qlast.FuncArg( arg=qlast.Path(steps=[ qlast.ObjectRef( name='__defaults_mask__') ])), qlast.FuncArg( arg=qlast.IntegerConstant(value=str(pi))) ]), right=qlast.IntegerConstant(value='0'), op='='), if_expr=qlast.Path( steps=[qlast.ObjectRef(name=p_shortname)]), else_expr=qlast._Optional(expr=p.get_ql_default(schema))))) ir = compile_ast_to_ir( tree, schema, anchors=anchors, func=func, security_context=security_context, modaliases=modaliases, implicit_id_in_shapes=implicit_id_in_shapes, implicit_tid_in_shapes=implicit_tid_in_shapes) return ir
def reduce_ICONST(self, *kids): self.val = qlast.IntegerConstant(value=kids[0].val)
def finalize_args( bound_call: polyres.BoundCall, *, actual_typemods: Sequence[ft.TypeModifier] = (), is_polymorphic: bool = False, ctx: context.ContextLevel, ) -> Tuple[List[irast.CallArg], List[ft.TypeModifier]]: args: List[irast.CallArg] = [] typemods = [] for i, barg in enumerate(bound_call.args): param = barg.param arg = barg.val if param is None: # defaults bitmask args.append(irast.CallArg(expr=arg)) typemods.append(ft.TypeModifier.SINGLETON) continue if actual_typemods: param_mod = actual_typemods[i] else: param_mod = param.get_typemod(ctx.env.schema) typemods.append(param_mod) if param_mod is not ft.TypeModifier.SET_OF: arg_scope = pathctx.get_set_scope(arg, ctx=ctx) param_shortname = param.get_parameter_name(ctx.env.schema) # Arg was wrapped for scope fencing purposes, # but that fence has been removed above, so unwrap it. orig_arg = arg arg = irutils.unwrap_set(arg) if (param_mod is ft.TypeModifier.OPTIONAL or param_shortname in bound_call.null_args): if arg_scope is not None: # Due to the construction of relgen, the (unfenced) # subscope is necessary to shield LHS paths from the outer # query to prevent path binding which may break OPTIONAL. branch = arg_scope.unfence() pathctx.register_set_in_scope(arg, ctx=ctx) pathctx.mark_path_as_optional(arg.path_id, ctx=ctx) if arg_scope is not None: pathctx.assign_set_scope(arg, branch, ctx=ctx) elif arg_scope is not None: arg_scope.collapse() if arg is orig_arg: pathctx.assign_set_scope(arg, None, ctx=ctx) else: if (is_polymorphic and ctx.expr_exposed and ctx.implicit_limit and isinstance(arg.expr, irast.SelectStmt) and arg.expr.limit is None): arg.expr.limit = setgen.ensure_set( dispatch.compile( qlast.IntegerConstant(value=str(ctx.implicit_limit)), ctx=ctx, ), ctx=ctx, ) paramtype = barg.param_type param_kind = param.get_kind(ctx.env.schema) if param_kind is ft.ParameterKind.VARIADIC: # For variadic params, paramtype would be array<T>, # and we need T to cast the arguments. assert isinstance(paramtype, s_types.Array) paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0] # Check if we need to cast the argument value before passing # it to the callable. compatible = schemactx.is_type_compatible( paramtype, barg.valtype, ctx=ctx, ) if not compatible: # The callable form was chosen via an implicit cast, # cast the arguments so that the backend has no # wiggle room to apply its own (potentially different) # casting. arg = casts.compile_cast( arg, paramtype, srcctx=None, ctx=ctx) call_arg = irast.CallArg(expr=arg, cardinality=None) stmtctx.get_expr_cardinality_later( target=call_arg, field='cardinality', irexpr=arg, ctx=ctx) args.append(call_arg) return args, typemods
def visit_IntValue(self, node): return qlast.IntegerConstant(value=node.value)
def finalize_args( bound_call: polyres.BoundCall, *, actual_typemods: Sequence[ft.TypeModifier] = (), guessed_typemods: Dict[Union[int, str], ft.TypeModifier], is_polymorphic: bool = False, ctx: context.ContextLevel, ) -> Tuple[List[irast.CallArg], List[ft.TypeModifier]]: args: List[irast.CallArg] = [] typemods = [] for i, barg in enumerate(bound_call.args): param = barg.param arg = barg.val arg_type_path_id: Optional[irast.PathId] = None if param is None: # defaults bitmask args.append(irast.CallArg(expr=arg)) typemods.append(ft.TypeModifier.SingletonType) continue if actual_typemods: param_mod = actual_typemods[i] else: param_mod = param.get_typemod(ctx.env.schema) typemods.append(param_mod) if param_mod is not ft.TypeModifier.SetOfType: param_shortname = param.get_parameter_name(ctx.env.schema) if param_shortname in bound_call.null_args: pathctx.register_set_in_scope(arg, optional=True, ctx=ctx) # If we guessed the argument was optional but it wasn't, # try to go back and make it *not* optional. elif (param_mod is ft.TypeModifier.SingletonType and barg.arg_id is not None and param_mod is not guessed_typemods[barg.arg_id]): for child in ctx.path_scope.children: if (child.path_id == arg.path_id or (arg.path_scope_id is not None and child.unique_id == arg.path_scope_id)): child.optional = False # Object type arguments to functions may be overloaded, so # we populate a path id field so that we can also pass the # type as an argument on the pgsql side. If the param type # is "anytype", though, then it can't be overloaded on # that argument. arg_type = setgen.get_set_type(arg, ctx=ctx) if (isinstance(arg_type, s_objtypes.ObjectType) and barg.param and not barg.param.get_type(ctx.env.schema).is_any( ctx.env.schema)): arg_type_path_id = pathctx.extend_path_id( arg.path_id, ptrcls=arg_type.getptr(ctx.env.schema, sn.UnqualName('__type__')), ctx=ctx, ) else: is_array_agg = (isinstance(bound_call.func, s_func.Function) and (bound_call.func.get_shortname(ctx.env.schema) == sn.QualName('std', 'array_agg'))) if ( # Ideally, we should implicitly slice all array values, # but in practice, the vast majority of large arrays # will come from array_agg, and so we only care about # that. is_array_agg and ctx.expr_exposed and ctx.implicit_limit and isinstance(arg.expr, irast.SelectStmt) and arg.expr.limit is None and not ctx.inhibit_implicit_limit): arg.expr.limit = dispatch.compile( qlast.IntegerConstant(value=str(ctx.implicit_limit)), ctx=ctx, ) paramtype = barg.param_type param_kind = param.get_kind(ctx.env.schema) if param_kind is ft.ParameterKind.VariadicParam: # For variadic params, paramtype would be array<T>, # and we need T to cast the arguments. assert isinstance(paramtype, s_types.Array) paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0] # Check if we need to cast the argument value before passing # it to the callable. compatible = s_types.is_type_compatible( paramtype, barg.valtype, schema=ctx.env.schema, ) if not compatible: # The callable form was chosen via an implicit cast, # cast the arguments so that the backend has no # wiggle room to apply its own (potentially different) # casting. arg = casts.compile_cast(arg, paramtype, srcctx=None, ctx=ctx) args.append( irast.CallArg(expr=arg, expr_type_path_id=arg_type_path_id, is_default=barg.is_default)) return args, typemods
def finalize_args( bound_call: polyres.BoundCall, *, arg_ctxs: Dict[irast.Set, context.ContextLevel], actual_typemods: Sequence[ft.TypeModifier] = (), is_polymorphic: bool = False, ctx: context.ContextLevel, ) -> Tuple[List[irast.CallArg], List[ft.TypeModifier]]: args: List[irast.CallArg] = [] typemods = [] for i, barg in enumerate(bound_call.args): param = barg.param arg = barg.val if param is None: # defaults bitmask args.append(irast.CallArg(expr=arg)) typemods.append(ft.TypeModifier.SingletonType) continue if actual_typemods: param_mod = actual_typemods[i] else: param_mod = param.get_typemod(ctx.env.schema) typemods.append(param_mod) orig_arg = arg arg_ctx = arg_ctxs.get(orig_arg) arg_scope = pathctx.get_set_scope(arg, ctx=ctx) if param_mod is not ft.TypeModifier.SetOfType: param_shortname = param.get_parameter_name(ctx.env.schema) # Arg was wrapped for scope fencing purposes, # but that fence has been removed above, so unwrap it. arg = irutils.unwrap_set(arg) if (param_mod is ft.TypeModifier.OptionalType or param_shortname in bound_call.null_args): process_path_log(arg_ctx, arg_scope) if arg_scope is not None: # Due to the construction of relgen, the (unfenced) # subscope is necessary to shield LHS paths from the outer # query to prevent path binding which may break OPTIONAL. arg_scope.mark_as_optional() branch = arg_scope.unfence() pathctx.register_set_in_scope(arg, optional=True, ctx=ctx) if arg_scope is not None: pathctx.assign_set_scope(arg, branch, ctx=ctx) elif arg_scope is not None: arg_scope.collapse() if arg is orig_arg: pathctx.assign_set_scope(arg, None, ctx=ctx) else: process_path_log(arg_ctx, arg_scope) is_array_agg = ( isinstance(bound_call.func, s_func.Function) and ( bound_call.func.get_shortname(ctx.env.schema) == sn.QualName('std', 'array_agg') ) ) if ( # Ideally, we should implicitly slice all array values, # but in practice, the vast majority of large arrays # will come from array_agg, and so we only care about # that. is_array_agg and ctx.expr_exposed and ctx.implicit_limit and isinstance(arg.expr, irast.SelectStmt) and arg.expr.limit is None and not ctx.inhibit_implicit_limit ): arg.expr.limit = setgen.ensure_set( dispatch.compile( qlast.IntegerConstant(value=str(ctx.implicit_limit)), ctx=ctx, ), ctx=ctx, ) paramtype = barg.param_type param_kind = param.get_kind(ctx.env.schema) if param_kind is ft.ParameterKind.VariadicParam: # For variadic params, paramtype would be array<T>, # and we need T to cast the arguments. assert isinstance(paramtype, s_types.Array) paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0] # Check if we need to cast the argument value before passing # it to the callable. compatible = s_types.is_type_compatible( paramtype, barg.valtype, schema=ctx.env.schema, ) if not compatible: # The callable form was chosen via an implicit cast, # cast the arguments so that the backend has no # wiggle room to apply its own (potentially different) # casting. arg = casts.compile_cast( arg, paramtype, srcctx=None, ctx=ctx) args.append(irast.CallArg(expr=arg, cardinality=None)) # If we have any logged paths left over and our enclosing # context is logging paths, propagate them up. if arg_ctx and arg_ctx.path_log and ctx.path_log is not None: ctx.path_log.extend(arg_ctx.path_log) return args, typemods
def compile_ForQuery( qlstmt: qlast.ForQuery, *, ctx: context.ContextLevel) -> irast.Set: with ctx.subquery() as sctx: stmt = irast.SelectStmt(context=qlstmt.context) init_stmt(stmt, qlstmt, ctx=sctx, parent_ctx=ctx) # As an optimization, if the iterator is a singleton set, use # the element directly. iterator = qlstmt.iterator if isinstance(iterator, qlast.Set) and len(iterator.elements) == 1: iterator = iterator.elements[0] # Compile the iterator iterator_ctx = None if (ctx.expr_exposed and ctx.iterator_ctx is not None and ctx.iterator_ctx is not sctx): iterator_ctx = ctx.iterator_ctx ictx = iterator_ctx or sctx contains_dml = qlutils.contains_dml(qlstmt.result) # If the body contains DML, then we need to prohibit # correlation between the iterator and the enclosing # query, since the correlation imposes compilation issues # we aren't willing to tackle. if contains_dml: ictx.path_scope.factoring_allowlist.update( ctx.iterator_path_ids) iterator_view = stmtctx.declare_view( iterator, s_name.UnqualName(qlstmt.iterator_alias), factoring_fence=contains_dml, path_id_namespace=ictx.path_id_namespace, ctx=ictx, ) iterator_stmt = setgen.new_set_from_set( iterator_view, preserve_scope_ns=True, ctx=sctx) stmt.iterator_stmt = iterator_stmt iterator_type = setgen.get_set_type(iterator_stmt, ctx=ctx) anytype = iterator_type.find_any(ctx.env.schema) if anytype is not None: raise errors.QueryError( 'FOR statement has iterator of indeterminate type', context=ctx.env.type_origins.get(anytype), ) if iterator_ctx is not None and iterator_ctx.stmt is not None: iterator_ctx.stmt.hoisted_iterators.append(iterator_stmt) view_scope_info = sctx.path_scope_map[iterator_view] pathctx.register_set_in_scope( iterator_stmt, path_scope=ictx.path_scope, ctx=sctx, ) # Iterator symbol is, by construction, outside of the scope # of the UNION argument, but is perfectly legal to be referenced # inside a factoring fence that is an immediate child of this # scope. ictx.path_scope.factoring_allowlist.add( stmt.iterator_stmt.path_id) sctx.iterator_path_ids |= {stmt.iterator_stmt.path_id} node = ictx.path_scope.find_descendant(iterator_stmt.path_id) if node is not None: # See above about why we need a factoring fence. # We need to do this again when we move the branch so # as to preserve the fencing. # Do this by sticking the iterator subtree onto a branch # with a factoring fence. if contains_dml: node = node.attach_branch() node.factoring_fence = True node = node.attach_branch() node.attach_subtree(view_scope_info.path_scope, context=iterator.context) # Compile the body with sctx.newscope(fenced=True) as bctx: stmt.result = setgen.scoped_set( compile_result_clause( qlstmt.result, view_scls=ctx.view_scls, view_rptr=ctx.view_rptr, result_alias=qlstmt.result_alias, view_name=ctx.toplevel_result_view_name, forward_rptr=True, ctx=bctx, ), ctx=bctx, ) # Inject an implicit limit if appropriate if ((ctx.expr_exposed or sctx.stmt is ctx.toplevel_stmt) and ctx.implicit_limit): stmt.limit = setgen.ensure_set( dispatch.compile( qlast.IntegerConstant(value=str(ctx.implicit_limit)), ctx=sctx, ), ctx=sctx, ) result = fini_stmt(stmt, qlstmt, ctx=sctx, parent_ctx=ctx) return result