def new_set( *, stype: s_types.Type, ctx: context.ContextLevel, ircls: Type[irast.Set] = irast.Set, **kwargs: Any, ) -> irast.Set: """Create a new ir.Set instance with given attributes. Absolutely all ir.Set instances must be created using this constructor. """ if (stype not in ctx.type_rewrites and isinstance(stype, s_objtypes.ObjectType) and ctx.env.options.apply_query_rewrites and (filters := stype.get_access_policy_filters(ctx.env.schema))): qry = qlast.SelectQuery(result=qlast.Path( steps=[s_utils.name_to_ast_ref(stype.get_name(ctx.env.schema))]), ) for f in filters: assert isinstance(f.qlast, qlast.Expr) qry.where = astutils.extend_binop(qry.where, f.qlast) with ctx.detached() as subctx: subctx.expr_exposed = False # This is a global rewrite operation that is done once # per type, and so we don't really care if we're in a # temporary scope or not. subctx.path_scope = subctx.env.path_scope.root subctx.in_temp_scope = False # Put a placeholder to prevent recursion. subctx.type_rewrites[stype] = irast.Set() filtered_set = dispatch.compile(qry, ctx=subctx) assert isinstance(filtered_set, irast.Set) subctx.type_rewrites[stype] = filtered_set
def compile_GlobalExpr( expr: qlast.GlobalExpr, *, ctx: context.ContextLevel) -> irast.Set: glob = ctx.env.get_track_schema_object( s_utils.ast_ref_to_name(expr.name), expr.name, modaliases=ctx.modaliases, type=s_globals.Global) assert isinstance(glob, s_globals.Global) if glob.is_computable(ctx.env.schema): obj_ref = s_utils.name_to_ast_ref( glob.get_target(ctx.env.schema).get_name(ctx.env.schema)) return dispatch.compile(qlast.Path(steps=[obj_ref]), ctx=ctx) objctx = ctx.env.options.schema_object_context if objctx in (s_constr.Constraint, s_indexes.Index): typname = objctx.get_schema_class_displayname() raise errors.SchemaDefinitionError( f'global variables cannot be referenced from {typname}', context=expr.context) default = glob.get_default(ctx.env.schema) param_set: qlast.Expr | irast.Set present_set: qlast.Expr | irast.Set | None if ctx.env.options.func_params is None: param_set, present_set = setgen.get_global_param_sets(glob, ctx=ctx) else: param_set, present_set = setgen.get_func_global_param_sets( glob, ctx=ctx) if default and not present_set: # If we have a default value and the global is required, # then we can use the param being {} as a signal to use # the default. with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() main_param = subctx.maybe_create_anchor(param_set, 'glob') param_set = func.compile_operator( expr, op_name='std::??', qlargs=[main_param, default.qlast], ctx=subctx) elif default and present_set: # ... but if {} is a valid value for the global, we need to # stick in an extra parameter to indicate whether to use # the default. with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() main_param = subctx.maybe_create_anchor(param_set, 'glob') present_param = subctx.maybe_create_anchor(present_set, 'present') param_set = func.compile_operator( expr, op_name='std::IF', qlargs=[main_param, present_param, default.qlast], ctx=subctx) elif not isinstance(param_set, irast.Set): param_set = dispatch.compile(param_set, ctx=ctx) return param_set
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.QualName] = None, is_insert: bool = False, is_update: bool = False, is_delete: bool = False, parser_context: pctx.ParserContext, 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.QualName( module=ctx.derived_target_module or '__derived__', name=name, ) view_scls = schemactx.derive_view( stype, is_insert=is_insert, is_update=is_update, is_delete=is_delete, derived_name=view_name, ctx=ctx, ) assert isinstance(view_scls, s_objtypes.ObjectType), view_scls 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_local_name(ctx.env.schema) 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) and pn != sn.UnqualName('__type__') ): 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', type=s_objects.SubclassableObject)): continue vn = ptrcls.get_verbosename( ctx.env.schema, with_parent=True) raise errors.MissingRequiredError( f'missing value for required {vn}') 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.apply_query_rewrites ): explicit_ptrs = { ptrcls.get_local_name(ctx.env.schema) 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: ptr_ref = s_utils.name_to_ast_ref(pn) implicit_ql = qlast.ShapeElement( expr=qlast.Path(steps=[qlast.Ptr(ptr=ptr_ref)]), compexpr=qlast.BinOp( left=qlast.Path( partial=True, steps=[ qlast.Ptr( ptr=ptr_ref, 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 != stype): ctx.env.schema = view_scls.set_field_value( ctx.env.schema, 'rptr', view_rptr.ptrcls) return view_scls
def compile_inheritance_conflict_selects( stmt: irast.MutatingStmt, conflict: irast.MutatingStmt, typ: s_objtypes.ObjectType, subject_type: s_objtypes.ObjectType, *, ctx: context.ContextLevel, ) -> List[irast.OnConflictClause]: """Compile the selects needed to resolve multiple DML to related types Generate a SELECT that finds all objects of type `typ` that conflict with the insert `stmt`. The backend will use this to explicitly check that no conflicts exist, and raise an error if they do. This is needed because we mostly use triggers to enforce these cross-type exclusive constraints, and they use a snapshot beginning at the start of the statement. """ pointers = _get_exclusive_ptr_constraints(typ, ctx=ctx) exclusive = ctx.env.schema.get('std::exclusive', type=s_constr.Constraint) obj_constrs = [ constr for constr in typ.get_constraints(ctx.env.schema).objects(ctx.env.schema) if constr.issubclass(ctx.env.schema, exclusive) ] shape_ptrs = set() for elem, op in stmt.subject.shape: assert elem.rptr is not None if op != qlast.ShapeOp.MATERIALIZE: shape_ptrs.add(elem.rptr.ptrref.shortname.name) # This is a little silly, but for *this* we need to do one per # constraint (so that we can properly identify which constraint # failed in the error messages) entries: List[Tuple[s_constr.Constraint, ConstraintPair]] = [] for name, (ptr, ptr_constrs) in pointers.items(): for ptr_constr in ptr_constrs: # For updates, we only need to emit the check if we actually # modify a pointer used by the constraint. For inserts, though # everything must be in play, since constraints can depend on # nonexistence also. if ( _constr_matters(ptr_constr, ctx) and ( isinstance(stmt, irast.InsertStmt) or (_get_needed_ptrs(typ, (), [name], ctx)[0] & shape_ptrs) ) ): entries.append((ptr_constr, ({name: (ptr, [ptr_constr])}, []))) for obj_constr in obj_constrs: # See note above about needed ptrs check if ( _constr_matters(obj_constr, ctx) and ( isinstance(stmt, irast.InsertStmt) or (_get_needed_ptrs( typ, [obj_constr], (), ctx)[0] & shape_ptrs) ) ): entries.append((obj_constr, ({}, [obj_constr]))) # For updates, we need to pull from the actual result overlay, # since the final row can depend on things not in the query. fake_dml_set = None if isinstance(stmt, irast.UpdateStmt): fake_subject = qlast.DetachedExpr(expr=qlast.Path(steps=[ s_utils.name_to_ast_ref(subject_type.get_name(ctx.env.schema))])) fake_dml_set = dispatch.compile(fake_subject, ctx=ctx) clauses = [] for cnstr, (p, o) in entries: select_ir, _, _ = compile_conflict_select( stmt, typ, for_inheritance=True, fake_dml_set=fake_dml_set, constrs=p, obj_constrs=o, parser_context=stmt.context, ctx=ctx) if isinstance(select_ir, irast.EmptySet): continue cnstr_ref = irast.ConstraintRef(id=cnstr.id) clauses.append( irast.OnConflictClause( constraint=cnstr_ref, select_ir=select_ir, always_check=False, else_ir=None, else_fail=conflict, update_query_set=fake_dml_set) ) return clauses
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, )) insert_subject = qlast.Path(steps=[ s_utils.name_to_ast_ref(subject_typ.get_name(ctx.env.schema))]) 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)) if not conds: return None # We use `any` to compute the disjunction here because some might # be empty. if len(conds) == 1: cond = conds[0]
def run_ddl(cls, schema, ddl, default_module=defines.DEFAULT_MODULE_ALIAS): statements = edgeql.parse_block(ddl) current_schema = schema target_schema = None migration_schema = None migration_target = None migration_script = [] for stmt in statements: if isinstance(stmt, qlast.StartMigration): # START MIGRATION if target_schema is None: target_schema = _load_std_schema() migration_target = s_ddl.apply_sdl( stmt.target, base_schema=target_schema, current_schema=current_schema, testmode=True, ) migration_schema = current_schema ddl_plan = None elif isinstance(stmt, qlast.PopulateMigration): # POPULATE MIGRATION if migration_target is None: raise errors.QueryError( 'unexpected POPULATE MIGRATION:' ' not currently in a migration block', context=stmt.context, ) migration_diff = s_ddl.delta_schemas( migration_schema, migration_target, ) if debug.flags.delta_plan: debug.header('Populate Migration Diff') debug.dump(migration_diff, schema=schema) new_ddl = s_ddl.ddlast_from_delta( migration_schema, migration_target, migration_diff, ) migration_script.extend(new_ddl) if debug.flags.delta_plan: debug.header('Populate Migration DDL AST') text = [] for cmd in new_ddl: debug.dump(cmd) text.append(edgeql.generate_source(cmd, pretty=True)) debug.header('Populate Migration DDL Text') debug.dump_code(';\n'.join(text) + ';') elif isinstance(stmt, qlast.CommitMigration): if migration_target is None: raise errors.QueryError( 'unexpected COMMIT MIGRATION:' ' not currently in a migration block', context=stmt.context, ) last_migration = current_schema.get_last_migration() if last_migration: last_migration_ref = s_utils.name_to_ast_ref( last_migration.get_name(current_schema), ) else: last_migration_ref = None create_migration = qlast.CreateMigration( body=qlast.MigrationBody(commands=tuple(migration_script)), parent=last_migration_ref, ) ddl_plan = s_ddl.delta_from_ddl( create_migration, schema=migration_schema, modaliases={None: default_module}, testmode=True, ) if debug.flags.delta_plan: debug.header('Delta Plan') debug.dump(ddl_plan, schema=schema) migration_schema = None migration_target = None migration_script = [] elif isinstance(stmt, qlast.DDL): if migration_target is not None: migration_script.append(stmt) ddl_plan = None else: ddl_plan = s_ddl.delta_from_ddl( stmt, schema=current_schema, modaliases={None: default_module}, testmode=True, ) if debug.flags.delta_plan: debug.header('Delta Plan') debug.dump(ddl_plan, schema=schema) else: raise ValueError( f'unexpected {stmt!r} in compiler setup script') if ddl_plan is not None: context = sd.CommandContext() context.testmode = True current_schema = ddl_plan.apply(current_schema, context) return current_schema
def run_ddl(cls, schema, ddl, default_module=defines.DEFAULT_MODULE_ALIAS): statements = edgeql.parse_block(ddl) current_schema = schema target_schema = None migration_schema = None migration_target = None migration_script = [] migration_plan = None for stmt in statements: if isinstance(stmt, qlast.StartMigration): # START MIGRATION if target_schema is None: target_schema = _load_std_schema() migration_target = s_ddl.apply_sdl( stmt.target, base_schema=target_schema, current_schema=current_schema, testmode=True, ) migration_schema = current_schema ddl_plan = None elif isinstance(stmt, qlast.PopulateMigration): # POPULATE MIGRATION if migration_target is None: raise errors.QueryError( 'unexpected POPULATE MIGRATION:' ' not currently in a migration block', context=stmt.context, ) migration_diff = s_ddl.delta_schemas( migration_schema, migration_target, ) if not migration_script: migration_plan = migration_diff migration_script.extend( s_ddl.ddlast_from_delta( migration_target, migration_diff, ), ) elif isinstance(stmt, qlast.CommitMigration): if migration_target is None: raise errors.QueryError( 'unexpected COMMIT MIGRATION:' ' not currently in a migration block', context=stmt.context, ) last_migration = current_schema.get_last_migration() if last_migration: last_migration_ref = s_utils.name_to_ast_ref( last_migration.get_name(current_schema), ) else: last_migration_ref = None create_migration = qlast.CreateMigration( commands=migration_script, auto_diff=migration_plan, parent=last_migration_ref, ) ddl_plan = s_ddl.delta_from_ddl( create_migration, schema=migration_schema, modaliases={None: default_module}, testmode=True, ) migration_schema = None migration_target = None migration_script = [] migration_plan = None elif isinstance(stmt, qlast.DDL): if migration_target is not None: migration_script.append(stmt) ddl_plan = None else: ddl_plan = s_ddl.delta_from_ddl( stmt, schema=current_schema, modaliases={None: default_module}, testmode=True, ) else: raise ValueError( f'unexpected {stmt!r} in compiler setup script') if ddl_plan is not None: context = sd.CommandContext() context.testmode = True current_schema = ddl_plan.apply(current_schema, context) return current_schema