Exemplo n.º 1
0
    def ExtractAnnotations(cls, rules, restrict_to=None, flag_values=None):
        """Extracting annotations from the rules."""
        result = {
            p: collections.OrderedDict()
            for p in cls.ANNOTATING_PREDICATES
        }
        for rule in rules:
            rule_predicate = rule['head']['predicate_name']
            if restrict_to and rule_predicate not in restrict_to:
                continue
            if (rule_predicate[0] == '@'
                    and rule_predicate not in cls.ANNOTATING_PREDICATES):
                raise rule_translate.RuleCompileException(
                    'Only {0} and {1} special predicates are allowed.'.format(
                        ', '.join(cls.ANNOTATING_PREDICATES[:-1]),
                        cls.ANNOTATING_PREDICATES[-1]), rule['full_text'])
            if rule_predicate in cls.ANNOTATING_PREDICATES:
                rule_text = rule['full_text']

                # pylint: disable=cell-var-from-loop
                def ThrowException(*args, **xargs):
                    _ = args
                    _ = xargs
                    if rule_predicate == '@Make':
                        # pylint: disable=raising-format-tuple
                        raise rule_translate.RuleCompileException(
                            'Incorrect syntax for functor call. '
                            'Functor call to be made as\n'
                            '  R := F(A: V, ...)\n'
                            'or\n'
                            '  @Make(R, F, {A: V, ...})\n'
                            'Where R, F, A\'s and V\'s are all '
                            'predicate names.', rule_text)
                    else:
                        raise rule_translate.RuleCompileException(
                            'Can not understand annotation.', rule_text)

                class Thrower(object):
                    def __contains__(self, key):
                        if rule_predicate == '@Make':
                            ThrowException()
                            return
                        raise rule_translate.RuleCompileException(
                            'Annotation may not use variables, but this one uses '
                            'variable %s.' % (color.Warn(key)), rule_text)

                flag_values = flag_values or Thrower()
                ql = expr_translate.QL(Thrower(), ThrowException,
                                       ThrowException, flag_values)
                ql.convert_to_json = True

                annotation = rule['head']['predicate_name']
                field_values_json_str = ql.ConvertToSql(
                    {'record': rule['head']['record']})
                try:
                    field_values = json.loads(field_values_json_str)
                except:
                    raise rule_translate.RuleCompileException(
                        'Could not understand arguments of annotation.',
                        rule['full_text'])
                if ('0' in field_values and isinstance(field_values['0'], dict)
                        and 'predicate_name' in field_values['0']):
                    subject = field_values['0']['predicate_name']
                else:
                    subject = field_values['0']
                del field_values['0']
                if rule_predicate in ['@OrderBy', '@Limit', '@NoInject']:
                    field_values_list = FieldValuesAsList(field_values)
                    if field_values_list is None:
                        raise rule_translate.RuleCompileException(
                            '@OrderBy and @Limit may only have positional '
                            'arguments.', rule['full_text'])
                    if rule_predicate == '@Limit' and len(
                            field_values_list) != 1:
                        raise rule_translate.RuleCompileException(
                            'Annotation @Limit must have exactly two arguments: '
                            'predicate and limit.', rule['full_text'])
                updated_annotation = result.get(annotation, {})
                field_values['__rule_text'] = rule['full_text']
                if subject in updated_annotation:
                    raise rule_translate.RuleCompileException(
                        color.Format(
                            '{annotation} annotates {warning}{subject}{end} more '
                            'than once: {before}, {after}',
                            dict(annotation=annotation,
                                 subject=subject,
                                 before=updated_annotation[subject]
                                 ['__rule_text'],
                                 after=field_values['__rule_text'])),
                        rule['full_text'])
                updated_annotation[subject] = field_values
                result[annotation] = updated_annotation
        return result
