def validate_object( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> None: """Check that link definition is sound.""" super().validate_object(schema, context) scls = self.scls assert isinstance(scls, Link) if not scls.get_owned(schema): return target = scls.get_target(schema) assert target is not None if not target.is_object_type(): srcctx = self.get_attribute_source_context('target') raise errors.InvalidLinkTargetError( f'invalid link target, expected object type, got ' f'{target.get_schema_class_displayname()}', context=srcctx, ) if (not scls.is_pure_computable(schema) and not scls.get_from_alias(schema) and target.is_view(schema)): srcctx = self.get_attribute_source_context('target') raise errors.InvalidLinkTargetError( f'invalid link type: {target.get_displayname(schema)!r}' f' is an expression alias, not a proper object type', context=srcctx, )
def _normalize_ptr_default(self, expr, source, ptr, ptrdecl): module_aliases = {None: source.get_name(self._schema).module} ir, _, expr_text = qlutils.normalize_tree( expr, self._schema, modaliases=module_aliases, anchors={qlast.Source: source}, singletons=[source]) expr_type = ir.stype self._schema = ptr.set_field_value(self._schema, 'default', expr_text) if ptr.is_pure_computable(self._schema): # Pure computable without explicit target. # Fixup pointer target and target property. self._schema = ptr.set_field_value(self._schema, 'target', expr_type) if isinstance(ptr, s_links.Link): if not isinstance(expr_type, s_objtypes.ObjectType): raise errors.InvalidLinkTargetError( f'invalid link target, expected object type, got ' f'{expr_type.__class__.__name__}', context=ptrdecl.expr.context) else: if not isinstance(expr_type, (s_scalars.ScalarType, s_types.Collection)): raise errors.InvalidPropertyTargetError( f'invalid property target, expected primitive type, ' f'got {expr_type.__class__.__name__}', context=ptrdecl.expr.context) if isinstance(ptr, s_links.Link): tgt_prop = ptr.getptr(self._schema, 'target') self._schema = tgt_prop.set_field_value( self._schema, 'target', expr_type) self._schema = ptr.set_field_value(self._schema, 'cardinality', ir.cardinality) if ptrdecl.cardinality is not ptr.get_cardinality(self._schema): if ptrdecl.cardinality is qlast.Cardinality.ONE: raise errors.SchemaError( f'computable expression possibly returns more than ' f'one value, but the {ptr.schema_class_displayname!r} ' f'is declared as "single"', context=expr.context) if (not isinstance(expr_type, s_abc.Type) or (ptr.get_target(self._schema) is not None and not expr_type.issubclass( self._schema, ptr.get_target(self._schema)))): raise errors.SchemaError( 'default value query must yield a single result of ' 'type {!r}'.format( ptr.get_target(self._schema).get_name(self._schema)), context=expr.context)
def _interpret_wrong_object_type(code, schema, err_details, hint): if err_details.message == 'covariance error': ptr = schema.get_by_id(uuidgen.UUID(err_details.column_name)) wrong_obj = schema.get_by_id(uuidgen.UUID(err_details.table_name)) vn = ptr.get_verbosename(schema, with_parent=True) return errors.InvalidLinkTargetError( f"invalid target for {vn}: '{wrong_obj.get_name(schema)}'" f" (expecting '{ptr.get_target(schema).get_name(schema)}')") return errors.InternalServerError(err_details.message)
def _validate_pointer_def(self, schema, context): """Check that link definition is sound.""" super()._validate_pointer_def(schema, context) scls = self.scls if not scls.get_is_local(schema): return target = scls.get_target(schema) if not target.is_object_type(): srcctx = self.get_attribute_source_context('target') raise errors.InvalidLinkTargetError( f'invalid link target, expected object type, got ' f'{target.get_schema_class_displayname()}', context=srcctx, )
def _validate_pointer_def( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> None: """Check that link definition is sound.""" super()._validate_pointer_def(schema, context) scls = self.scls assert isinstance(scls, Link) if not scls.get_is_local(schema): return target = scls.get_target(schema) assert target is not None if not target.is_object_type(): srcctx = self.get_attribute_source_context('target') raise errors.InvalidLinkTargetError( f'invalid link target, expected object type, got ' f'{target.get_schema_class_displayname()}', context=srcctx, )
def _init_objtypes(self, objtypes): for objtype, objtypedecl in objtypes.items(): self._parse_source_props(objtype, objtypedecl) if objtypedecl.fields: self._parse_field_setters(objtype, objtypedecl.fields) for linkdecl in objtypedecl.links: link_name = linkdecl.name if len(link_name) > s_pointers.MAX_NAME_LENGTH: raise errors.SchemaDefinitionError( f'link or property name length exceeds the maximum of ' f'{s_pointers.MAX_NAME_LENGTH} characters', context=linkdecl.context) if linkdecl.extends: link_bases = [ self._get_ref_type(b) for b in linkdecl.extends ] else: link_bases = [ self._schema.get(s_links.Link.get_default_base_name()) ] if linkdecl.expr is not None: # This is a computable, but we cannot interpret # the expression yet, so set the target to `any` # temporarily. _targets = [s_pseudo.Any.instance] else: _targets = [self._get_ref_type(t) for t in linkdecl.target] if len(_targets) == 1: # Usual case, just one target spectargets = None target = _targets[0] else: # Multiple explicit targets, create common virtual # parent and use it as target. spectargets = s_obj.ObjectSet.create( self._schema, _targets) self._schema, target = s_inh.create_virtual_parent( self._schema, _targets, module_name=self._module.get_name(self._schema)) self._schema = target.set_field_value( self._schema, 'is_derived', True) if (not target.is_any() and not isinstance(target, s_objtypes.ObjectType)): raise errors.InvalidLinkTargetError( f'invalid link target, expected object type, got ' f'{target.__class__.__name__}', context=linkdecl.target[0].context ) new_props = { 'sourcectx': linkdecl.context, } name = self._get_derived_ptr_name(link_name, objtype) self._schema, link = link_bases[0].derive( self._schema, objtype, target, attrs=new_props, merge_bases=link_bases, apply_defaults=not linkdecl.inherited, name=name) if linkdecl.cardinality is None: if linkdecl.expr is None: cardinality = qltypes.Cardinality.ONE else: cardinality = None else: cardinality = linkdecl.cardinality self._schema = link.update(self._schema, { 'spectargets': spectargets, 'required': bool(linkdecl.required), 'cardinality': cardinality, 'declared_inherited': linkdecl.inherited, }) if linkdecl.on_target_delete is not None: self._schema = link.set_field_value( self._schema, 'on_target_delete', linkdecl.on_target_delete.cascade) if linkdecl.expr is not None: self._schema = link.set_field_value( self._schema, 'computable', True) self._parse_source_props(link, linkdecl) self._schema = objtype.add_pointer(self._schema, link) for objtype, objtypedecl in objtypes.items(): if objtypedecl.indexes: self._parse_subject_indexes(objtype, objtypedecl) if objtypedecl.constraints: self._parse_subject_constraints(objtype, objtypedecl)
def _process_link_create_or_alter(cls, schema, astnode, context, cmd): from . import objtypes as s_objtypes parent_ctx = context.get(LinkSourceCommandContext) if isinstance(astnode, qlast.CreateConcreteLink): # "source" attribute is set automatically as a refdict back-attr source_name = parent_ctx.op.classname if astnode.is_required is not None: cmd.set_attribute_value('required', astnode.is_required) if astnode.cardinality is not None: cmd.set_attribute_value('cardinality', astnode.cardinality) # FIXME: this is an approximate solution targets = qlast.get_targets(astnode.target) if len(targets) > 1: new_targets = [ utils.ast_to_typeref( t, modaliases=context.modaliases, schema=schema) for t in targets ] target = cls._create_union_target( schema, context, new_targets, module=source_name.module) else: target_expr = targets[0] if isinstance(target_expr, qlast.TypeName): target = utils.ast_to_typeref( target_expr, modaliases=context.modaliases, schema=schema) else: # computable target, base = cmd._parse_computable( target_expr, schema, context) if base is not None: cmd.set_attribute_value( 'bases', so.ObjectList.create(schema, [base]), ) cmd.set_attribute_value( 'is_derived', True ) if context.declarative: cmd.set_attribute_value( 'declared_inherited', True ) if (isinstance(target, so.ObjectRef) and target.name == source_name): # Special case for loop links. Since the target # is the same as the source, we know it's a proper # type. pass else: target_type = utils.resolve_typeref(target, schema=schema) if not isinstance(target_type, s_objtypes.ObjectType): raise errors.InvalidLinkTargetError( f'invalid link target, expected object type, got ' f'{target_type.__class__.__name__}', context=astnode.target.context ) if isinstance(cmd, sd.CreateObject): cmd.set_attribute_value('target', target) if cmd.get_attribute_value('cardinality') is None: cmd.set_attribute_value( 'cardinality', qltypes.Cardinality.ONE) if cmd.get_attribute_value('required') is None: cmd.set_attribute_value( 'required', False) else: slt = SetLinkType(classname=cmd.classname, type=target) slt.set_attribute_value('target', target) cmd.add(slt) cls._parse_default(cmd) if parent_ctx is None: # this is an abstract link then if cmd.get_attribute_value('default') is not None: raise errors.SchemaDefinitionError( f"'default' is not a valid field for an abstact link", context=astnode.context)
def _init_objtypes(self, objtypes): for objtype, objtypedecl in objtypes.items(): self._parse_source_props(objtype, objtypedecl) if objtypedecl.fields: self._parse_field_setters(objtype, objtypedecl.fields) for linkdecl in objtypedecl.links: link_name = self._get_ref_name(linkdecl.name) if len(link_name) > s_pointers.MAX_NAME_LENGTH: raise errors.SchemaDefinitionError( f'link or property name length exceeds the maximum of ' f'{s_pointers.MAX_NAME_LENGTH} characters', context=linkdecl.context) link_base = self._schema.get(link_name, type=s_links.Link, default=None, module_aliases=self._mod_aliases) if link_base is None: # The link has not been defined globally. if not s_name.Name.is_qualified(link_name): # If the name is not fully qualified, assume inline # link definition. The only attribute that is used for # global definition is the name. link_qname = s_name.Name(name=link_name, module=objtype.get_name( self._schema).module) std_link = self._schema.get( s_links.Link.get_default_base_name(), module_aliases=self._mod_aliases) self._schema, link_base = \ s_links.Link.create_in_schema( self._schema, name=link_qname, bases=[std_link]) else: link_qname = s_name.Name(link_name) else: link_qname = link_base.get_name(self._schema) if linkdecl.expr is not None: # This is a computable, but we cannot interpret # the expression yet, so set the target to `any` # temporarily. _targets = [s_pseudo.Any.create()] else: _targets = [self._get_ref_type(t) for t in linkdecl.target] if len(_targets) == 1: # Usual case, just one target spectargets = None target = _targets[0] else: # Multiple explicit targets, create common virtual # parent and use it as target. spectargets = s_obj.ObjectSet.create( self._schema, _targets) self._schema, target = link_base.create_common_target( self._schema, _targets) if (not target.is_any() and not isinstance(target, s_objtypes.ObjectType)): raise errors.InvalidLinkTargetError( f'invalid link target, expected object type, got ' f'{target.__class__.__name__}', context=linkdecl.target[0].context) new_props = { 'sourcectx': linkdecl.context, } self._schema, link = link_base.derive( self._schema, objtype, target, attrs=new_props, apply_defaults=not linkdecl.inherited) self._schema = link.update( self._schema, { 'spectargets': spectargets, 'required': bool(linkdecl.required), 'cardinality': linkdecl.cardinality, 'declared_inherited': linkdecl.inherited, }) if linkdecl.on_target_delete is not None: self._schema = link.set_field_value( self._schema, 'on_target_delete', linkdecl.on_target_delete.cascade) if linkdecl.expr is not None: self._schema = link.set_field_value( self._schema, 'computable', True) self._parse_source_props(link, linkdecl) self._schema = objtype.add_pointer(self._schema, link) for objtype, objtypedecl in objtypes.items(): if objtypedecl.indexes: self._parse_subject_indexes(objtype, objtypedecl) if objtypedecl.constraints: self._parse_subject_constraints(objtype, objtypedecl)
def interpret_backend_error(schema, fields): err_details = get_error_details(fields) hint = None details = None if err_details.detail_json: hint = err_details.detail_json.get('hint') # all generic errors are static and have been handled by this point if err_details.code == pgerrors.ERROR_NOT_NULL_VIOLATION: colname = err_details.column_name if colname: if colname.startswith('??'): ptr_id, *_ = colname[2:].partition('_') else: ptr_id = colname pointer = common.get_object_from_backend_name( schema, s_pointers.Pointer, ptr_id) pname = pointer.get_verbosename(schema, with_parent=True) else: pname = None if pname is not None: if err_details.detail_json: object_id = err_details.detail_json.get('object_id') if object_id is not None: details = f'Failing object id is {str(object_id)!r}.' return errors.MissingRequiredError( f'missing value for required {pname}', details=details, hint=hint, ) else: return errors.InternalServerError(err_details.message) elif err_details.code in constraint_errors: error_type = None match = None for errtype, ere in constraint_res.items(): m = ere.match(err_details.message) if m: error_type = errtype match = m break # no need for else clause since it would have been handled by # the static version if error_type == 'constraint': # similarly, if we're here it's because we have a constraint_id constraint_id, _, _ = err_details.constraint_name.rpartition(';') constraint_id = uuidgen.UUID(constraint_id) constraint = schema.get_by_id(constraint_id) return errors.ConstraintViolationError( constraint.format_error_message(schema)) elif error_type == 'newconstraint': # If we're here, it means that we already validated that # schema_name, table_name and column_name all exist. tabname = (err_details.schema_name, err_details.table_name) source = common.get_object_from_backend_name( schema, s_objtypes.ObjectType, tabname) source_name = source.get_displayname(schema) pointer = common.get_object_from_backend_name( schema, s_pointers.Pointer, err_details.column_name) pointer_name = pointer.get_shortname(schema).name return errors.ConstraintViolationError( f'Existing {source_name}.{pointer_name} ' f'values violate the new constraint') elif error_type == 'scalar': domain_name = match.group(1) stype_name = types.base_type_name_map_r.get(domain_name) if stype_name: if match.group(2) in range_constraints: msg = f'{str(stype_name)!r} value out of range' else: msg = f'invalid value for scalar type {str(stype_name)!r}' else: msg = translate_pgtype(schema, err_details.message) return errors.InvalidValueError(msg) elif err_details.code == pgerrors.ERROR_INVALID_TEXT_REPRESENTATION: return errors.InvalidValueError( translate_pgtype(schema, err_details.message)) elif err_details.code == pgerrors.ERROR_NUMERIC_VALUE_OUT_OF_RANGE: return errors.NumericOutOfRangeError( translate_pgtype(schema, err_details.message)) elif err_details.code in {pgerrors.ERROR_INVALID_DATETIME_FORMAT, pgerrors.ERROR_DATETIME_FIELD_OVERFLOW}: return errors.InvalidValueError( translate_pgtype(schema, err_details.message), hint=hint) elif ( err_details.code == pgerrors.ERROR_WRONG_OBJECT_TYPE and err_details.message == 'covariance error' ): ptr = schema.get_by_id(uuidgen.UUID(err_details.column_name)) wrong_obj = schema.get_by_id(uuidgen.UUID(err_details.table_name)) vn = ptr.get_verbosename(schema, with_parent=True) return errors.InvalidLinkTargetError( f"invalid target for {vn}: '{wrong_obj.get_name(schema)}'" f" (expecting '{ptr.get_target(schema).get_name(schema)}')" ) return errors.InternalServerError(err_details.message)
def _cmd_tree_from_ast(cls, schema, astnode, context): from . import objtypes as s_objtypes cmd = super()._cmd_tree_from_ast(schema, astnode, context) if isinstance(astnode, qlast.CreateConcreteLink): cmd.add( sd.AlterObjectProperty( property='required', new_value=astnode.is_required ) ) cmd.add( sd.AlterObjectProperty( property='cardinality', new_value=astnode.cardinality ) ) # "source" attribute is set automatically as a refdict back-attr parent_ctx = context.get(LinkSourceCommandContext) source_name = parent_ctx.op.classname target_type = None # FIXME: this is an approximate solution targets = qlast.get_targets(astnode.target) if len(targets) > 1: cmd.add( sd.AlterObjectProperty( property='spectargets', new_value=so.ObjectList([ utils.ast_to_typeref( t, modaliases=context.modaliases, schema=schema) for t in targets ]) ) ) target_name = sources.Source.gen_virt_parent_name( (sn.Name(module=t.maintype.module, name=t.maintype.name) for t in targets), module=source_name.module ) target = so.ObjectRef(name=target_name) create_virt_parent = s_objtypes.CreateObjectType( classname=target_name, metaclass=s_objtypes.ObjectType ) create_virt_parent.update(( sd.AlterObjectProperty( property='bases', new_value=so.ObjectList([ so.ObjectRef(name=sn.Name( module='std', name='Object' )) ]) ), sd.AlterObjectProperty( property='name', new_value=target_name ), sd.AlterObjectProperty( property='is_virtual', new_value=True ) )) delta_ctx = context.get(sd.DeltaRootContext) for cc in delta_ctx.op.get_subcommands( type=s_objtypes.CreateObjectType): if cc.classname == create_virt_parent.classname: break else: delta_ctx.op.add(create_virt_parent) else: target_expr = targets[0] if isinstance(target_expr, qlast.TypeName): target = utils.ast_to_typeref( target_expr, modaliases=context.modaliases, schema=schema) else: # computable target = cmd._parse_computable( target_expr, schema, context) if (isinstance(target, so.ObjectRef) and target.name == source_name): # Special case for loop links. Since the target # is the same as the source, we know it's a proper # type. pass else: if target_type is None: target_type = utils.resolve_typeref(target, schema=schema) if not isinstance(target_type, s_objtypes.ObjectType): raise errors.InvalidLinkTargetError( f'invalid link target, expected object type, got ' f'{target_type.__class__.__name__}', context=astnode.target.context ) cmd.add( sd.AlterObjectProperty( property='target', new_value=target ) ) base_prop_name = sn.Name('std::source') s_name = sn.get_specialized_name(base_prop_name, cmd.classname) src_prop_name = sn.Name(name=s_name, module=cmd.classname.module) src_prop = lproperties.CreateProperty( classname=src_prop_name, metaclass=lproperties.Property ) src_prop.update(( sd.AlterObjectProperty( property='name', new_value=src_prop_name ), sd.AlterObjectProperty( property='bases', new_value=[ so.ObjectRef( name=base_prop_name ) ] ), sd.AlterObjectProperty( property='source', new_value=so.ObjectRef( name=cmd.classname ) ), sd.AlterObjectProperty( property='target', new_value=so.ObjectRef( name=source_name ) ), sd.AlterObjectProperty( property='required', new_value=True ), sd.AlterObjectProperty( property='readonly', new_value=True ), )) cmd.add(src_prop) base_prop_name = sn.Name('std::target') s_name = sn.get_specialized_name(base_prop_name, cmd.classname) tgt_prop_name = sn.Name(name=s_name, module=cmd.classname.module) tgt_prop = lproperties.CreateProperty( classname=tgt_prop_name, metaclass=lproperties.Property ) tgt_prop.update(( sd.AlterObjectProperty( property='name', new_value=tgt_prop_name ), sd.AlterObjectProperty( property='bases', new_value=[ so.ObjectRef( name=base_prop_name ) ] ), sd.AlterObjectProperty( property='source', new_value=so.ObjectRef( name=cmd.classname ) ), sd.AlterObjectProperty( property='target', new_value=target ), sd.AlterObjectProperty( property='required', new_value=False ), sd.AlterObjectProperty( property='readonly', new_value=True ), )) cmd.add(tgt_prop) cls._parse_default(cmd) return cmd