Example #1
0
def compile_TypeName(
        node: qlast.TypeName,
        *,
        schema: s_schema.Schema,
        modaliases: Mapping[Optional[str], str],
        localnames: AbstractSet[str] = frozenset(),
) -> None:

    # Resolve the main type
    if isinstance(node.maintype, qlast.ObjectRef):
        # This is a specific path root, resolve it.
        if (not node.maintype.module and
                # maintype names 'array' and 'tuple' specifically
                # should also be ignored
                node.maintype.name not in {'array', 'tuple', *localnames}):
            maintype = schema.get(
                node.maintype.name,
                default=None,
                module_aliases=modaliases,
            )

            if maintype is not None:
                node.maintype.module = maintype.get_name(schema).module
            elif None in modaliases:
                # Even if the name was not resolved in the schema it
                # may be the name of the object being defined, as such
                # the default module should be used. Names that must
                # be ignored (like aliases and parameters) have
                # already been filtered by the localnames.
                node.maintype.module = modaliases[None]

    if node.subtypes is not None:
        for st in node.subtypes:
            normalize(
                st,
                schema=schema,
                modaliases=modaliases,
                localnames=localnames,
            )
Example #2
0
def scalar_type_to_python_type(stype: s_types.Type,
                               schema: s_schema.Schema) -> type:

    typemap = {
        'std::str': str,
        'std::anyint': int,
        'std::anyfloat': float,
        'std::decimal': decimal.Decimal,
        'std::bigint': decimal.Decimal,
        'std::bool': bool,
        'std::json': str,
        'std::uuid': uuidgen.UUID,
    }

    for basetype_name, python_type in typemap.items():
        basetype = schema.get(basetype_name)
        assert isinstance(basetype, s_inh.InheritingObject)
        if stype.issubclass(schema, basetype):
            return python_type

    raise UnsupportedExpressionError(
        f'{stype.get_displayname(schema)} is not representable in Python')
Example #3
0
def compile_FunctionCall(
        node: qlast.FunctionCall,
        *,
        schema: s_schema.Schema,
        modaliases: Mapping[Optional[str], str],
        localnames: AbstractSet[str] = frozenset(),
) -> None:

    if isinstance(node.func, str) and node.func not in localnames:
        funcs = schema.get_functions(node.func,
                                     default=tuple(),
                                     module_aliases=modaliases)
        if funcs:
            # As long as we found some functions, they will be from
            # the same module (the first valid resolved module for the
            # function name will mask "std").
            _, fname = funcs[0].get_name(schema).as_tuple()
            _, module, name = sn.split_name(sn.shortname_from_fullname(fname))
            node.func = (module, name)

        # It's odd we don't find a function, but this will be picked up
        # by the compiler with a more appropriate error message.

    for arg in node.args:
        normalize(
            arg,
            schema=schema,
            modaliases=modaliases,
            localnames=localnames,
        )

    for val in node.kwargs.values():
        normalize(
            val,
            schema=schema,
            modaliases=modaliases,
            localnames=localnames,
        )
Example #4
0
def is_castable(
    schema: s_schema.Schema,
    source: s_types.Type,
    target: s_types.Type,
) -> bool:

    # Implicitly castable
    if is_implicitly_castable(schema, source, target):
        return True

    elif is_assignment_castable(schema, source, target):
        return True

    else:
        casts = schema.get_casts_to_type(target)
        if not casts:
            return False
        else:
            for c in casts:
                if c.get_from_type(schema) == source:
                    return True
            else:
                return False
