def _validate(self, schema, context): implicit_bases = [ b for b in self.scls.get_bases(schema).objects(schema) if not b.generic(schema) ] referrer_ctx = self.get_referrer_context(context) objcls = self.get_schema_metaclass() referrer_class = referrer_ctx.op.get_schema_metaclass() refdict = referrer_class.get_refdict_for_class(objcls) if context.declarative and self.scls.get_is_local(schema): if (implicit_bases and refdict.requires_explicit_inherit and not self.get_attribute_value('declared_inherited')): ancestry = [obj.get_referrer(schema) for obj in implicit_bases] raise errors.SchemaDefinitionError( f'{self.scls.get_verbosename(schema, with_parent=True)} ' f'must be declared using the `inherited` keyword because ' f'it is defined in the following ancestor(s): ' f'{", ".join(a.get_shortname(schema) for a in ancestry)}', context=self.source_context, ) elif (not implicit_bases and self.get_attribute_value('declared_inherited')): raise errors.SchemaDefinitionError( f'{self.scls.get_verbosename(schema, with_parent=True)}: ' f'cannot be declared `inherited` as there are no ' f'ancestors defining it.', context=self.source_context, )
def _validate_pointer_def( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> None: """Check that pointer definition is sound.""" from edb.ir import ast as irast referrer_ctx = self.get_referrer_context(context) if referrer_ctx is None: return scls = self.scls if not scls.get_is_local(schema): return default_expr = scls.get_default(schema) if default_expr is not None: if default_expr.irast is None: default_expr = default_expr.compiled(default_expr, schema) assert isinstance(default_expr.irast, irast.Statement) default_type = default_expr.irast.stype assert default_type is not None ptr_target = scls.get_target(schema) assert ptr_target is not None source_context = self.get_attribute_source_context('default') if not default_type.assignment_castable_to(ptr_target, schema): raise errors.SchemaDefinitionError( f'default expression is of invalid type: ' f'{default_type.get_displayname(schema)}, ' f'expected {ptr_target.get_displayname(schema)}', context=source_context, ) # "required" status of defaults should not be enforced # because it's impossible to actually guarantee that any # SELECT involving a path is non-empty ptr_cardinality = scls.get_cardinality(schema) default_required, default_cardinality = \ default_expr.irast.cardinality.to_schema_value() if (ptr_cardinality is qltypes.SchemaCardinality.ONE and default_cardinality != ptr_cardinality): raise errors.SchemaDefinitionError( f'possibly more than one element returned by ' f'the default expression for ' f'{scls.get_verbosename(schema)} declared as ' f"'single'", context=source_context, )
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.from_ast( astnode.value, schema, context.modaliases, ) 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 canonicalize_attributes( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> s_schema.Schema: from edb.ir import ast as irast schema = super().canonicalize_attributes(schema, context) parent_ctx = self.get_referrer_context_or_die(context) source = parent_ctx.op.scls pol_name = self.get_verbosename(parent=source.get_verbosename(schema)) for field in ('expr', 'condition'): if (expr := self.get_local_attribute_value(field)) is None: continue vname = 'when' if field == 'condition' else 'using' expression = self.compile_expr_field( schema, context, field=AccessPolicy.get_field(field), value=expr, ) assert isinstance(expression.irast, irast.Statement) zero = expression.irast.cardinality.can_be_zero() if zero or expression.irast.cardinality.is_multi(): srcctx = self.get_attribute_source_context(field) if zero: problem = 'an empty set' else: problem = 'more than one element' raise errors.SchemaDefinitionError( f'possibly {problem} returned by {vname} ' f'expression for the {pol_name} ', context=srcctx) target = schema.get(sn.QualName('std', 'bool'), type=s_types.Type) expr_type = expression.irast.stype if not expr_type.issubclass(schema, target): srcctx = self.get_attribute_source_context(field) raise errors.SchemaDefinitionError( f'{vname} expression for {pol_name} is of invalid type: ' f'{expr_type.get_displayname(schema)}, ' f'expected {target.get_displayname(schema)}', context=srcctx, )
def _classname_from_ast( cls, schema: s_schema.Schema, astnode: qlast.NamedDDL, context: sd.CommandContext, ) -> sn.Name: referrer_ctx = cls.get_referrer_context(context) if referrer_ctx is not None: referrer_name = referrer_ctx.op.classname assert isinstance(referrer_name, sn.Name) shortname = sn.Name( module='__', name=astnode.name.name, ) name = sn.Name( module=referrer_name.module, name=sn.get_specialized_name( shortname, referrer_name, ), ) else: name = super()._classname_from_ast(schema, astnode, context) shortname = sn.shortname_from_fullname(name) if len(shortname.name) > s_def.MAX_NAME_LENGTH: raise errors.SchemaDefinitionError( f'link or property name length exceeds the maximum of ' f'{s_def.MAX_NAME_LENGTH} characters', context=astnode.context) return name
def get_schema_object( name: Union[str, qlast.BaseObjectRef], module: Optional[str] = None, *, item_type: Optional[Type[s_obj.Object]] = None, condition: Optional[Callable[[s_obj.Object], bool]] = None, label: Optional[str] = None, ctx: context.ContextLevel, srcctx: Optional[parsing.ParserContext] = None) -> s_obj.Object: if isinstance(name, qlast.ObjectRef): if srcctx is None: srcctx = name.context module = name.module name = name.name elif isinstance(name, qlast.AnyType): return s_pseudo.PseudoType.get(ctx.env.schema, 'anytype') elif isinstance(name, qlast.AnyTuple): return s_pseudo.PseudoType.get(ctx.env.schema, 'anytuple') elif isinstance(name, qlast.BaseObjectRef): raise AssertionError(f"Unhandled BaseObjectRef subclass: {name!r}") if module: name = sn.Name(name=name, module=module) elif isinstance(name, str): view = _get_type_variant(name, ctx) if view is not None: return view try: stype = ctx.env.get_track_schema_object( name=name, modaliases=ctx.modaliases, type=item_type, condition=condition, label=label, ) except errors.QueryError as e: s_utils.enrich_schema_lookup_error(e, name, modaliases=ctx.modaliases, schema=ctx.env.schema, item_type=item_type, condition=condition, context=srcctx) raise view = _get_type_variant(stype.get_name(ctx.env.schema), ctx) if view is not None: return view elif stype == ctx.defining_view: # stype is the view in process of being defined and as such is # not yet a valid schema object raise errors.SchemaDefinitionError( f'illegal self-reference in definition of {name!r}', context=srcctx) else: return stype
def _parse_computable(self, expr, schema, context) -> so.ObjectRef: from edb.lang.edgeql import utils as ql_utils from . import sources as s_sources # "source" attribute is set automatically as a refdict back-attr parent_ctx = context.get(s_sources.SourceCommandContext) source_name = parent_ctx.op.classname source = schema.get(source_name, default=None) if source is None: raise errors.SchemaDefinitionError( f'cannot define link/property computables in CREATE TYPE', hint='Perform a CREATE TYPE without the link ' 'followed by ALTER TYPE defining the computable', context=expr.context) ir, _, target_expr = ql_utils.normalize_tree( expr, schema, anchors={qlast.Source: source}, singletons=[source]) target = utils.reduce_to_typeref(schema, ir.stype) self.add( sd.AlterObjectProperty(property='default', new_value=target_expr)) self.add(sd.AlterObjectProperty(property='computable', new_value=True)) self.add( sd.AlterObjectProperty(property='cardinality', new_value=ir.cardinality)) return target
def _rename_begin(self, schema: s_schema.Schema, context: sd.CommandContext) -> s_schema.Schema: orig_schema = schema schema = super()._rename_begin(schema, context) scls = self.scls if not context.canonical and not scls.generic(schema): implicit_bases = scls.get_implicit_bases(schema) non_renamed_bases = set(implicit_bases) - context.renamed_objs # This object is inherited from one or more ancestors that # are not renamed in the same op, and this is an error. if non_renamed_bases: bases_str = ', '.join( b.get_verbosename(schema, with_parent=True) for b in non_renamed_bases) verb = 'are' if len(non_renamed_bases) > 1 else 'is' vn = scls.get_verbosename(orig_schema, with_parent=True) raise errors.SchemaDefinitionError( f'cannot rename inherited {vn}', details=(f'{vn} is inherited from ' f'{bases_str}, which {verb} not being renamed'), context=self.source_context, ) schema = self._propagate_ref_rename(schema, context, scls) return schema
def _classname_from_ast(cls, schema, astnode, context): referrer_ctx = cls.get_referrer_context(context) if referrer_ctx is not None: referrer_name = referrer_ctx.op.classname shortname = sn.Name( module='__', name=astnode.name.name, ) name = sn.Name( module=referrer_name.module, name=sn.get_specialized_name( shortname, referrer_name, ), ) else: name = super()._classname_from_ast(schema, astnode, context) shortname = sn.shortname_from_fullname(name) if len(shortname.name) > MAX_NAME_LENGTH: raise errors.SchemaDefinitionError( f'link or property name length exceeds the maximum of ' f'{MAX_NAME_LENGTH} characters', context=astnode.context) return name
def merge_cardinality( target: Pointer, sources: List[Pointer], field_name: str, *, ignore_local: bool, schema: s_schema.Schema, ) -> Any: current = None current_from = None if ignore_local: pointers = list(sources) else: pointers = [target] + list(sources) for source in pointers: nextval = source.get_explicit_field_value(schema, field_name, None) if nextval is not None: if current is None: current = nextval current_from = source elif current is not nextval: tgt_repr = target.get_verbosename(schema, with_parent=True) cf_repr = current_from.get_verbosename(schema, with_parent=True) other_repr = source.get_verbosename(schema, with_parent=True) raise errors.SchemaDefinitionError( f'cannot redefine the target cardinality of ' f'{tgt_repr}: it is defined ' f'as {current.as_ptr_qual()!r} in {cf_repr} and ' f'as {nextval.as_ptr_qual()!r} in {other_repr}.') return current
def init_stmt( irstmt: irast.Stmt, qlstmt: qlast.Statement, *, ctx: context.ContextLevel, parent_ctx: context.ContextLevel) -> None: if isinstance(irstmt, irast.MutatingStmt): # This is some kind of mutation, so we need to check if it is # allowed. if ctx.env.options.in_ddl_context_name is not None: raise errors.SchemaDefinitionError( f'invalid mutation in {ctx.env.options.in_ddl_context_name}', context=qlstmt.context, ) elif ((dv := ctx.defining_view) is not None and dv.get_expr_type(ctx.env.schema) is s_types.ExprType.Select and not ctx.env.options.allow_top_level_shape_dml): # This is some shape in a regular query. Although # DML is not allowed in the computable, but it may # be possible to refactor it. raise errors.QueryError( f'invalid mutation in a shape computable', hint=( f'To resolve this try to factor out the mutation ' f'expression into the top-level WITH block.' ), context=qlstmt.context, )
def _cmd_tree_from_ast(cls, schema, astnode, context): from edb.lang.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 astnode.as_expr: new_value = s_expr.ExpressionText( edgeql.generate_source(astnode.value, pretty=False)) else: if isinstance(astnode.value, qlast.BaseConstant): new_value = qlcompiler.evaluate_ast_to_python_val( astnode.value, schema=schema) elif 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) else: raise ValueError( f'unexpected value in attribute: {astnode.value!r}') return cls(property=propname, new_value=new_value)
def _alter_begin(self, schema, context): schema = super()._alter_begin(schema, context) scls = self.scls context.altered_targets.add(scls) # Type alters of pointers used in expressions is prohibited. # Eventually we may be able to relax this by allowing to # alter to the type that is compatible (i.e. does not change) # with all expressions it is used in. vn = scls.get_verbosename(schema) self._prohibit_if_expr_refs(schema, context, action=f'alter the type of {vn}') if not context.canonical: implicit_bases = scls.get_implicit_bases(schema) non_altered_bases = [] tgt = scls.get_target(schema) for base in set(implicit_bases) - context.altered_targets: base_tgt = base.get_target(schema) if not tgt.issubclass(schema, base_tgt): non_altered_bases.append(base) # This pointer is inherited from one or more ancestors that # are not altered in the same op, and this is an error. if non_altered_bases: bases_str = ', '.join( b.get_verbosename(schema, with_parent=True) for b in non_altered_bases) vn = scls.get_verbosename(schema) raise errors.SchemaDefinitionError( f'cannot change the target type of inherited {vn}', details=(f'{vn} is inherited from ' f'{bases_str}'), context=self.source_context, ) if context.enable_recursion: tgt = self.get_attribute_value('target') def _set_type(alter_cmd, refname): s_t = type(self)(classname=alter_cmd.classname, ) s_t.set_attribute_value('target', tgt) alter_cmd.add(s_t) schema = self._propagate_ref_op(schema, context, self.scls, cb=_set_type) else: for op in self.get_subcommands(type=sd.ObjectCommand): schema = op.apply(schema, context) return schema
def _check_expr( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> s_schema.Schema: from edb.ir import ast as irast expression = self.get_attribute_value('expr') assert isinstance(expression, s_expr.Expression) assert isinstance(expression.irast, irast.Statement) required, card = expression.irast.cardinality.to_schema_value() spec_required: Optional[bool] = (self.get_specified_attribute_value( 'required', schema, context)) spec_card: Optional[qltypes.SchemaCardinality] = ( self.get_specified_attribute_value('cardinality', schema, context)) glob_name = self.get_verbosename() if spec_required and not required: srcctx = self.get_attribute_source_context('target') raise errors.SchemaDefinitionError( f'possibly an empty set returned by an ' f'expression for the computed ' f'{glob_name} ' f"explicitly declared as 'required'", context=srcctx) if (spec_card is qltypes.SchemaCardinality.One and card is not qltypes.SchemaCardinality.One): srcctx = self.get_attribute_source_context('target') raise errors.SchemaDefinitionError( f'possibly more than one element returned by an ' f'expression for the computed ' f'{glob_name} ' f"explicitly declared as 'single'", context=srcctx) if spec_card is None: self.set_attribute_value('cardinality', card, computed=True) if spec_required is None: self.set_attribute_value('required', required, computed=True) return schema
def _classname_from_ast(cls, schema, astnode, context): name = super()._classname_from_ast(schema, astnode, context) shortname = sn.shortname_from_fullname(name) if len(shortname.name) > MAX_NAME_LENGTH: raise errors.SchemaDefinitionError( f'link or property name length exceeds the maximum of ' f'{MAX_NAME_LENGTH} characters', context=astnode.context) return name
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 _validate_pointer_def(self, schema, context): """Check that pointer definition is sound.""" referrer_ctx = self.get_referrer_context(context) if referrer_ctx is None: return scls = self.scls if not scls.get_is_local(schema): return default_expr = scls.get_default(schema) if default_expr is not None: if default_expr.irast is None: default_expr = default_expr.compiled(default_expr, schema) default_type = default_expr.irast.stype ptr_target = scls.get_target(schema) set_cmd = self.get_attribute_set_cmd('default') if set_cmd: source_context = set_cmd.source_context else: source_context = None if not default_type.assignment_castable_to(ptr_target, schema): raise errors.SchemaDefinitionError( f'default expression is of invalid type: ' f'{default_type.get_displayname(schema)}, ' f'expected {ptr_target.get_displayname(schema)}', context=source_context, ) ptr_cardinality = scls.get_cardinality(schema) default_cardinality = default_expr.irast.cardinality if (ptr_cardinality is qltypes.Cardinality.ONE and default_cardinality is qltypes.Cardinality.MANY): raise errors.SchemaDefinitionError( f'possibly more than one element returned by ' f'the default expression for ' f'{scls.get_verbosename(schema)} declared as ' f'\'single\'', context=source_context, )
def _classname_from_ast(cls, schema, astnode, context): nqname = cls._get_ast_name(schema, astnode, context) module = context.modaliases.get(astnode.name.module, astnode.name.module) if module is None: raise errors.SchemaDefinitionError( f'unqualified name and no default module set', context=astnode.name.context) return sn.Name(module=module, name=nqname)
def _cmd_tree_from_ast(cls, schema, astnode, context): cmd = super()._cmd_tree_from_ast(schema, astnode, context) if isinstance(astnode, qlast.CreateConcreteLink): cmd._process_create_or_alter_ast(schema, astnode, context) else: # this is an abstract property then if cmd.get_attribute_value('default') is not None: raise errors.SchemaDefinitionError( f"'default' is not a valid field for an abstract link", context=astnode.context) return cmd
def _validate(self, schema: s_schema.Schema, context: sd.CommandContext) -> None: scls = self.scls implicit_bases = [ b for b in scls.get_bases(schema).objects(schema) if not b.generic(schema) ] referrer_ctx = self.get_referrer_context_or_die(context) objcls = self.get_schema_metaclass() referrer_class = referrer_ctx.op.get_schema_metaclass() refdict = referrer_class.get_refdict_for_class(objcls) if context.declarative and scls.get_is_owned(schema): if (implicit_bases and refdict.requires_explicit_overloaded and not self.get_attribute_value('declared_overloaded')): ancestry = [] for obj in implicit_bases: bref = obj.get_referrer(schema) assert bref is not None ancestry.append(bref) raise errors.SchemaDefinitionError( f'{self.scls.get_verbosename(schema, with_parent=True)} ' f'must be declared using the `overloaded` keyword because ' f'it is defined in the following ancestor(s): ' f'{", ".join(a.get_shortname(schema) for a in ancestry)}', context=self.source_context, ) elif (not implicit_bases and self.get_attribute_value('declared_overloaded')): raise errors.SchemaDefinitionError( f'{self.scls.get_verbosename(schema, with_parent=True)}: ' f'cannot be declared `overloaded` as there are no ' f'ancestors defining it.', context=self.source_context, )
def _parse_attr_setters( self, scls, attrdecls: typing.List[qlast.Annotation]): for attrdecl in attrdecls: attr = self._get_ref_obj(attrdecl.name, s_anno.Annotation) value = qlcompiler.evaluate_ast_to_python_val( attrdecl.value, self._schema, modaliases=self._mod_aliases) if not isinstance(value, str): raise errors.SchemaDefinitionError( 'annotation value is not a string', context=attrdecl.value.context) self._schema = scls.set_annotation(self._schema, attr, value)
def _validate_name( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> None: name = self.get_attribute_value('name') if len(str(name)) > s_def.MAX_NAME_LENGTH: source_context = self.get_attribute_source_context('name') raise errors.SchemaDefinitionError( f'Role names longer than {s_def.MAX_NAME_LENGTH} ' f'characters are not supported', context=source_context, )
def _validate_legal_command( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> None: super()._validate_legal_command(schema, context) if (not context.stdmode and not context.testmode and (modname := self.classname) in s_schema.STD_MODULES): raise errors.SchemaDefinitionError( f'cannot {self._delta_action} {self.get_verbosename()}: ' f'module {modname} is read-only', context=self.source_context)
def compile_expr_field( self, schema: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: s_expr.Expression, track_schema_ref_exprs: bool = False, ) -> s_expr.Expression: singletons: List[s_types.Type] if field.name == 'expr': # type ignore below, for the class is used as mixin parent_ctx = context.get_ancestor( IndexSourceCommandContext, # type: ignore self) assert parent_ctx is not None assert isinstance(parent_ctx.op, sd.ObjectCommand) subject = parent_ctx.op.get_object(schema, context) expr = type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, schema_object_context=self.get_schema_metaclass(), anchors={qlast.Subject().name: subject}, path_prefix_anchor=qlast.Subject().name, singletons=frozenset([subject]), apply_query_rewrites=not context.stdmode, track_schema_ref_exprs=track_schema_ref_exprs, ), ) # Check that the inferred cardinality is no more than 1 from edb.ir import ast as ir_ast assert isinstance(expr.irast, ir_ast.Statement) if expr.irast.cardinality.is_multi(): raise errors.ResultCardinalityMismatchError( f'possibly more than one element returned by ' f'the index expression where only singletons ' f'are allowed') if expr.irast.volatility != qltypes.Volatility.Immutable: raise errors.SchemaDefinitionError( f'index expressions must be immutable', context=value.qlast.context, ) return expr else: return super().compile_expr_field(schema, context, field, value, track_schema_ref_exprs)
def merge_readonly( target: Pointer, sources: List[Pointer], field_name: str, *, ignore_local: bool, schema: s_schema.Schema, ) -> Any: current = None current_from = None # The target field value is only relevant if it is explicit, # otherwise it should be based on the inherited value. if not ignore_local: current = target.get_explicit_field_value(schema, field_name, None) if current is not None: current_from = target for source in list(sources): # ignore abstract pointers if source.generic(schema): continue # We want the field value including the default, not just # explicit value. nextval = source.get_field_value(schema, field_name) if nextval is not None: if current is None: current = nextval current_from = source elif current is not nextval: assert current_from is not None tgt_repr = target.get_verbosename( schema, with_parent=True) cf_repr = current_from.get_verbosename( schema, with_parent=True) other_repr = source.get_verbosename( schema, with_parent=True) raise errors.SchemaDefinitionError( f'cannot redefine the readonly flag of ' f'{tgt_repr}: it is defined ' f'as {current} in {cf_repr} and ' f'as {nextval} in {other_repr}.' ) return current
def _parse_computable(self, expr, schema, context) -> so.ObjectRef: from . import sources as s_sources # "source" attribute is set automatically as a refdict back-attr parent_ctx = context.get(s_sources.SourceCommandContext) source_name = parent_ctx.op.classname source = schema.get(source_name, default=None) if source is None: raise errors.SchemaDefinitionError( f'cannot define link/property computables in CREATE TYPE', hint='Perform a CREATE TYPE without the link ' 'followed by ALTER TYPE defining the computable', context=expr.context ) expr = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr, schema, context.modaliases), schema=schema, modaliases=context.modaliases, anchors={qlast.Source: source}, path_prefix_anchor=qlast.Source, singletons=[source], ) target = utils.reduce_to_typeref(schema, expr.irast.stype) self.add( sd.AlterObjectProperty( property='default', new_value=expr, ) ) self.add( sd.AlterObjectProperty( property='computable', new_value=True ) ) self.add( sd.AlterObjectProperty( property='cardinality', new_value=expr.irast.cardinality ) ) return target
def get_struct_properties(self, schema): result = {} metaclass = self.get_schema_metaclass() for op in self.get_subcommands(type=AlterObjectProperty): field = metaclass.get_field(op.property) if field is None: raise errors.SchemaDefinitionError( f'got AlterObjectProperty command for ' f'invalid field: {metaclass.__name__}.{op.property}') result[op.property] = self._resolve_attr_value( op.new_value, op.property, field, schema) return result
def _cmd_tree_from_ast( cls, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> sd.Command: cmd = super()._cmd_tree_from_ast(schema, astnode, context) if isinstance(astnode, qlast.CreateConcreteLink): assert isinstance(cmd, pointers.PointerCommand) cmd._process_create_or_alter_ast(schema, astnode, context) else: # this is an abstract property then if cmd.get_attribute_value('default') is not None: raise errors.SchemaDefinitionError( f"'default' is not a valid field for an abstract link", context=astnode.context) assert isinstance(cmd, sd.Command) return cmd
def _create_begin( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> s_schema.Schema: schema = super()._create_begin(schema, context) # Validate that the database name is fewer than 64 characters name = self.get_attribute_value('name') if len(str(name)) > s_def.MAX_NAME_LENGTH: source_context = self.get_attribute_source_context('name') raise errors.SchemaDefinitionError( f'Database names longer than {s_def.MAX_NAME_LENGTH} ' f'characters are not supported', context=source_context, ) return schema
def _validate_legal_command(self, schema, context): from . import functions as s_functions if (not context.stdmode and not context.testmode and not isinstance(self, s_functions.ParameterCommand)): if isinstance(self.classname, sn.Name): shortname = sn.shortname_from_fullname(self.classname) modname = self.classname.module else: # modules have classname as simple strings shortname = modname = self.classname if modname in s_schema.STD_MODULES: raise errors.SchemaDefinitionError( f'cannot {self._delta_action} `{shortname}`: ' f'module {modname} is read-only', context=self.source_context)