Exemplo n.º 2
0
    def FunctionSql(self, name, allocator=None, internal_mode=False):
        """Print formatted SQL function creation statement."""
        # TODO: Refactor this into FunctionSqlInternal and FunctionSql.
        if not allocator:
            allocator = self.NewNamesAllocator()

        rules = list(self.GetPredicateRules(name))

        # Check that the predicate is defined via a single rule.
        if not rules:
            raise rule_translate.RuleCompileException(
                color.Format(
                    'No rules are defining {warning}{name}{end}, but compilation '
                    'was requested.', dict(name=name)), r'        ¯\_(ツ)_/¯')
        elif len(rules) > 1:
            raise rule_translate.RuleCompileException(
                color.Format(
                    'Predicate {warning}{name}{end} is defined by more than 1 rule '
                    'and can not be compiled into a function.',
                    dict(name=name)),
                '\n\n'.join(r['full_text'] for r in rules))
        [rule] = rules

        # Extract structure and assert that it is isomorphic to a function.
        s = rule_translate.ExtractRuleStructure(rule,
                                                external_vocabulary=None,
                                                names_allocator=allocator)

        udf_variables = [
            v if isinstance(v, str) else 'col%d' % v for v in s.select
            if v != 'logica_value'
        ]
        s.select = self.TurnPositionalIntoNamed(s.select)

        variables = [v for v in s.select if v != 'logica_value']
        if 0 in variables:
            raise rule_translate.RuleCompileException(
                color.Format(
                    'Predicate {warning}{name}{end} must have all aruments named for '
                    'compilation as a function.', dict(name=name)),
                rule['full_text'])
        for v in variables:
            if ('variable' not in s.select[v]
                    or s.select[v]['variable']['var_name'] != v):
                raise rule_translate.RuleCompileException(
                    color.Format(
                        'Predicate {warning}{name}{end} must not rename arguments '
                        'for compilation as a function.', dict(name=name)),
                    rule['full_text'])

        vocabulary = {v: v for v in variables}
        s.external_vocabulary = vocabulary
        self.RunInjections(s, allocator)
        s.ElliminateInternalVariables(assert_full_ellimination=True)
        s.UnificationsToConstraints()
        sql = s.AsSql(subquery_encoder=self.MakeSubqueryTranslator(allocator))
        if s.constraints or s.unnestings or s.tables:
            raise rule_translate.RuleCompileException(
                color.Format(
                    'Predicate {warning}{name}{end} is not a simple function, but '
                    'compilation as function was requested. Full SQL:\n{sql}',
                    dict(name=name, sql=sql)), rule['full_text'])
        if 'logica_value' not in s.select:
            raise rule_translate.RuleCompileException(
                color.Format(
                    'Predicate {warning}{name}{end} does not have a value, but '
                    'compilation as function was requested. Full SQL:\n%s' %
                    sql), rule['full_text'])

        # pylint: disable=g-long-lambda
        # Compile the function!
        ql = expr_translate.QL(
            vocabulary,
            self.MakeSubqueryTranslator(allocator),
            lambda message: rule_translate.RuleCompileException(
                message, rule['full_text']),
            self.flag_values,
            custom_udfs=self.custom_udfs,
            dialect=self.execution.dialect)
        value_sql = ql.ConvertToSql(s.select['logica_value'])

        sql = 'CREATE TEMP FUNCTION {name}({signature}) AS ({value})'.format(
            name=name,
            signature=', '.join('%s ANY TYPE' % v for v in variables),
            value=value_sql)

        sql = FormatSql(sql)

        if internal_mode:
            return ('%s(%s)' % (name, ', '.join('{%s}' % v
                                                for v in udf_variables)), sql)

        return sql
Exemplo n.º 3
0
    def AsSql(self, subquery_encoder=None, flag_values=None):
        """Outputing SQL representing this structure."""
        # pylint: disable=g-long-lambda
        ql = expr_translate.QL(
            self.VarsVocabulary(),
            subquery_encoder,
            lambda message: RuleCompileException(message, self.full_rule_text),
            flag_values,
            custom_udfs=subquery_encoder.execution.custom_udfs,
            dialect=subquery_encoder.execution.dialect)
        r = 'SELECT\n'
        fields = []
        if not self.select:
            raise RuleCompileException(
                color.Format(
                    'Tables with {warning}no columns{end} are not allowed in '
                    'StandardSQL, so they are not allowed in Logica.'),
                self.full_rule_text)

        for k, v in self.select.items():
            if k == '*':
                fields.append('%s.*' % ql.ConvertToSql(v))
            else:
                fields.append('%s AS %s' %
                              (ql.ConvertToSql(v), LogicaFieldToSqlField(k)))
        r += ',\n'.join('  ' + f for f in fields)
        if (self.tables or self.unnestings or self.constraints
                or self.distinct_denoted):
            r += '\nFROM\n'
            tables = []
            for k, v in self.tables.items():
                if subquery_encoder:
                    # Note that we are passing external_vocabulary, not VarsVocabulary
                    # here. I.e. if this is a sub-query then variables of outer tables
                    # can be used.
                    sql = subquery_encoder.TranslateTable(
                        v, self.external_vocabulary)
                    if not sql:
                        raise RuleCompileException(
                            color.Format(
                                'Rule uses table {warning}{table}{end}, which is not '
                                'defined. External tables can not be used in '
                                '{warning}\'testrun\'{end} mode. This error may come '
                                'from injected sub-rules.', dict(table=v)),
                            self.full_rule_text)
                if sql != k:
                    tables.append(sql + ' AS ' + k)
                else:
                    tables.append(sql)
            self.SortUnnestings()
            for element, the_list in self.unnestings:
                tables.append(
                    'UNNEST(%s) as %s' %
                    (ql.ConvertToSql(the_list), ql.ConvertToSql(element)))
            if not tables:
                tables.append('UNNEST(ARRAY[\'UNUSED\']) as unused_unnest')
            from_str = ', '.join(tables)
            # Indent the from_str.
            from_str = '\n'.join('  ' + l for l in from_str.split('\n'))
            r += from_str
            if self.constraints:
                r += '\nWHERE\n'
                constraints = []
                for c in self.constraints:
                    constraints.append(ql.ConvertToSql(c))
                r += ' AND\n'.join(map(Indent2, constraints))
            if self.distinct_vars:
                ordered_distinct_vars = [
                    v for v in self.select.keys() if v in self.distinct_vars
                ]
                r += '\nGROUP BY '
                r += ', '.join(
                    map(LogicaFieldToSqlField, ordered_distinct_vars))
        return r