Example #5
0
    def format_error_message(
        self,
        schema: s_schema.Schema,
        subjtitle: str,
    ) -> str:
        errmsg = self.get_errmessage(schema)
        args = self.get_args(schema)
        if args:
            args_ql: List[qlast.Base] = [
                qlast.Path(steps=[qlast.ObjectRef(name=subjtitle)]),
            ]

            args_ql.extend(arg.qlast 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
Example #6
0
def _is_reachable(
    schema: s_schema.Schema,
    cast_kwargs: Mapping[str, bool],
    source: s_types.Type,
    target: s_types.Type,
    distance: int,
) -> int:

    if source == target:
        return distance

    casts = schema.get_casts_to_type(target, **cast_kwargs)
    if not casts:
        return _NOT_REACHABLE

    sources = {c.get_from_type(schema) for c in casts}

    distance += 1
    if source in sources:
        return distance
    else:
        return min(
            _is_reachable(schema, cast_kwargs, source, s, distance)
            for s in sources)
Example #7
0
def compile_Path(
        node: qlast.Path,
        *,
        schema: s_schema.Schema,
        modaliases: Mapping[Optional[str], str],
        localnames: AbstractSet[str] = frozenset(),
) -> None:

    for step in node.steps:
        if isinstance(step, (qlast.Expr, qlast.TypeIntersection)):
            normalize(
                step,
                schema=schema,
                modaliases=modaliases,
                localnames=localnames,
            )
        elif isinstance(step, qlast.ObjectRef):
            # This is a specific path root, resolve it.
            if not step.module and step.name not in localnames:
                obj = schema.get(
                    step.name,
                    default=None,
                    module_aliases=modaliases,
                )
                if obj is not None:
                    name = obj.get_name(schema)
                    assert isinstance(name, sn.QualName)
                    step.module = name.module
                elif None in modaliases:
                    # Even if the name was not resolved in the
                    # schema it may be the name of the object
                    # being defined, as such the default module
                    # should be used. Names that must be ignored
                    # (like aliases and parameters) have already
                    # been filtered by the localnames.
                    step.module = modaliases[None]
Example #8
0
    def _cmd_tree_from_ast(
        cls,
        schema: s_schema.Schema,
        astnode: qlast.DDLOperation,
        context: sd.CommandContext,
    ) -> CreateConstraint:
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        if isinstance(astnode, qlast.CreateConcreteConstraint):
            if astnode.delegated:
                cmd.set_attribute_value('delegated', astnode.delegated)

            args = cls._constraint_args_from_ast(schema, astnode, context)
            if args:
                cmd.set_attribute_value('args', 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.set_attribute_value(
                'return_type',
                schema.get('std::bool'),
            )

        if cmd.get_attribute_value('return_typemod') is None:
            cmd.set_attribute_value(
                'return_typemod',
                ft.TypeModifier.SINGLETON,
            )

        assert isinstance(
            astnode, (qlast.CreateConstraint, qlast.CreateConcreteConstraint))
        # 'subjectexpr' can be present in either astnode type
        if astnode.subjectexpr:
            orig_text = cls.get_orig_expr_text(schema, astnode, 'subjectexpr')

            subjectexpr = s_expr.Expression.from_ast(
                astnode.subjectexpr,
                schema,
                context.modaliases,
                orig_text=orig_text,
            )

            cmd.set_attribute_value(
                'subjectexpr',
                subjectexpr,
            )

        cls._validate_subcommands(astnode)
        assert isinstance(cmd, CreateConstraint)
        return cmd
Example #9
0
def parse_into(
    schema: s_schema.Schema,
    data: Sequence[str],
    schema_class_layout: Dict[Type[s_obj.Object], sr_struct.SchemaTypeLayout],
) -> s_schema.Schema:
    """Parse JSON-encoded schema objects and populate the schema with them.

    Args:
        schema:
            A schema instance to use as a starting point.
        data:
            A sequence of JSON-encoded schema object data as returned
            by an introspection query.
        schema_class_layout:
            A mapping describing schema class layout in the reflection,
            as returned from
            :func:`schema.reflection.structure.generate_structure`.

    Returns:
        A schema instance including objects encoded in the provided
        JSON sequence.
    """

    id_to_type = {}
    id_to_data = {}
    name_to_id = {}
    shortname_to_id = collections.defaultdict(set)
    globalname_to_id = {}
    dict_of_dicts: Callable[
        [],
        Dict[Tuple[Type[s_obj.Object], str], Dict[uuid.UUID, None]],
    ] = functools.partial(collections.defaultdict, dict)  # type: ignore
    refs_to: Dict[
        uuid.UUID,
        Dict[Tuple[Type[s_obj.Object], str], Dict[uuid.UUID, None]]
    ] = collections.defaultdict(dict_of_dicts)

    objects: Dict[uuid.UUID, Tuple[s_obj.Object, Dict[str, Any]]] = {}

    for entry_json in data:
        entry = json.loads(entry_json)
        _, _, clsname = entry['_tname'].rpartition('::')
        mcls = s_obj.ObjectMeta.get_schema_metaclass(clsname)
        if mcls is None:
            raise ValueError(
                f'unexpected type in schema reflection: {clsname}')
        objid = uuidgen.UUID(entry['id'])
        objects[objid] = (mcls._create_from_id(objid), entry)

    refdict_updates = {}

    for objid, (obj, entry) in objects.items():
        mcls = type(obj)
        name = entry['name__internal']
        layout = schema_class_layout[mcls]

        if isinstance(obj, s_obj.QualifiedObject):
            name = s_name.Name(name)
            name_to_id[name] = objid
        else:
            globalname_to_id[mcls, name] = objid

        if isinstance(obj, (s_func.Function, s_oper.Operator)):
            shortname = mcls.get_shortname_static(name)
            shortname_to_id[mcls, shortname].add(objid)

        id_to_type[objid] = obj

        objdata: Dict[str, Any] = {}
        val: Any

        for k, v in entry.items():
            desc = layout.get(k)
            if desc is None:
                continue

            fn = desc.fieldname

            if desc.storage is not None:
                if v is None:
                    pass
                elif desc.storage.ptrkind == 'link':
                    refid = uuidgen.UUID(v['id'])
                    newobj = objects.get(refid)
                    if newobj is not None:
                        val = newobj[0]
                    else:
                        val = schema.get_by_id(refid)
                    objdata[fn] = val
                    refs_to[val.id][mcls, fn][objid] = None

                elif desc.storage.ptrkind == 'multi link':
                    ftype = mcls.get_field(fn).type
                    if issubclass(ftype, s_obj.ObjectDict):
                        refids = ftype._container(
                            uuidgen.UUID(e['value']) for e in v)
                        val = ftype(refids, _private_init=True)
                        val._keys = tuple(e['name'] for e in v)
                    else:
                        refids = ftype._container(
                            uuidgen.UUID(e['id']) for e in v)
                        val = ftype(refids, _private_init=True)
                    objdata[fn] = val
                    for refid in refids:
                        refs_to[refid][mcls, fn][objid] = None

                elif desc.storage.shadow_ptrkind:
                    val = entry[f'{k}__internal']
                    ftype = mcls.get_field(fn).type
                    if val is not None and type(val) is not ftype:
                        if issubclass(ftype, s_expr.Expression):
                            val = _parse_expression(val)
                            for refid in val.refs.ids(schema):
                                refs_to[refid][mcls, fn][objid] = None
                        elif issubclass(ftype, s_expr.ExpressionList):
                            exprs = []
                            for e_dict in val:
                                e = _parse_expression(e_dict)
                                assert e.refs is not None
                                for refid in e.refs.ids(schema):
                                    refs_to[refid][mcls, fn][objid] = None
                                exprs.append(e)
                            val = ftype(exprs)
                        else:
                            val = ftype(val)
                    objdata[fn] = val

                else:
                    ftype = mcls.get_field(fn).type
                    if type(v) is not ftype:
                        objdata[fn] = ftype(v)
                    else:
                        objdata[fn] = v

            elif desc.is_refdict:
                ftype = mcls.get_field(fn).type
                refids = ftype._container(uuidgen.UUID(e['id']) for e in v)
                for refid in refids:
                    refs_to[refid][mcls, fn][objid] = None

                val = ftype(refids, _private_init=True)
                objdata[fn] = val
                if desc.properties:
                    for e_dict in v:
                        refdict_updates[uuidgen.UUID(e_dict['id'])] = {
                            p: pv for p in desc.properties
                            if (pv := e_dict[f'@{p}']) is not None
                        }

        id_to_data[objid] = immutables.Map(objdata)

    for objid, updates in refdict_updates.items():
        if updates:
            id_to_data[objid] = id_to_data[objid].update(updates)

    with schema._refs_to.mutate() as mm:
        for referred_id, refdata in refs_to.items():
            try:
                refs = mm[referred_id]
            except KeyError:
                refs = immutables.Map((
                    (k, immutables.Map(r)) for k, r in refdata.items()
                ))
            else:
                refs_update = {}
                for k, referrers in refdata.items():
                    try:
                        rt = refs[k]
                    except KeyError:
                        rt = immutables.Map(referrers)
                    else:
                        rt = rt.update(referrers)
                    refs_update[k] = rt

                refs = refs.update(refs_update)

            mm[referred_id] = refs

    schema = schema._replace(
        id_to_type=schema._id_to_type.update(id_to_type),
        id_to_data=schema._id_to_data.update(id_to_data),
        name_to_id=schema._name_to_id.update(name_to_id),
        shortname_to_id=schema._shortname_to_id.update(
            (k, frozenset(v)) for k, v in shortname_to_id.items()
        ),
        globalname_to_id=schema._globalname_to_id.update(globalname_to_id),
        refs_to=mm.finish(),
    )

    return schema
Example #10
0
    def _populate_concrete_constraint_attrs(
            self,
            schema: s_schema.Schema,
            subject_obj: Optional[so.Object],
            *,
            name: str,
            subjectexpr: Optional[s_expr.Expression] = None,
            sourcectx: Optional[c_parsing.ParserContext] = None,
            args: Any = None,
            **kwargs: Any) -> None:
        from edb.ir import ast as ir_ast
        from edb.ir import utils as ir_utils

        constr_base = schema.get(name, type=Constraint)

        orig_subjectexpr = subjectexpr
        orig_subject = subject_obj
        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(
                f'subjectexpr is already defined for {name!r}')

        if (isinstance(subject_obj, s_scalars.ScalarType)
                and constr_base.get_is_aggregate(schema)):
            raise errors.InvalidConstraintDefinitionError(
                f'{constr_base.get_verbosename(schema)} may not '
                f'be used on scalar types')

        if subjectexpr is not None:
            subject_ql = subjectexpr.qlast
            subject = subject_ql
        else:
            subject = subject_obj

        expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr')
        if not expr:
            raise errors.InvalidConstraintDefinitionError(
                f'missing constraint expression in {name!r}')

        # Re-parse instead of using expr.qlast, because we mutate
        # the AST below.
        expr_ql = qlparser.parse(expr.text)

        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
                inherited['subjectexpr'] = True

        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
            assert isinstance(subject, qlast.Base)
            qlutils.inline_anchors(expr_ql,
                                   anchors={qlast.Subject().name: subject})
            subject = orig_subject

        if args:
            args_ql: List[qlast.Base] = [
                qlast.Path(steps=[qlast.Subject()]),
            ]
            args_ql.extend(arg.qlast 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)

        attrs['args'] = args

        if expr == '__subject__':
            expr_context = sourcectx
        else:
            expr_context = None

        assert subject is not None
        final_expr = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr_ql, schema, {}),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                anchors={qlast.Subject().name: subject}, ),
        )

        bool_t: s_scalars.ScalarType = schema.get('std::bool')
        assert isinstance(final_expr.irast, ir_ast.Statement)

        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_verbosename(schema)}',
                context=expr_context)

        if (subjectexpr is not None and isinstance(subject_obj, s_types.Type)
                and subject_obj.is_object_type()):
            final_subjectexpr = s_expr.Expression.compiled(
                subjectexpr,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    anchors={qlast.Subject().name: subject},
                    singletons=frozenset({subject_obj}),
                ),
            )
            assert isinstance(final_subjectexpr.irast, ir_ast.Statement)

            if final_subjectexpr.irast.cardinality.is_multi():
                refs = ir_utils.get_longest_paths(final_expr.irast)
                if len(refs) > 1:
                    raise errors.InvalidConstraintDefinitionError(
                        "Constraint with multi cardinality may not "
                        "reference multiple links or properties",
                        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)
        attrs['is_abstract'] = False

        for k, v in attrs.items():
            self.set_attribute_value(k, v, inherited=bool(inherited.get(k)))
Example #11
0
def object_type_to_python_type(
        objtype: s_types.Type,
        schema: s_schema.Schema,
        *,
        base_class: typing.Optional[type] = None,
        _memo: typing.Optional[typing.Mapping[s_types.Type,
                                              type]] = None) -> type:

    if _memo is None:
        _memo = {}

    fields = []
    subclasses = []

    for pn, p in objtype.get_pointers(schema).items(schema):
        if pn in ('id', '__type__'):
            continue

        ptype = p.get_target(schema)

        if ptype.is_object_type():
            pytype = _memo.get(ptype)
            if pytype is None:
                pytype = object_type_to_python_type(ptype,
                                                    schema,
                                                    base_class=base_class,
                                                    _memo=_memo)
                _memo[ptype] = pytype

                for subtype in ptype.children(schema):
                    subclasses.append(
                        object_type_to_python_type(subtype,
                                                   schema,
                                                   base_class=pytype,
                                                   _memo=_memo))
        else:
            pytype = scalar_type_to_python_type(ptype, schema)

        is_multi = p.get_cardinality(schema) is qltypes.Cardinality.MANY
        if is_multi:
            pytype = typing.FrozenSet[pytype]

        default = p.get_default(schema)
        if default is None:
            if p.get_required(schema):
                default = dataclasses.MISSING
        else:
            default = ql_compiler.evaluate_to_python_val(default.text,
                                                         schema=schema)
            if is_multi and not isinstance(default, frozenset):
                default = frozenset((default, ))

        constraints = p.get_constraints(schema).objects(schema)
        exclusive = schema.get('std::exclusive')
        unique = (not ptype.is_object_type() and any(
            c.issubclass(schema, exclusive) for c in constraints))
        field = dataclasses.field(
            compare=unique,
            hash=unique,
            repr=True,
            default=default,
        )
        fields.append((pn, pytype, field))

    return dataclasses.make_dataclass(
        objtype.get_name(schema).name,
        fields=fields,
        bases=(base_class, ) if base_class is not None else (),
        frozen=True,
        namespace={'_subclasses': subclasses},
    )
Example #12
0
 def update_schema(self, new_schema: s_schema.Schema):
     assert isinstance(new_schema, s_schema.ChainedSchema)
     self._current = self._current._replace(
         user_schema=new_schema.get_top_schema(),
         global_schema=new_schema.get_global_schema(),
     )
