def normalize_constraint_expr(cls, schema, module_aliases, expr, *, subject=None, constraint_name, expr_context=None, enforce_boolean=False): if subject is None: subject = cls._dummy_subject(schema) edgeql_tree, ir_result = cls._normalize_constraint_expr( schema, module_aliases, expr, subject) if enforce_boolean: bool_t = schema.get('std::bool') expr_type = ir_result.stype if not expr_type.issubclass(schema, bool_t): raise errors.InvalidConstraintDefinitionError( f'{constraint_name} constraint expression expected ' f'to return a bool value, got ' f'{expr_type.get_name(schema).name!r}', context=expr_context) expr = edgeql.generate_source(edgeql_tree, pretty=False) return expr
def statements_from_delta( schema_a: Optional[s_schema.Schema], schema_b: s_schema.Schema, delta: sd.DeltaRoot, *, sdlmode: bool = False, descriptive_mode: bool = False, limit_ref_classes: Iterable[so.ObjectMeta] = tuple(), ) -> Tuple[Tuple[str, sd.Command], ...]: stmts = ddlast_from_delta( schema_a, schema_b, delta, sdlmode=sdlmode, descriptive_mode=descriptive_mode, ) ql_classes_src = {scls.get_ql_class() for scls in limit_ref_classes} ql_classes = {q for q in ql_classes_src if q is not None} text = [] for stmt_ast, cmd in stmts.items(): stmt_text = edgeql.generate_source( stmt_ast, sdlmode=sdlmode, descmode=descriptive_mode, limit_ref_classes=ql_classes, ) text.append((stmt_text + ';', cmd)) return tuple(text)
def _text_from_delta( schema: s_schema.Schema, delta: sd.DeltaRoot, *, sdlmode: bool, descriptive_mode: bool = False, limit_ref_classes: Iterable[so.ObjectMeta] = tuple(), ) -> str: context = sd.CommandContext( descriptive_mode=descriptive_mode, declarative=sdlmode, ) text = [] for command in delta.get_subcommands(): with context(sd.DeltaRootContext(schema=schema, op=delta)): delta_ast = command.get_ast(schema, context) if delta_ast: ql_classes_src = { scls.get_ql_class() for scls in limit_ref_classes } ql_classes = {q for q in ql_classes_src if q is not None} stmt_text = edgeql.generate_source( delta_ast, sdlmode=sdlmode, descmode=descriptive_mode, limit_ref_classes=ql_classes, ) text.append(stmt_text + ';') return '\n'.join(text)
def _encode_default(self, schema, context, node, op): if op.new_value: expr = op.new_value if not isinstance(expr, s_expr.Expression): expr_t = qlast.SelectQuery( result=qlast.BaseConstant.from_python(expr)) expr = edgeql.generate_source(expr_t, pretty=False) op.new_value = s_expr.Expression(expr) super()._apply_field_ast(schema, context, node, op)
def _constraint_args_from_ast(cls, schema, astnode): args = [] if astnode.args: for arg in astnode.args: arg_expr = s_expr.Expression( text=edgeql.generate_source(arg.arg, pretty=False)) args.append(arg_expr) return args
def _cmd_tree_from_ast(cls, schema, astnode, context): cmd = super()._cmd_tree_from_ast(schema, astnode, context) cmd.set_attribute_value( 'expr', s_expr.Expression( text=edgeql.generate_source(astnode.expr, pretty=False) ), ) return cmd
def _cmd_tree_from_ast(cls, schema, astnode, context): cmd = super()._cmd_tree_from_ast(schema, astnode, context) if isinstance(astnode, qlast.CreateConcreteConstraint): args = cls._constraint_args_from_ast(schema, astnode) if args: cmd.add(sd.AlterObjectProperty(property='args', new_value=args)) elif isinstance(astnode, qlast.CreateConstraint): params = cls._get_param_desc_from_ast(schema, context.modaliases, astnode) for param in params: if param.get_kind(schema) is ft.ParameterKind.NAMED_ONLY: raise errors.InvalidConstraintDefinitionError( 'named only parameters are not allowed ' 'in this context', context=astnode.context) if param.get_default(schema) is not None: raise errors.InvalidConstraintDefinitionError( 'constraints do not support parameters ' 'with defaults', context=astnode.context) if cmd.get_attribute_value('return_type') is None: cmd.add( sd.AlterObjectProperty(property='return_type', new_value=utils.reduce_to_typeref( schema, schema.get('std::bool')))) if cmd.get_attribute_value('return_typemod') is None: cmd.add( sd.AlterObjectProperty( property='return_typemod', new_value=ft.TypeModifier.SINGLETON, )) # 'subjectexpr' can be present in either astnode type if astnode.subjectexpr: subjectexpr = s_expr.Expression( text=edgeql.generate_source(astnode.subjectexpr, pretty=False)) cmd.add( sd.AlterObjectProperty(property='subjectexpr', new_value=subjectexpr)) cls._validate_subcommands(astnode) return cmd
def format_error_message( self, schema: s_schema.Schema, ) -> str: errmsg = self.get_errmessage(schema) subject = self.get_subject(schema) titleattr = subject.get_annotation(schema, 'std::title') if not titleattr: subjname = subject.get_shortname(schema) subjtitle = subjname.name else: subjtitle = titleattr args = self.get_args(schema) if args: from edb.edgeql import parser as qlparser from edb.edgeql import utils as qlutils args_ql: List[qlast.Base] = [ qlast.Path(steps=[qlast.ObjectRef(name=subjtitle)]), ] args_ql.extend(qlparser.parse(arg.text) for arg in args) constr_base: Constraint = schema.get(self.get_name(schema), type=type(self)) index_parameters = qlutils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema, ) expr = constr_base.get_field_value(schema, 'expr') expr_ql = qlparser.parse(expr.text) qlutils.inline_parameters(expr_ql, index_parameters) args_map = { name: edgeql.generate_source(val, pretty=False) for name, val in index_parameters.items() } else: args_map = {'__subject__': subjtitle} assert errmsg is not None formatted = errmsg.format(**args_map) return formatted
def ddl_text_from_delta_command(schema, delta): """Return DDL text for a delta command tree.""" if isinstance(delta, sd.DeltaRoot): commands = delta else: commands = [delta] text = [] for command in commands: delta_ast = ddl_from_delta(schema, command) if delta_ast: stmt_text = edgeql.generate_source(delta_ast) text.append(stmt_text + ';') return '\n'.join(text)
def _cmd_tree_from_ast(cls, schema, astnode, context): from edb.edgeql import compiler as qlcompiler propname = astnode.name.name parent_ctx = context.get(CommandContextToken) parent_op = parent_ctx.op field = parent_op.get_schema_metaclass().get_field(propname) if field is None: raise errors.SchemaDefinitionError( f'{propname!r} is not a valid field', context=astnode.context) if not (isinstance(astnode, qlast.SetInternalField) or field.allow_ddl_set or context.stdmode or context.testmode): raise errors.SchemaDefinitionError( f'{propname!r} is not a valid field', context=astnode.context) if field.type is s_expr.Expression: new_value = s_expr.Expression( text=edgeql.generate_source(astnode.value, pretty=False)) else: if isinstance(astnode.value, qlast.Tuple): new_value = tuple( qlcompiler.evaluate_ast_to_python_val(el.value, schema=schema) for el in astnode.value.elements) elif isinstance(astnode.value, qlast.ObjectRef): new_value = utils.ast_objref_to_objref( astnode.value, modaliases=context.modaliases, schema=schema) elif (isinstance(astnode.value, qlast.Set) and not astnode.value.elements): # empty set new_value = None else: new_value = qlcompiler.evaluate_ast_to_python_val( astnode.value, schema=schema) return cls(property=propname, new_value=new_value)
def ddl_text_from_delta(schema, delta): """Return DDL text for a delta object.""" root = sd.DeltaRoot(canonical=True) root.update(delta.get_commands(schema)) context = sd.CommandContext() schema, _ = root.apply(schema, context) context = sd.CommandContext() text = [] for command in root.get_subcommands(): with context(sd.DeltaRootContext(schema=schema, op=root)): delta_ast = ddl_from_delta(schema, context, command) if delta_ast: stmt_text = edgeql.generate_source(delta_ast) text.append(stmt_text + ';') return '\n'.join(text)
def ddl_text_from_delta(schema: s_schema.Schema, delta: sd.DeltaRoot) -> str: """Return DDL text corresponding to a delta plan. Args: schema: The schema to which the *delta* has **already** been applied. delta: The delta plan. Returns: DDL text corresponding to *delta*. """ context = sd.CommandContext() text = [] for command in delta.get_subcommands(): with context(sd.DeltaRootContext(schema=schema, op=delta)): delta_ast = command.get_ast(schema, context) if delta_ast: stmt_text = edgeql.generate_source(delta_ast) text.append(stmt_text + ';') return '\n'.join(text)
def _classname_quals_from_ast(cls, schema, astnode, base_name, referrer_name, context): subject = schema.get(referrer_name, None) if subject is None: return () props = {} args = cls._constraint_args_from_ast(schema, astnode) if args: props['args'] = args if astnode.subjectexpr: props['subjectexpr'] = s_expr.Expression( text=edgeql.generate_source(astnode.subject, pretty=False)) _, attrs = Constraint.get_concrete_constraint_attrs( schema, subject, name=base_name, sourcectx=astnode.context, modaliases=context.modaliases, **props) return (Constraint._name_qual_from_expr(schema, attrs['finalexpr'].text), )
def get_concrete_constraint_attrs( cls, schema, subject, *, name, subjectexpr=None, sourcectx=None, args=[], modaliases=None, **kwargs): from edb.edgeql import parser as qlparser from edb.edgeql import utils as qlutils constr_base = schema.get(name, module_aliases=modaliases) module_aliases = {} orig_subjectexpr = subjectexpr orig_subject = subject base_subjectexpr = constr_base.get_field_value(schema, 'subjectexpr') if subjectexpr is None: subjectexpr = base_subjectexpr elif (base_subjectexpr is not None and subjectexpr.text != base_subjectexpr.text): raise errors.InvalidConstraintDefinitionError( 'subjectexpr is already defined for ' + f'{str(name)!r}') if subjectexpr is not None: subject_ql = subjectexpr.qlast if subject_ql is None: subject_ql = qlparser.parse(subjectexpr.text, module_aliases) subject = subject_ql expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr') if not expr: raise errors.InvalidConstraintDefinitionError( f'missing constraint expression in {name!r}') expr_ql = qlparser.parse(expr.text, module_aliases) if not args: args = constr_base.get_field_value(schema, 'args') attrs = dict(kwargs) inherited = dict() if orig_subjectexpr is not None: attrs['subjectexpr'] = orig_subjectexpr else: base_subjectexpr = constr_base.get_subjectexpr(schema) if base_subjectexpr is not None: attrs['subjectexpr'] = base_subjectexpr errmessage = attrs.get('errmessage') if not errmessage: errmessage = constr_base.get_errmessage(schema) inherited['errmessage'] = True attrs['errmessage'] = errmessage if subject is not orig_subject: # subject has been redefined qlutils.inline_anchors(expr_ql, anchors={qlast.Subject: subject}) subject = orig_subject args_map = None if args: args_ql = [ qlast.Path(steps=[qlast.Subject()]), ] args_ql.extend( qlparser.parse(arg.text, module_aliases) for arg in args ) args_map = qlutils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema) qlutils.inline_parameters(expr_ql, args_map) args_map = {name: edgeql.generate_source(val, pretty=False) for name, val in args_map.items()} args_map['__subject__'] = '{__subject__}' attrs['errmessage'] = attrs['errmessage'].format(**args_map) inherited.pop('errmessage', None) attrs['args'] = args if expr == '__subject__': expr_context = sourcectx else: expr_context = None final_expr = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr_ql, schema, module_aliases), schema=schema, modaliases=module_aliases, anchors={qlast.Subject: subject}, ) bool_t = schema.get('std::bool') expr_type = final_expr.irast.stype if not expr_type.issubclass(schema, bool_t): raise errors.InvalidConstraintDefinitionError( f'{name} constraint expression expected ' f'to return a bool value, got ' f'{expr_type.get_name(schema).name!r}', context=expr_context ) attrs['return_type'] = constr_base.get_return_type(schema) attrs['return_typemod'] = constr_base.get_return_typemod(schema) attrs['finalexpr'] = final_expr attrs['params'] = constr_base.get_params(schema) return constr_base, attrs, inherited
def statements_from_delta( schema_a: Optional[s_schema.Schema], schema_b: s_schema.Schema, delta: sd.DeltaRoot, *, sdlmode: bool = False, descriptive_mode: bool = False, # Used for backwards compatibility with older migration text. uppercase: bool = False, limit_ref_classes: Iterable[so.ObjectMeta] = tuple(), ) -> Tuple[Tuple[str, qlast.DDLOperation, sd.Command], ...]: stmts = ddlast_from_delta( schema_a, schema_b, delta, sdlmode=sdlmode, descriptive_mode=descriptive_mode, ) ql_classes_src = { scls.get_ql_class() for scls in limit_ref_classes } ql_classes = {q for q in ql_classes_src if q is not None} # If we're generating SDL and it includes modules, try to nest the # module contents in the actual modules. processed: List[Tuple[qlast.DDLOperation, sd.Command]] = [] unqualified: List[Tuple[qlast.DDLOperation, sd.Command]] = [] modules = dict() for stmt_ast, cmd in stmts.items(): if sdlmode: if isinstance(stmt_ast, qlast.CreateModule): # Record the module stubs. modules[stmt_ast.name.name] = stmt_ast stmt_ast.commands = [] processed.append((stmt_ast, cmd)) elif ( modules and not isinstance(stmt_ast, qlast.UnqualifiedObjectCommand) ): # This SDL included creation of modules, so we will try to # nest the declarations in them. assert isinstance(stmt_ast, qlast.CreateObject) assert stmt_ast.name.module is not None module = modules[stmt_ast.name.module] module.commands.append(stmt_ast) # Strip the module from the object name, since we nest # them in a module already. stmt_ast.name.module = None elif isinstance(stmt_ast, qlast.UnqualifiedObjectCommand): unqualified.append((stmt_ast, cmd)) else: processed.append((stmt_ast, cmd)) else: processed.append((stmt_ast, cmd)) text = [] for stmt_ast, cmd in itertools.chain(unqualified, processed): stmt_text = edgeql.generate_source( stmt_ast, sdlmode=sdlmode, descmode=descriptive_mode, limit_ref_classes=ql_classes, uppercase=uppercase, ) text.append((stmt_text + ';', stmt_ast, cmd)) return tuple(text)
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 get_concrete_constraint_attrs(cls, schema, subject, *, name, subjectexpr=None, sourcectx=None, args=[], modaliases=None, **kwargs): from edb.edgeql import utils as edgeql_utils from edb.edgeql import parser as edgeql_parser constr_base = schema.get(name, module_aliases=modaliases) module_aliases = {} orig_subject = subject base_subjectexpr = constr_base.get_field_value(schema, 'subjectexpr') if subjectexpr is None: subjectexpr = base_subjectexpr elif (base_subjectexpr is not None and subjectexpr.text != base_subjectexpr.text): raise errors.InvalidConstraintDefinitionError( 'subjectexpr is already defined for ' + f'{str(name)!r}') if subjectexpr is not None: subject, _ = cls._normalize_constraint_expr( schema, {}, subjectexpr.text, subject) expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr') if not expr: raise errors.InvalidConstraintDefinitionError( f'missing constraint expression in {name!r}') expr_ql = edgeql_parser.parse(expr.text, module_aliases) if not args: args = constr_base.get_field_value(schema, 'args') attrs = dict(kwargs) args_map = None if args: args_ql = [ edgeql_parser.parse(arg.text, module_aliases) for arg in args ] args_map = edgeql_utils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema) edgeql_utils.inline_parameters(expr_ql, args_map) args_map = { name: edgeql.generate_source(val, pretty=False) for name, val in args_map.items() } errmessage = attrs.get('errmessage') if not errmessage: errmessage = constr_base.get_errmessage(schema) attrs['errmessage'] = errmessage.format( __subject__='{__subject__}', **args_map) attrs['args'] = args if expr == '__subject__': expr_context = sourcectx else: expr_context = None if subject is not orig_subject: # subject has been redefined subject_anchor = qlast.SubExpr( expr=subject, anchors={qlast.Subject: orig_subject}) else: subject_anchor = subject expr_text = cls.normalize_constraint_expr(schema, module_aliases, expr_ql, subject=subject_anchor, constraint_name=name, enforce_boolean=True, expr_context=expr_context) attrs['finalexpr'] = s_expr.Expression(text=expr_text) return constr_base, attrs