Example #13
0
    def _cmd_tree_from_ast(
        cls,
        schema: s_schema.Schema,
        astnode: qlast.DDLOperation,
        context: sd.CommandContext,
    ) -> CreateConstraint:
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        if isinstance(astnode, qlast.CreateConcreteConstraint):
            if astnode.delegated:
                cmd.set_attribute_value('delegated', astnode.delegated)

            args = cls._constraint_args_from_ast(schema, astnode, context)
            if args:
                cmd.set_attribute_value('args', 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.NamedOnlyParam:
                    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.set_attribute_value(
                'return_type',
                schema.get('std::bool'),
            )

        if cmd.get_attribute_value('return_typemod') is None:
            cmd.set_attribute_value(
                'return_typemod',
                ft.TypeModifier.SingletonType,
            )

        assert isinstance(
            astnode, (qlast.CreateConstraint, qlast.CreateConcreteConstraint))
        # 'subjectexpr' can be present in either astnode type
        if astnode.subjectexpr:
            orig_text = cls.get_orig_expr_text(schema, astnode, 'subjectexpr')

            if (orig_text is not None and context.compat_ver_is_before(
                (1, 0, verutils.VersionStage.ALPHA, 6))):
                # Versions prior to a6 used a different expression
                # normalization strategy, so we must renormalize the
                # expression.
                expr_ql = qlcompiler.renormalize_compat(
                    astnode.subjectexpr,
                    orig_text,
                    schema=schema,
                    localnames=context.localnames,
                )
            else:
                expr_ql = astnode.subjectexpr

            subjectexpr = s_expr.Expression.from_ast(
                expr_ql,
                schema,
                context.modaliases,
                context.localnames,
            )

            cmd.set_attribute_value(
                'subjectexpr',
                subjectexpr,
            )

        cls._validate_subcommands(astnode)
        assert isinstance(cmd, CreateConstraint)
        return cmd
Example #14
0
    def _populate_concrete_constraint_attrs(
            self,
            schema: s_schema.Schema,
            context: sd.CommandContext,
            subject_obj: Optional[so.Object],
            *,
            name: sn.QualName,
            subjectexpr: Optional[s_expr.Expression] = None,
            sourcectx: Optional[c_parsing.ParserContext] = None,
            args: Any = None,
            **kwargs: Any) -> None:
        from edb.ir import ast as ir_ast
        from edb.ir import utils as ir_utils

        constr_base = schema.get(name, type=Constraint)

        orig_subjectexpr = subjectexpr
        orig_subject = subject_obj
        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(
                f'subjectexpr is already defined for {name}')

        if (isinstance(subject_obj, s_scalars.ScalarType)
                and constr_base.get_is_aggregate(schema)):
            raise errors.InvalidConstraintDefinitionError(
                f'{constr_base.get_verbosename(schema)} may not '
                f'be used on scalar types')

        if subjectexpr is not None:
            subject_ql = subjectexpr.qlast
            subject = subject_ql
        else:
            subject = subject_obj

        expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr')
        if not expr:
            raise errors.InvalidConstraintDefinitionError(
                f'missing constraint expression in {name}')

        # Re-parse instead of using expr.qlast, because we mutate
        # the AST below.
        expr_ql = qlparser.parse(expr.text)

        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
                inherited['subjectexpr'] = True

        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
            assert isinstance(subject, qlast.Base)
            qlutils.inline_anchors(expr_ql,
                                   anchors={qlast.Subject().name: subject})
            subject = orig_subject

        if args:
            args_ql: List[qlast.Base] = [
                qlast.Path(steps=[qlast.Subject()]),
            ]
            args_ql.extend(arg.qlast 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)

        attrs['args'] = args

        assert subject is not None
        path_prefix_anchor = (qlast.Subject().name if isinstance(
            subject, s_types.Type) else None)

        final_expr = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr_ql, schema, {}),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                anchors={qlast.Subject().name: subject},
                path_prefix_anchor=path_prefix_anchor,
                apply_query_rewrites=not context.stdmode,
            ),
        )

        bool_t = schema.get('std::bool', type=s_scalars.ScalarType)
        assert isinstance(final_expr.irast, ir_ast.Statement)

        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_verbosename(schema)}',
                context=sourcectx)

        if subjectexpr is not None:
            if (isinstance(subject_obj, s_types.Type)
                    and subject_obj.is_object_type()):
                singletons = frozenset({subject_obj})
            else:
                singletons = frozenset()

            final_subjectexpr = s_expr.Expression.compiled(
                subjectexpr,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    anchors={qlast.Subject().name: subject},
                    path_prefix_anchor=path_prefix_anchor,
                    singletons=singletons,
                    apply_query_rewrites=not context.stdmode,
                ),
            )
            assert isinstance(final_subjectexpr.irast, ir_ast.Statement)

            refs = ir_utils.get_longest_paths(final_expr.irast)
            has_multi = False
            for ref in refs:
                while ref.rptr:
                    rptr = ref.rptr
                    if rptr.ptrref.dir_cardinality.is_multi():
                        has_multi = True
                    if (not isinstance(rptr.ptrref,
                                       ir_ast.TupleIndirectionPointerRef)
                            and rptr.ptrref.source_ptr is None
                            and rptr.source.rptr is not None):
                        raise errors.InvalidConstraintDefinitionError(
                            "constraints cannot contain paths with more "
                            "than one hop",
                            context=sourcectx)

                    ref = rptr.source

            if has_multi and len(refs) > 1:
                raise errors.InvalidConstraintDefinitionError(
                    "cannot reference multiple links or properties in a "
                    "constraint where at least one link or property is MULTI",
                    context=sourcectx)

            if has_multi and ir_utils.contains_set_of_op(
                    final_subjectexpr.irast):
                raise errors.InvalidConstraintDefinitionError(
                    "cannot use aggregate functions or operators "
                    "in a non-aggregating constraint",
                    context=sourcectx)

        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)
        attrs['abstract'] = False

        for k, v in attrs.items():
            self.set_attribute_value(k, v, inherited=bool(inherited.get(k)))
Example #15
0
def sdl_to_ddl(
    schema: s_schema.Schema,
    documents: Mapping[str, List[qlast.DDL]],
) -> Tuple[qlast.DDLCommand, ...]:

    ddlgraph: DDLGraph = {}
    mods: List[qlast.DDLCommand] = []

    ctx = LayoutTraceContext(
        schema,
        local_modules=frozenset(mod for mod in documents),
    )

    ctx.objects[s_name.QualName('std', 'anytype')] = (schema.get_global(
        s_pseudo.PseudoType, 'anytype'))
    ctx.objects[s_name.QualName('std', 'anytuple')] = (schema.get_global(
        s_pseudo.PseudoType, 'anytuple'))

    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        for decl_ast in declarations:
            if isinstance(decl_ast, qlast.CreateObject):
                _, fq_name = ctx.get_fq_name(decl_ast)

                if isinstance(decl_ast, qlast.CreateObjectType):
                    ctx.objects[fq_name] = qltracer.ObjectType(fq_name)
                elif isinstance(decl_ast, qlast.CreateAlias):
                    ctx.objects[fq_name] = qltracer.Alias(fq_name)
                elif isinstance(decl_ast, qlast.CreateScalarType):
                    ctx.objects[fq_name] = qltracer.ScalarType(fq_name)
                elif isinstance(decl_ast, qlast.CreateLink):
                    ctx.objects[fq_name] = qltracer.Link(fq_name,
                                                         source=None,
                                                         target=None)
                elif isinstance(decl_ast, qlast.CreateProperty):
                    ctx.objects[fq_name] = qltracer.Property(fq_name,
                                                             source=None,
                                                             target=None)
                elif isinstance(decl_ast, qlast.CreateFunction):
                    ctx.objects[fq_name] = qltracer.Function(fq_name)
                elif isinstance(decl_ast, qlast.CreateConstraint):
                    ctx.objects[fq_name] = qltracer.Constraint(fq_name)
                elif isinstance(decl_ast, qlast.CreateAnnotation):
                    ctx.objects[fq_name] = qltracer.Annotation(fq_name)
                elif isinstance(decl_ast, qlast.CreateGlobal):
                    ctx.objects[fq_name] = qltracer.Global(fq_name)
                else:
                    raise AssertionError(
                        f'unexpected SDL declaration: {decl_ast}')

    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        for decl_ast in declarations:
            trace_layout(decl_ast, ctx=ctx)

    # compute the ancestors graph
    for obj_name in ctx.parents.keys():
        ctx.ancestors[obj_name] = get_ancestors(obj_name, ctx.ancestors,
                                                ctx.parents)

    topological.normalize(
        ctx.inh_graph,
        merger=_graph_merge_cb,  # type: ignore
        schema=schema,
    )

    tracectx = DepTraceContext(schema, ddlgraph, ctx.objects, ctx.parents,
                               ctx.ancestors, ctx.defdeps, ctx.constraints)
    for module_name, declarations in documents.items():
        tracectx.set_module(module_name)
        # module needs to be created regardless of whether its
        # contents are empty or not
        mods.append(qlast.CreateModule(name=qlast.ObjectRef(name=module_name)))
        for decl_ast in declarations:
            trace_dependencies(decl_ast, ctx=tracectx)

    # Before sorting normalize all ordering, to make sure that errors
    # are consistent.
    for ddlentry in ddlgraph.values():
        ddlentry.deps = OrderedSet(sorted(ddlentry.deps))
        ddlentry.weak_deps = OrderedSet(sorted(ddlentry.weak_deps))

    try:
        ordered = topological.sort(ddlgraph, allow_unresolved=False)
    except topological.CycleError as e:
        assert isinstance(e.item, s_name.QualName)
        node = tracectx.ddlgraph[e.item].item
        item_vn = get_verbosename_from_fqname(e.item, tracectx)

        if e.path is not None and len(e.path):
            # Recursion involving more than one schema object.
            rec_vn = get_verbosename_from_fqname(e.path[-1], tracectx)
            msg = (f'definition dependency cycle between {rec_vn} '
                   f'and {item_vn}')
        else:
            # A single schema object with a recursive definition.
            msg = f'{item_vn} is defined recursively'

        raise errors.InvalidDefinitionError(msg, context=node.context) from e

    return tuple(mods) + tuple(ordered)
Example #16
0
    def _populate_concrete_constraint_attrs(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        subject_obj: Optional[so.Object],
        *,
        name: sn.QualName,
        subjectexpr: Optional[s_expr.Expression] = None,
        subjectexpr_inherited: bool = False,
        sourcectx: Optional[c_parsing.ParserContext] = None,
        args: Any = None,
        **kwargs: Any
    ) -> None:
        from edb.ir import ast as ir_ast
        from edb.ir import utils as ir_utils
        from . import pointers as s_pointers
        from . import links as s_links
        from . import scalars as s_scalars

        bases = self.get_resolved_attribute_value(
            'bases', schema=schema, context=context,
        )
        if not bases:
            bases = self.scls.get_bases(schema)
        constr_base = bases.objects(schema)[0]
        # If we have a concrete base, then we should inherit all of
        # these attrs through the normal inherit_fields() mechanisms,
        # and populating them ourselves will just mess up
        # inherited_fields.
        if not constr_base.generic(schema):
            return

        orig_subjectexpr = subjectexpr
        orig_subject = subject_obj
        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(
                f'subjectexpr is already defined for {name}'
            )

        if (isinstance(subject_obj, s_scalars.ScalarType)
                and constr_base.get_is_aggregate(schema)):
            raise errors.InvalidConstraintDefinitionError(
                f'{constr_base.get_verbosename(schema)} may not '
                f'be used on scalar types'
            )

        if subjectexpr is not None:
            subject_ql = subjectexpr.qlast
            subject = subject_ql
        else:
            subject = subject_obj

        expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr')
        if not expr:
            raise errors.InvalidConstraintDefinitionError(
                f'missing constraint expression in {name}')

        # Re-parse instead of using expr.qlast, because we mutate
        # the AST below.
        expr_ql = qlparser.parse(expr.text)

        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
            inherited['subjectexpr'] = subjectexpr_inherited
        else:
            base_subjectexpr = constr_base.get_subjectexpr(schema)
            if base_subjectexpr is not None:
                attrs['subjectexpr'] = base_subjectexpr
                inherited['subjectexpr'] = True

        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
            assert isinstance(subject, qlast.Base)
            qlutils.inline_anchors(
                expr_ql, anchors={qlast.Subject().name: subject})
            subject = orig_subject

        if args:
            args_ql: List[qlast.Base] = [
                qlast.Path(steps=[qlast.Subject()]),
            ]
            args_ql.extend(arg.qlast 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)

        attrs['args'] = args

        assert subject is not None
        final_expr = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr_ql, schema, {}),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                anchors={qlast.Subject().name: subject},
                path_prefix_anchor=qlast.Subject().name,
                apply_query_rewrites=not context.stdmode,
            ),
        )

        bool_t = schema.get('std::bool', type=s_scalars.ScalarType)
        assert isinstance(final_expr.irast, ir_ast.Statement)

        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_verbosename(schema)}',
                context=sourcectx
            )

        if subjectexpr is not None:
            assert isinstance(subject_obj, (s_types.Type, s_pointers.Pointer))
            singletons = frozenset({subject_obj})

            final_subjectexpr = s_expr.Expression.compiled(
                subjectexpr,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    anchors={qlast.Subject().name: subject},
                    path_prefix_anchor=qlast.Subject().name,
                    singletons=singletons,
                    apply_query_rewrites=not context.stdmode,
                ),
            )
            assert isinstance(final_subjectexpr.irast, ir_ast.Statement)

            refs = ir_utils.get_longest_paths(final_expr.irast)
            has_multi = False
            for ref in refs:
                while ref.rptr:
                    rptr = ref.rptr
                    if rptr.dir_cardinality.is_multi():
                        has_multi = True

                    # We don't need to look further than the subject,
                    # which is always valid. (And which is a singleton
                    # in a constraint expression if it is itself a
                    # singleton, regardless of other parts of the path.)
                    if (
                        isinstance(rptr.ptrref, ir_ast.PointerRef)
                        and rptr.ptrref.id == subject_obj.id
                    ):
                        break

                    if (not isinstance(rptr.ptrref,
                                       ir_ast.TupleIndirectionPointerRef)
                            and rptr.ptrref.source_ptr is None
                            and rptr.source.rptr is not None):
                        if isinstance(subject, s_links.Link):
                            raise errors.InvalidConstraintDefinitionError(
                                "link constraints may not access "
                                "the link target",
                                context=sourcectx
                            )
                        else:
                            raise errors.InvalidConstraintDefinitionError(
                                "constraints cannot contain paths with more "
                                "than one hop",
                                context=sourcectx
                            )

                    ref = rptr.source

            if has_multi and len(refs) > 1:
                raise errors.InvalidConstraintDefinitionError(
                    "cannot reference multiple links or properties in a "
                    "constraint where at least one link or property is MULTI",
                    context=sourcectx
                )

            if has_multi and ir_utils.contains_set_of_op(
                    final_subjectexpr.irast):
                raise errors.InvalidConstraintDefinitionError(
                    "cannot use aggregate functions or operators "
                    "in a non-aggregating constraint",
                    context=sourcectx
                )

        attrs['finalexpr'] = final_expr
        attrs['params'] = constr_base.get_params(schema)
        inherited['params'] = True
        attrs['abstract'] = False

        for k, v in attrs.items():
            self.set_attribute_value(k, v, inherited=bool(inherited.get(k)))
Example #17
0
def type_to_typeref(
    schema: s_schema.Schema,
    t: s_types.Type,
    *,
    cache: Optional[Dict[TypeRefCacheKey, irast.TypeRef]] = None,
    typename: Optional[s_name.Name] = None,
    include_descendants: bool = False,
    _name: Optional[str] = None,
) -> irast.TypeRef:
    """Return an instance of :class:`ir.ast.TypeRef` for a given type.

    An IR TypeRef is an object that fully describes a schema type for
    the purposes of query compilation.

    Args:
        schema:
            A schema instance, in which the type *t* is defined.
        t:
            A schema type instance.
        cache:
            Optional mapping from (type UUID, typename) to cached IR TypeRefs.
        typename:
            Optional name hint to use for the type in the returned
            TypeRef.  If ``None``, the type name is used.
        include_descendants:
            Whether to include the description of all material type descendants
            of *t*.
        _name:
            Optional subtype element name if this type is a collection within
            a Tuple,

    Returns:
        A ``TypeRef`` instance corresponding to the given schema type.
    """

    result: irast.TypeRef
    material_type: s_types.Type

    if cache is not None and typename is None:
        cached_result = cache.get((t.id, include_descendants))
        if cached_result is not None:
            # If the schema changed due to an ongoing compilation, the name
            # hint might be outdated.
            if cached_result.name_hint == t.get_name(schema):
                return cached_result

    if t.is_anytuple(schema):
        result = irast.AnyTupleRef(
            id=t.id,
            name_hint=typename or t.get_name(schema),
        )
    elif t.is_any(schema):
        result = irast.AnyTypeRef(
            id=t.id,
            name_hint=typename or t.get_name(schema),
        )
    elif not isinstance(t, s_types.Collection):
        assert isinstance(t, s_types.InheritingType)
        union_of = t.get_union_of(schema)
        if union_of:
            non_overlapping, union_is_concrete = (
                s_utils.get_non_overlapping_union(
                    schema,
                    union_of.objects(schema),
                )
            )
            union = frozenset(
                type_to_typeref(schema, c, cache=cache)
                for c in non_overlapping
            )
        else:
            union_is_concrete = False
            union = frozenset()

        intersection_of = t.get_intersection_of(schema)
        if intersection_of:
            intersection = frozenset(
                type_to_typeref(schema, c, cache=cache)
                for c in intersection_of.objects(schema)
            )
        else:
            intersection = frozenset()

        schema, material_type = t.material_type(schema)

        material_typeref: Optional[irast.TypeRef]
        if material_type is not t:
            material_typeref = type_to_typeref(
                schema,
                material_type,
                include_descendants=include_descendants,
                cache=cache,
            )
        else:
            material_typeref = None

        if (isinstance(material_type, s_scalars.ScalarType)
                and not material_type.get_is_abstract(schema)):
            base_type = material_type.get_topmost_concrete_base(schema)
            if base_type is material_type:
                base_typeref = None
            else:
                assert isinstance(base_type, s_types.Type)
                base_typeref = type_to_typeref(
                    schema, base_type, cache=cache
                )
        else:
            base_typeref = None

        tname = t.get_name(schema)
        if typename is not None:
            name = typename
        else:
            name = tname
        module = schema.get_global(s_mod.Module, tname.module)

        common_parent_ref: Optional[irast.TypeRef]
        if union_of:
            common_parent = s_utils.get_class_nearest_common_ancestor(
                schema, union_of.objects(schema))
            assert isinstance(common_parent, s_types.Type)
            common_parent_ref = type_to_typeref(
                schema, common_parent, cache=cache
            )
        else:
            common_parent_ref = None

        descendants: Optional[FrozenSet[irast.TypeRef]]

        if material_typeref is None and include_descendants:
            descendants = frozenset(
                type_to_typeref(
                    schema,
                    child,
                    cache=cache,
                )
                for child in t.children(schema)
                if not child.get_is_derived(schema)
            )
        else:
            descendants = None

        result = irast.TypeRef(
            id=t.id,
            module_id=module.id,
            name_hint=name,
            material_type=material_typeref,
            base_type=base_typeref,
            descendants=descendants,
            union=union,
            union_is_concrete=union_is_concrete,
            intersection=intersection,
            common_parent=common_parent_ref,
            element_name=_name,
            is_scalar=t.is_scalar(),
            is_abstract=t.get_is_abstract(schema),
            is_view=t.is_view(schema),
            is_opaque_union=t.get_is_opaque_union(schema),
        )
    elif isinstance(t, s_types.Tuple) and t.is_named(schema):
        schema, material_type = t.material_type(schema)

        if material_type is not t:
            material_typeref = type_to_typeref(
                schema, material_type, cache=cache
            )
        else:
            material_typeref = None

        result = irast.TypeRef(
            id=t.id,
            name_hint=typename or t.get_name(schema),
            material_type=material_typeref,
            element_name=_name,
            collection=t.schema_name,
            in_schema=t.get_is_persistent(schema),
            subtypes=tuple(
                type_to_typeref(schema, st, _name=sn)  # note: no cache
                for sn, st in t.iter_subtypes(schema)
            )
        )
    else:
        schema, material_type = t.material_type(schema)

        if material_type is not t:
            material_typeref = type_to_typeref(
                schema, material_type, cache=cache
            )
        else:
            material_typeref = None

        result = irast.TypeRef(
            id=t.id,
            name_hint=typename or t.get_name(schema),
            material_type=material_typeref,
            element_name=_name,
            collection=t.schema_name,
            in_schema=t.get_is_persistent(schema),
            subtypes=tuple(
                type_to_typeref(schema, st, cache=cache)
                for st in t.get_subtypes(schema)
            )
        )

    if cache is not None and typename is None and _name is None:
        # Note: there is no cache for `_name` variants since they are only used
        # for Tuple subtypes and thus they will be cached on the outer level
        # anyway.
        # There's also no variant for types with custom typenames since they
        # proved to have a very low hit rate.
        # This way we save on the size of the key tuple.
        cache[t.id, include_descendants] = result
    return result
Example #18
0
def ptrref_from_ptrcls(
    *,
    schema: s_schema.Schema,
    ptrcls: s_pointers.PointerLike,
    direction: s_pointers.PointerDirection = (
        s_pointers.PointerDirection.Outbound),
    cache: Optional[Dict[PtrRefCacheKey, irast.BasePointerRef]] = None,
    typeref_cache: Optional[Dict[TypeRefCacheKey, irast.TypeRef]] = None,
    include_descendants: bool = False,
) -> irast.BasePointerRef:
    """Return an IR pointer descriptor for a given schema pointer.

    An IR PointerRef is an object that fully describes a schema pointer for
    the purposes of query compilation.

    Args:
        schema:
            A schema instance, in which the type *t* is defined.
        ptrcls:
            A :class:`schema.pointers.Pointer` instance for which to
            return the PointerRef.
        direction:
            The direction of the pointer in the path expression.

    Returns:
        An instance of a subclass of :class:`ir.ast.BasePointerRef`
        corresponding to the given schema pointer.
    """

    if cache is not None:
        cached = cache.get((ptrcls, direction, include_descendants))
        if cached is not None:
            return cached

    kwargs: Dict[str, Any] = {}

    ircls: Type[irast.BasePointerRef]

    source_ref: Optional[irast.TypeRef]
    target_ref: Optional[irast.TypeRef]
    out_source: Optional[irast.TypeRef]

    if isinstance(ptrcls, irast.TupleIndirectionLink):
        ircls = irast.TupleIndirectionPointerRef
    elif isinstance(ptrcls, irast.TypeIntersectionLink):
        ircls = irast.TypeIntersectionPointerRef
        kwargs['optional'] = ptrcls.is_optional()
        kwargs['is_empty'] = ptrcls.is_empty()
        kwargs['is_subtype'] = ptrcls.is_subtype()
        kwargs['rptr_specialization'] = ptrcls.get_rptr_specialization()
    elif isinstance(ptrcls, s_pointers.Pointer):
        ircls = irast.PointerRef
        kwargs['id'] = ptrcls.id
        name = ptrcls.get_name(schema)
        kwargs['module_id'] = schema.get_global(
            s_mod.Module, name.module).id
    else:
        raise AssertionError(f'unexpected pointer class: {ptrcls}')

    target = ptrcls.get_far_endpoint(schema, direction)
    if target is not None and not isinstance(target, irast.TypeRef):
        assert isinstance(target, s_types.Type)
        target_ref = type_to_typeref(schema, target, cache=typeref_cache)
    else:
        target_ref = target

    source = ptrcls.get_near_endpoint(schema, direction)

    source_ptr: Optional[irast.BasePointerRef]
    if (isinstance(ptrcls, s_props.Property)
            and isinstance(source, s_links.Link)):
        source_ptr = ptrref_from_ptrcls(
            ptrcls=source,
            direction=direction,
            schema=schema,
            cache=cache,
            typeref_cache=typeref_cache,
        )
        source_ref = None
    else:
        if source is not None and not isinstance(source, irast.TypeRef):
            assert isinstance(source, s_types.Type)
            source_ref = type_to_typeref(schema,
                                         source,
                                         cache=typeref_cache)
        else:
            source_ref = source
        source_ptr = None

    if direction is s_pointers.PointerDirection.Inbound:
        out_source = target_ref
        out_target = source_ref
    else:
        out_source = source_ref
        out_target = target_ref

    out_cardinality, dir_cardinality = cardinality_from_ptrcls(
        schema, ptrcls, direction=direction)

    material_ptrcls = ptrcls.material_type(schema)
    material_ptr: Optional[irast.BasePointerRef]
    if material_ptrcls is not None and material_ptrcls is not ptrcls:
        material_ptr = ptrref_from_ptrcls(
            ptrcls=material_ptrcls,
            direction=direction,
            schema=schema,
            cache=cache,
            typeref_cache=typeref_cache,
            include_descendants=include_descendants,
        )
    else:
        material_ptr = None

    union_components: Set[irast.BasePointerRef] = set()
    union_of = ptrcls.get_union_of(schema)
    union_is_concrete = False
    if union_of:
        union_ptrs = set()

        for component in union_of.objects(schema):
            assert isinstance(component, s_pointers.Pointer)
            material_comp = component.material_type(schema)
            union_ptrs.add(material_comp)

        non_overlapping, union_is_concrete = s_utils.get_non_overlapping_union(
            schema,
            union_ptrs,
        )

        union_components = {
            ptrref_from_ptrcls(
                ptrcls=p,
                direction=direction,
                schema=schema,
                cache=cache,
                typeref_cache=typeref_cache,
            ) for p in non_overlapping
        }

    std_parent_name = None
    for ancestor in ptrcls.get_ancestors(schema).objects(schema):
        ancestor_name = ancestor.get_name(schema)
        if ancestor_name.module == 'std' and ancestor.generic(schema):
            std_parent_name = ancestor_name
            break

    is_derived = ptrcls.get_is_derived(schema)
    base_ptr: Optional[irast.BasePointerRef]
    if is_derived:
        base_ptrcls = ptrcls.get_bases(schema).first(schema)
        top_ptr_name = type(base_ptrcls).get_default_base_name()
        if base_ptrcls.get_name(schema) != top_ptr_name:
            base_ptr = ptrref_from_ptrcls(
                ptrcls=base_ptrcls,
                direction=direction,
                schema=schema,
                cache=cache,
                typeref_cache=typeref_cache,
            )
        else:
            base_ptr = None
    else:
        base_ptr = None

    if (
        material_ptr is None
        and include_descendants
        and isinstance(ptrcls, s_pointers.Pointer)
    ):
        descendants = frozenset(
            ptrref_from_ptrcls(
                ptrcls=child,
                direction=direction,
                schema=schema,
                cache=cache,
                typeref_cache=typeref_cache,
            )
            for child in ptrcls.children(schema)
            if not child.get_is_derived(schema)
        )
    else:
        descendants = frozenset()

    kwargs.update(dict(
        out_source=out_source,
        out_target=out_target,
        name=ptrcls.get_name(schema),
        shortname=ptrcls.get_shortname(schema),
        path_id_name=ptrcls.get_path_id_name(schema),
        std_parent_name=std_parent_name,
        direction=direction,
        source_ptr=source_ptr,
        base_ptr=base_ptr,
        material_ptr=material_ptr,
        descendants=descendants,
        is_derived=ptrcls.get_is_derived(schema),
        is_computable=ptrcls.get_computable(schema),
        union_components=union_components,
        union_is_concrete=union_is_concrete,
        has_properties=ptrcls.has_user_defined_properties(schema),
        dir_cardinality=dir_cardinality,
        out_cardinality=out_cardinality,
    ))

    ptrref = ircls(**kwargs)

    if cache is not None:
        cache[ptrcls, direction, include_descendants] = ptrref

    return ptrref
Example #19
0
    def get_concrete_constraint_attrs(
        cls,
        schema: s_schema.Schema,
        subject: Optional[so.Object],
        *,
        name: str,
        subjectexpr: Optional[s_expr.Expression] = None,
        sourcectx: Optional[c_parsing.ParserContext] = None,
        args: Any = None,
        modaliases: Optional[Mapping[Optional[str], str]] = None,
        **kwargs: Any
    ) -> Tuple[Any, Dict[str, Any], Dict[str, bool]]:
        # constr_base, attrs, inherited
        from edb.edgeql import parser as qlparser
        from edb.edgeql import utils as qlutils
        from edb.ir import ast as ir_ast

        constr_base: Constraint = schema.get(name, module_aliases=modaliases)

        module_aliases: Mapping[Optional[str], str] = {}

        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
                inherited['subjectexpr'] = True

        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
            assert isinstance(subject, qlast.Base)
            qlutils.inline_anchors(
                expr_ql, anchors={qlast.Subject().name: subject})
            subject = orig_subject

        if args:
            args_map = None
            args_ql: List[qlast.Base] = [
                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)

        attrs['args'] = args

        if expr == '__subject__':
            expr_context = sourcectx
        else:
            expr_context = None

        assert subject is not None
        final_expr = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr_ql, schema, module_aliases),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                modaliases=module_aliases,
                anchors={qlast.Subject().name: subject},
            ),
        )

        bool_t: s_scalars.ScalarType = schema.get('std::bool')
        assert isinstance(final_expr.irast, ir_ast.Statement)

        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_verbosename(schema)}',
                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)
        attrs['is_abstract'] = False

        return constr_base, attrs, inherited
Example #20
0
def object_type_to_python_type(
    objtype: s_objtypes.ObjectType,
    schema: s_schema.Schema,
    *,
    base_class: Optional[type] = None,
    _memo: Optional[Dict[s_types.Type, type]] = None,
) -> type:
    if _memo is None:
        _memo = {}
    default: Any
    fields = []
    subclasses = []

    for pn, p in objtype.get_pointers(schema).items(schema):
        str_pn = str(pn)
        if str_pn in ('id', '__type__'):
            continue

        ptype = p.get_target(schema)
        assert ptype is not None

        if isinstance(ptype, s_objtypes.ObjectType):
            pytype = _memo.get(ptype)
            if pytype is None:
                pytype = object_type_to_python_type(
                    ptype, schema, base_class=base_class, _memo=_memo)
                _memo[ptype] = pytype

                for subtype in ptype.children(schema):
                    subclasses.append(
                        object_type_to_python_type(
                            subtype, schema,
                            base_class=pytype, _memo=_memo))
        else:
            pytype = scalar_type_to_python_type(ptype, schema)

        ptr_card = p.get_cardinality(schema)
        is_multi = ptr_card.is_multi()
        if is_multi:
            pytype = FrozenSet[pytype]  # type: ignore

        default = p.get_default(schema)
        if default is None:
            if p.get_required(schema):
                default = dataclasses.MISSING
        else:
            default = qlcompiler.evaluate_to_python_val(
                default.text, schema=schema)
            if is_multi and not isinstance(default, frozenset):
                default = frozenset((default,))

        constraints = p.get_constraints(schema).objects(schema)
        exclusive = schema.get('std::exclusive', type=s_constr.Constraint)
        unique = (
            not ptype.is_object_type()
            and any(c.issubclass(schema, exclusive) for c in constraints)
        )
        field = dataclasses.field(
            compare=unique,
            hash=unique,
            repr=True,
            default=default,
        )
        fields.append((str_pn, pytype, field))

    bases: Tuple[type, ...]
    if base_class is not None:
        bases = (base_class,)
    else:
        bases = ()

    ptype_dataclass = dataclasses.make_dataclass(
        objtype.get_name(schema).name,
        fields=fields,
        bases=bases,
        frozen=True,
        namespace={'_subclasses': subclasses},
    )
    assert isinstance(ptype_dataclass, type)
    return ptype_dataclass
Example #21
0
    def _create_begin(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> s_schema.Schema:
        fullname = self.classname
        shortname = sn.shortname_from_fullname(fullname)
        schema, cp = self._get_param_desc_from_delta(schema, context, self)
        signature = f'{shortname}({", ".join(p.as_str(schema) for p in cp)})'

        func = schema.get(fullname, None)
        if func:
            raise errors.InvalidOperatorDefinitionError(
                f'cannot create the `{signature}` operator: '
                f'an operator with the same signature '
                f'is already defined',
                context=self.source_context)

        schema = super()._create_begin(schema, context)

        params: s_func.FuncParameterList = self.scls.get_params(schema)
        fullname = self.scls.get_name(schema)
        shortname = sn.shortname_from_fullname(fullname)
        return_typemod = self.scls.get_return_typemod(schema)
        assert isinstance(self.scls, Operator)
        recursive = self.scls.get_recursive(schema)
        derivative_of = self.scls.get_derivative_of(schema)

        # an operator must have operands
        if len(params) == 0:
            raise errors.InvalidOperatorDefinitionError(
                f'cannot create the `{signature}` operator: '
                f'an operator must have operands',
                context=self.source_context)

        # We'll need to make sure that there's no mix of recursive and
        # non-recursive operators being overloaded.
        all_arrays = all_tuples = True
        for param in params.objects(schema):
            ptype = param.get_type(schema)
            all_arrays = all_arrays and ptype.is_array()
            all_tuples = all_tuples and ptype.is_tuple(schema)

        # It's illegal to declare an operator as recursive unless all
        # of its operands are the same basic type of collection.
        if recursive and not (all_arrays or all_tuples):
            raise errors.InvalidOperatorDefinitionError(
                f'cannot create the `{signature}` operator: '
                f'operands of a recursive operator must either be '
                f'all arrays or all tuples',
                context=self.source_context)

        for oper in schema.get_operators(shortname, ()):
            if oper == self.scls:
                continue

            oper_return_typemod = oper.get_return_typemod(schema)
            if oper_return_typemod != return_typemod:
                raise errors.DuplicateOperatorDefinitionError(
                    f'cannot create the `{signature}` '
                    f'operator: overloading another operator with different '
                    f'return type {oper_return_typemod.to_edgeql()} '
                    f'{oper.get_return_type(schema).name}',
                    context=self.source_context)

            oper_derivative_of = oper.get_derivative_of(schema)
            if oper_derivative_of:
                raise errors.DuplicateOperatorDefinitionError(
                    f'cannot create the `{signature}` '
                    f'operator: there exists a derivative operator of the '
                    f'same name',
                    context=self.source_context)
            elif derivative_of:
                raise errors.DuplicateOperatorDefinitionError(
                    f'cannot create `{signature}` '
                    f'as a derivative operator: there already exists an '
                    f'operator of the same name',
                    context=self.source_context)

            # Check if there is a recursive/non-recursive operator
            # overloading.
            oper_recursive = oper.get_recursive(schema)
            if recursive != oper_recursive:
                oper_signature = oper.get_display_signature(schema)
                oper_all_arrays = oper_all_tuples = True
                for param in oper.get_params(schema).objects(schema):
                    ptype = param.get_type(schema)
                    oper_all_arrays = oper_all_arrays and ptype.is_array()
                    oper_all_tuples = (oper_all_tuples
                                       and ptype.is_tuple(schema))

                if (all_arrays == oper_all_arrays
                        and all_tuples == oper_all_tuples):
                    new_rec = 'recursive' if recursive else 'non-recursive'
                    oper_rec = \
                        'recursive' if oper_recursive else 'non-recursive'

                    raise errors.InvalidOperatorDefinitionError(
                        f'cannot create the {new_rec} `{signature}` operator: '
                        f'overloading a {oper_rec} operator '
                        f'`{oper_signature}` with a {new_rec} one '
                        f'is not allowed',
                        context=self.source_context)

        return schema
Example #22
0
def generate_structure(schema: s_schema.Schema) -> SchemaReflectionParts:
    """Generate schema reflection structure from Python schema classes.

    Returns:
        A quadruple (as a SchemaReflectionParts instance) containing:
            - Delta, which, when applied to stdlib, yields an enhanced
              version of the `schema` module that contains all types
              and properties, not just those that are publicly exposed
              for introspection.
            - A mapping, containing type layout description for all
              schema classes.
            - A sequence of EdgeQL queries necessary to introspect
              a database schema.
            - A sequence of EdgeQL queries necessary to introspect
              global objects, such as roles and databases.
    """

    delta = sd.DeltaRoot()
    classlayout: Dict[Type[s_obj.Object], SchemaTypeLayout, ] = {}

    ordered_link = schema.get('schema::ordered', type=s_links.Link)

    py_classes = []

    schema = _run_ddl(
        '''
            CREATE FUNCTION sys::_get_pg_type_for_scalar_type(
                typeid: std::uuid
            ) -> std::int64 {
                USING SQL $$
                    SELECT
                        coalesce(
                            (
                                SELECT
                                    tn::regtype::oid
                                FROM
                                    edgedb._get_base_scalar_type_map()
                                        AS m(tid uuid, tn text)
                                WHERE
                                    m.tid = "typeid"
                            ),
                            (
                                SELECT
                                    typ.oid
                                FROM
                                    pg_catalog.pg_type typ
                                WHERE
                                    typ.typname = "typeid"::text || '_domain'
                            ),

                            edgedb.raise(
                                NULL::bigint,
                                'invalid_parameter_value',
                                msg => (
                                    'cannot determine OID of '
                                    || typeid::text
                                )
                            )
                        )::bigint
                $$;
                SET volatility := 'STABLE';
            };

            CREATE FUNCTION sys::_expr_from_json(
                data: json
            ) -> OPTIONAL tuple<text: str, refs: array<uuid>> {
                USING SQL $$
                    SELECT
                        "data"->>'text'                     AS text,
                        coalesce(r.refs, ARRAY[]::uuid[])   AS refs
                    FROM
                        (SELECT
                            array_agg(v::uuid) AS refs
                         FROM
                            jsonb_array_elements_text("data"->'refs') AS v
                        ) AS r
                    WHERE
                        jsonb_typeof("data") != 'null'
                $$;
                SET volatility := 'IMMUTABLE';
            };
        ''',
        schema=schema,
        delta=delta,
    )

    for py_cls in s_obj.ObjectMeta.get_schema_metaclasses():
        if isinstance(py_cls, adapter.Adapter):
            continue

        if py_cls is s_obj.GlobalObject:
            continue

        py_classes.append(py_cls)

    read_sets: Dict[Type[s_obj.Object], List[str]] = {}

    for py_cls in py_classes:
        rschema_name = get_schema_name_for_pycls(py_cls)
        schema_objtype = schema.get(
            rschema_name,
            type=s_objtypes.ObjectType,
            default=None,
        )

        bases = []
        for base in py_cls.__bases__:
            if base in py_classes:
                bases.append(get_schema_name_for_pycls(base))

        default_base = get_default_base_for_pycls(py_cls)
        if not bases and rschema_name != default_base:
            bases.append(default_base)

        reflection = py_cls.get_reflection_method()
        is_simple_wrapper = issubclass(py_cls, s_types.CollectionExprAlias)

        if schema_objtype is None:
            as_abstract = (reflection is s_obj.ReflectionMethod.REGULAR
                           and not is_simple_wrapper)

            schema = _run_ddl(
                f'''
                    CREATE {'ABSTRACT' if as_abstract else ''}
                    TYPE {rschema_name}
                    EXTENDING {', '.join(str(b) for b in bases)};
                ''',
                schema=schema,
                delta=delta,
            )

            schema_objtype = schema.get(rschema_name,
                                        type=s_objtypes.ObjectType)
        else:
            ex_bases = schema_objtype.get_bases(schema).names(schema)
            _, added_bases = s_inh.delta_bases(ex_bases, bases)

            if added_bases:
                for subset, position in added_bases:
                    if isinstance(position, tuple):
                        position_clause = (f'{position[0]} {position[1].name}')
                    else:
                        position_clause = position

                    bases_expr = ', '.join(str(t.name) for t in subset)

                    stmt = f'''
                        ALTER TYPE {rschema_name} {{
                            EXTENDING {bases_expr} {position_clause}
                        }}
                    '''

                    schema = _run_ddl(
                        stmt,
                        schema=schema,
                        delta=delta,
                    )

        if reflection is s_obj.ReflectionMethod.NONE:
            continue

        referrers = py_cls.get_referring_classes()

        if reflection is s_obj.ReflectionMethod.AS_LINK:
            if not referrers:
                raise RuntimeError(
                    f'schema class {py_cls.__name__} is declared with AS_LINK '
                    f'reflection method but is not referenced in any RefDict')

        is_concrete = not schema_objtype.get_abstract(schema)

        if (is_concrete and not is_simple_wrapper
                and any(not b.get_abstract(schema) for b in
                        schema_objtype.get_ancestors(schema).objects(schema))):
            raise RuntimeError(
                f'non-abstract {schema_objtype.get_verbosename(schema)} has '
                f'non-abstract ancestors')

        read_shape = read_sets[py_cls] = []

        if is_concrete:
            read_shape.append(
                '_tname := .__type__[IS schema::ObjectType].name')

        classlayout[py_cls] = {}
        ownfields = py_cls.get_ownfields()

        for fn, field in py_cls.get_fields().items():
            if (field.ephemeral or (field.reflection_method
                                    is not s_obj.ReflectionMethod.REGULAR)):
                continue

            storage = _classify_object_field(field)

            ptr = schema_objtype.maybe_get_ptr(schema, sn.UnqualName(fn))

            if fn in ownfields:
                qual = "REQUIRED" if field.required else "OPTIONAL"
                if ptr is None:
                    schema = _run_ddl(
                        f'''
                            ALTER TYPE {rschema_name} {{
                                CREATE {qual}
                                {storage.ptrkind} {fn} -> {storage.ptrtype};
                            }}
                        ''',
                        schema=schema,
                        delta=delta,
                    )
                    ptr = schema_objtype.getptr(schema, sn.UnqualName(fn))

                if storage.shadow_ptrkind is not None:
                    pn = f'{fn}__internal'
                    internal_ptr = schema_objtype.maybe_get_ptr(
                        schema, sn.UnqualName(pn))
                    if internal_ptr is None:
                        ptrkind = storage.shadow_ptrkind
                        ptrtype = storage.shadow_ptrtype
                        schema = _run_ddl(
                            f'''
                                ALTER TYPE {rschema_name} {{
                                    CREATE {qual}
                                    {ptrkind} {pn} -> {ptrtype};
                                }}
                            ''',
                            schema=schema,
                            delta=delta,
                        )

            else:
                assert ptr is not None

            if is_concrete:
                read_ptr = fn

                if field.type_is_generic_self:
                    read_ptr = f'{read_ptr}[IS {rschema_name}]'

                if field.reflection_proxy:
                    proxy_type, proxy_link = field.reflection_proxy
                    read_ptr = (
                        f'{read_ptr}: {{name, value := .{proxy_link}.id}}')

                if ptr.issubclass(schema, ordered_link):
                    read_ptr = f'{read_ptr} ORDER BY @index'

                read_shape.append(read_ptr)

                if storage.shadow_ptrkind is not None:
                    read_shape.append(f'{fn}__internal')

            if field.reflection_proxy:
                proxy_type_name, proxy_link_name = field.reflection_proxy
                proxy_obj = schema.get(proxy_type_name,
                                       type=s_objtypes.ObjectType)
                proxy_link_obj = proxy_obj.getptr(
                    schema, sn.UnqualName(proxy_link_name))
                tgt = proxy_link_obj.get_target(schema)
            else:
                tgt = ptr.get_target(schema)
            assert tgt is not None
            cardinality = ptr.get_cardinality(schema)
            assert cardinality is not None
            classlayout[py_cls][fn] = SchemaFieldDesc(
                fieldname=fn,
                type=tgt,
                cardinality=cardinality,
                properties={},
                storage=storage,
                is_ordered=ptr.issubclass(schema, ordered_link),
                reflection_proxy=field.reflection_proxy,
            )

    # Second pass: deal with RefDicts, which are reflected as links.
    for py_cls in py_classes:
        rschema_name = get_schema_name_for_pycls(py_cls)
        schema_cls = schema.get(rschema_name, type=s_objtypes.ObjectType)

        for refdict in py_cls.get_own_refdicts().values():
            ref_ptr = schema_cls.maybe_get_ptr(schema,
                                               sn.UnqualName(refdict.attr))
            ref_cls = refdict.ref_cls
            assert issubclass(ref_cls, s_obj.Object)
            shadow_ref_ptr = None
            reflect_as_link = (ref_cls.get_reflection_method() is
                               s_obj.ReflectionMethod.AS_LINK)

            if reflect_as_link:
                reflection_link = ref_cls.get_reflection_link()
                assert reflection_link is not None
                target_field = ref_cls.get_field(reflection_link)
                target_cls = target_field.type
                shadow_pn = f'{refdict.attr}__internal'

                schema = _run_ddl(
                    f'''
                        ALTER TYPE {rschema_name} {{
                            CREATE OPTIONAL MULTI LINK {shadow_pn}
                            EXTENDING schema::reference
                             -> {get_schema_name_for_pycls(ref_cls)} {{
                                 ON TARGET DELETE ALLOW;
                             }};
                        }}
                    ''',
                    schema=schema,
                    delta=delta,
                )
                shadow_ref_ptr = schema_cls.getptr(schema,
                                                   sn.UnqualName(shadow_pn))
            else:
                target_cls = ref_cls

            if ref_ptr is None:
                ptr_type = get_schema_name_for_pycls(target_cls)
                schema = _run_ddl(
                    f'''
                        ALTER TYPE {rschema_name} {{
                            CREATE OPTIONAL MULTI LINK {refdict.attr}
                            EXTENDING schema::reference
                             -> {ptr_type} {{
                                 ON TARGET DELETE ALLOW;
                             }};
                        }}
                    ''',
                    schema=schema,
                    delta=delta,
                )

                ref_ptr = schema_cls.getptr(schema,
                                            sn.UnqualName(refdict.attr))
            else:
                schema = _run_ddl(
                    f'''
                        ALTER TYPE {rschema_name} {{
                            ALTER LINK {refdict.attr}
                            ON TARGET DELETE ALLOW;
                        }}
                    ''',
                    schema=schema,
                    delta=delta,
                )

            assert isinstance(ref_ptr, s_links.Link)

            if py_cls not in classlayout:
                classlayout[py_cls] = {}

            # First, fields declared to be reflected as link properties.
            props = _get_reflected_link_props(
                ref_ptr=ref_ptr,
                target_cls=ref_cls,
                schema=schema,
            )

            if reflect_as_link:
                # Then, because it's a passthrough reflection, all scalar
                # fields of the proxy object.
                fields_as_props = [
                    f for f in ref_cls.get_ownfields().values()
                    if (not f.ephemeral and (
                        f.reflection_method is not s_obj.ReflectionMethod.
                        AS_LINK) and f.name != refdict.backref_attr
                        and f.name != ref_cls.get_reflection_link())
                ]

                extra_props = _classify_scalar_object_fields(fields_as_props)

            for fn, storage in {**props, **extra_props}.items():
                prop_ptr = ref_ptr.maybe_get_ptr(schema, sn.UnqualName(fn))
                if prop_ptr is None:
                    pty = storage.ptrtype
                    schema = _run_ddl(
                        f'''
                            ALTER TYPE {rschema_name} {{
                                ALTER LINK {refdict.attr} {{
                                    CREATE OPTIONAL PROPERTY {fn} -> {pty};
                                }}
                            }}
                        ''',
                        schema=schema,
                        delta=delta,
                    )

            if shadow_ref_ptr is not None:
                assert isinstance(shadow_ref_ptr, s_links.Link)
                shadow_pn = shadow_ref_ptr.get_shortname(schema).name
                for fn, storage in props.items():
                    prop_ptr = shadow_ref_ptr.maybe_get_ptr(
                        schema, sn.UnqualName(fn))
                    if prop_ptr is None:
                        pty = storage.ptrtype
                        schema = _run_ddl(
                            f'''
                                ALTER TYPE {rschema_name} {{
                                    ALTER LINK {shadow_pn} {{
                                        CREATE OPTIONAL PROPERTY {fn} -> {pty};
                                    }}
                                }}
                            ''',
                            schema=schema,
                            delta=delta,
                        )

    for py_cls in py_classes:
        rschema_name = get_schema_name_for_pycls(py_cls)
        schema_cls = schema.get(rschema_name, type=s_objtypes.ObjectType)

        is_concrete = not schema_cls.get_abstract(schema)
        read_shape = read_sets[py_cls]

        for refdict in py_cls.get_refdicts():
            if py_cls not in classlayout:
                classlayout[py_cls] = {}

            ref_ptr = schema_cls.getptr(schema,
                                        sn.UnqualName(refdict.attr),
                                        type=s_links.Link)
            tgt = ref_ptr.get_target(schema)
            assert tgt is not None
            cardinality = ref_ptr.get_cardinality(schema)
            assert cardinality is not None
            classlayout[py_cls][refdict.attr] = SchemaFieldDesc(
                fieldname=refdict.attr,
                type=tgt,
                cardinality=cardinality,
                properties={},
                is_ordered=ref_ptr.issubclass(schema, ordered_link),
                reflection_proxy=None,
                is_refdict=True,
            )

            target_cls = refdict.ref_cls

            props = _get_reflected_link_props(
                ref_ptr=ref_ptr,
                target_cls=target_cls,
                schema=schema,
            )

            reflect_as_link = (target_cls.get_reflection_method() is
                               s_obj.ReflectionMethod.AS_LINK)

            prop_layout = {}
            extra_prop_layout = {}

            for fn, storage in props.items():
                prop_ptr = ref_ptr.getptr(schema, sn.UnqualName(fn))
                prop_tgt = prop_ptr.get_target(schema)
                assert prop_tgt is not None
                prop_layout[fn] = (prop_tgt, storage.fieldtype)

            if reflect_as_link:
                # Then, because it's a passthrough reflection, all scalar
                # fields of the proxy object.
                fields_as_props = [
                    f for f in target_cls.get_ownfields().values()
                    if (not f.ephemeral and (
                        f.reflection_method is not s_obj.ReflectionMethod.
                        AS_LINK) and f.name != refdict.backref_attr
                        and f.name != target_cls.get_reflection_link())
                ]

                extra_props = _classify_scalar_object_fields(fields_as_props)

                for fn, storage in extra_props.items():
                    prop_ptr = ref_ptr.getptr(schema, sn.UnqualName(fn))
                    prop_tgt = prop_ptr.get_target(schema)
                    assert prop_tgt is not None
                    extra_prop_layout[fn] = (prop_tgt, storage.fieldtype)
            else:
                extra_prop_layout = {}

            classlayout[py_cls][refdict.attr].properties.update({
                **prop_layout,
                **extra_prop_layout,
            })

            if reflect_as_link:
                shadow_tgt = schema.get(
                    get_schema_name_for_pycls(ref_cls),
                    type=s_objtypes.ObjectType,
                )

                classlayout[py_cls][f'{refdict.attr}__internal'] = (
                    SchemaFieldDesc(
                        fieldname=refdict.attr,
                        type=shadow_tgt,
                        cardinality=qltypes.SchemaCardinality.Many,
                        properties=prop_layout,
                        is_refdict=True,
                    ))

            if is_concrete:
                read_ptr = refdict.attr
                prop_shape_els = []

                if reflect_as_link:
                    read_ptr = f'{read_ptr}__internal'
                    ref_ptr = schema_cls.getptr(
                        schema,
                        sn.UnqualName(f'{refdict.attr}__internal'),
                    )

                for fn in props:
                    prop_shape_els.append(f'@{fn}')

                if prop_shape_els:
                    prop_shape = ',\n'.join(prop_shape_els)
                    read_ptr = f'{read_ptr}: {{id, {prop_shape}}}'

                if ref_ptr.issubclass(schema, ordered_link):
                    read_ptr = f'{read_ptr} ORDER BY @index'

                read_shape.append(read_ptr)

    local_parts = []
    global_parts = []
    for py_cls, shape_els in read_sets.items():
        if (not shape_els
                # The CollectionExprAlias family needs to be excluded
                # because TupleExprAlias and ArrayExprAlias inherit from
                # concrete classes and so are picked up from those.
                or issubclass(py_cls, s_types.CollectionExprAlias)):
            continue

        rschema_name = get_schema_name_for_pycls(py_cls)
        shape = ',\n'.join(shape_els)
        qry = f'''
            SELECT {rschema_name} {{
                {shape}
            }}
        '''
        if not issubclass(py_cls, (s_types.Collection, s_obj.GlobalObject)):
            qry += ' FILTER NOT .builtin'

        if issubclass(py_cls, s_obj.GlobalObject):
            global_parts.append(qry)
        else:
            local_parts.append(qry)

    delta.canonical = True
    return SchemaReflectionParts(
        intro_schema_delta=delta,
        class_layout=classlayout,
        local_intro_parts=local_parts,
        global_intro_parts=global_parts,
    )
Example #23
0
def init_context(
        *,
        schema: s_schema.Schema,
        func_params: Optional[s_func.ParameterLikeList]=None,
        parent_object_type: Optional[s_obj.ObjectMeta]=None,
        modaliases: Optional[Mapping[Optional[str], str]]=None,
        anchors: Optional[
            Mapping[
                Union[str, qlast.SpecialAnchorT],
                Union[s_obj.Object, irast.Base],
            ],
        ]=None,
        singletons: Optional[Iterable[s_types.Type]]=None,
        security_context: Optional[str]=None,
        derived_target_module: Optional[str]=None,
        result_view_name: Optional[s_name.SchemaName]=None,
        schema_view_mode: bool=False,
        disable_constant_folding: bool=False,
        allow_generic_type_output: bool=False,
        implicit_limit: int=0,
        implicit_id_in_shapes: bool=False,
        implicit_tid_in_shapes: bool=False,
        json_parameters: bool=False,
        session_mode: bool=False) -> \
        context.ContextLevel:
    if not schema.get_global(s_mod.Module, '__derived__', None):
        schema, _ = s_mod.Module.create_in_schema(schema, name='__derived__')
    env = context.Environment(
        schema=schema,
        path_scope=irast.new_scope_tree(),
        constant_folding=not disable_constant_folding,
        func_params=func_params,
        parent_object_type=parent_object_type,
        schema_view_mode=schema_view_mode,
        json_parameters=json_parameters,
        session_mode=session_mode,
        allow_generic_type_output=allow_generic_type_output)
    ctx = context.ContextLevel(None, context.ContextSwitchMode.NEW, env=env)
    _ = context.CompilerContext(initial=ctx)

    if singletons:
        # The caller wants us to treat these type references
        # as singletons for the purposes of the overall expression
        # cardinality inference, so we set up the scope tree in
        # the necessary fashion.
        for singleton in singletons:
            path_id = pathctx.get_path_id(singleton, ctx=ctx)
            ctx.env.path_scope.attach_path(path_id)

        ctx.path_scope = ctx.env.path_scope.attach_fence()

    if modaliases:
        ctx.modaliases.update(modaliases)

    if anchors:
        with ctx.newscope(fenced=True) as subctx:
            populate_anchors(anchors, ctx=subctx)

    ctx.derived_target_module = derived_target_module
    ctx.toplevel_result_view_name = result_view_name
    ctx.implicit_id_in_shapes = implicit_id_in_shapes
    ctx.implicit_tid_in_shapes = implicit_tid_in_shapes
    ctx.implicit_limit = implicit_limit

    return ctx