Beispiel #1
0
 def GetValueOfField(field_values, field):
     for field_value in field_values:
         if field_value['field'] == field:
             return field_value['value']['expression']
     raise self.exception_maker(
         'Expected field %s missing in a record inside %s statement.' %
         (color.Warn(subscript), color.Warn('if')))
     assert False
Beispiel #2
0
 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)
Beispiel #3
0
    def PredicateSql(self, name, allocator=None, external_vocabulary=None):
        """Producing SQL for a predicate."""
        # Load proto if necessary.
        rules = list(self.GetPredicateRules(name))
        if len(rules) == 1:
            [rule] = rules
            result = (
                self.SingleRuleSql(rule, allocator, external_vocabulary) +
                self.annotations.OrderByClause(name) +
                self.annotations.LimitClause(name))
            if result.startswith('/* nil */'):
                raise rule_translate.RuleCompileException(
                    'Single rule is nil for predicate %s. '
                    'Recursion unfolding failed.' % color.Warn(name),
                    rule['full_text'])
            return result
        elif len(rules) > 1:
            rules_sql = []
            for rule in rules:
                if 'distinct_denoted' in rule:
                    raise rule_translate.RuleCompileException(
                        color.Format(
                            'For distinct denoted predicates multiple rules are not '
                            'currently supported. Consider taking '
                            '{warning}union of bodies manually{end}, if that was what '
                            'you intended.'), rule['full_text'])
                single_rule_sql = self.SingleRuleSql(rule, allocator,
                                                     external_vocabulary)
                if not single_rule_sql.startswith('/* nil */'):
                    rules_sql.append('\n%s\n' % Indent2(single_rule_sql))
            if not rules_sql:
                raise rule_translate.RuleCompileException(
                    'All disjuncts are nil for predicate %s.' %
                    color.Warn(name), rule['full_text'])

            rules_sql = [
                '\n'.join('  ' + l for l in r.split('\n')) for r in rules_sql
            ]
            return 'SELECT * FROM (\n%s\n) AS UNUSED_TABLE_NAME %s %s' % (
                ' UNION ALL\n'.join(rules_sql),
                self.annotations.OrderByClause(name),
                self.annotations.LimitClause(name))
        else:
            raise rule_translate.RuleCompileException(
                color.Format(
                    'No rules are defining {warning}{name}{end}, but compilation '
                    'was requested.', dict(name=name)), r'        ¯\_(ツ)_/¯')
Beispiel #4
0
 def CheckAnnotatedObjects(self, rules):
   """Verify that annotations are applied to existing predicates."""
   all_predicates = {rule['head']['predicate_name'] for rule in rules} | set(
       self.annotations['@Ground']) | set(self.annotations['@Make'])
   for annotation_name in self.annotations:
     if annotation_name in {'@Limit', '@OrderBy',
                            '@NoInject', '@CompileAsTvf', '@With', '@NoWith',
                            '@CompileAsUdf'}:
       for annotated_predicate in self.annotations[annotation_name]:
         if annotated_predicate not in all_predicates:
           rule_text = self.annotations[annotation_name][annotated_predicate][
               '__rule_text']
           RaiseCompilerError(
               'Annotation %s must be applied to an existing predicate, but '
               'it was applied to a non-existing predicate %s.' %
               (color.Warn(annotation_name), color.Warn(annotated_predicate)),
               rule_text)
Beispiel #5
0
    def ConvertToSql(self, expression):
        """Converting Logica expression into SQL."""
        # print('EXPR:', expression)
        # Variables.
        if 'variable' in expression:
            return self.Variable(expression['variable'])

        # Literals.
        if 'literal' in expression:
            literal = expression['literal']
            if 'the_number' in literal:
                return self.IntLiteral(literal['the_number'])
            if 'the_string' in literal:
                return self.StrLiteral(literal['the_string'])
            if 'the_list' in literal:
                return self.ListLiteral(literal['the_list'])
            if 'the_bool' in literal:
                return self.BoolLiteral(literal['the_bool'])
            if 'the_null' in literal:
                return self.NullLiteral(literal['the_null'])
            if 'the_predicate' in literal:
                return self.PredicateLiteral(literal['the_predicate'])
            assert False, 'Logica bug: unsupported literal parsed: %s' % literal

        if 'call' in expression:
            call = expression['call']
            arguments = self.ConvertRecord(call['record'])
            if call['predicate_name'] in self.ANALYTIC_FUNCTIONS:
                return self.ConvertAnalytic(call)
            if call['predicate_name'] == 'SqlExpr':
                return self.GenericSqlExpression(call['record'])
            if call['predicate_name'] == 'Cast':
                if (len(arguments) != 2 or 'literal' not in call['record']
                    ['field_value'][1]['value']['expression']
                        or 'the_string' not in call['record']['field_value'][1]
                    ['value']['expression']['literal']):
                    raise self.exception_maker(
                        'Cast must have 2 arguments and the second argument must be a '
                        'string literal.')
                cast_to = (
                    call['record']['field_value'][1]['value']['expression']
                    ['literal']['the_string']['the_string'])
                return 'CAST(%s AS %s)' % (self.ConvertToSql(
                    call['record']['field_value'][0]['value']['expression']),
                                           cast_to)

            if call['predicate_name'] == 'FlagValue':
                if (len(arguments) != 1 or 'literal' not in call['record']
                    ['field_value'][0]['value']['expression']
                        or 'the_string' not in call['record']['field_value'][0]
                    ['value']['expression']['literal']):
                    raise self.exception_maker(
                        'FlagValue argument must be a string literal.')
                flag = (call['record']['field_value'][0]['value']['expression']
                        ['literal']['the_string']['the_string'])
                if flag not in self.flag_values:
                    raise self.exception_maker('Unspecified flag: %s' % flag)
                return self.StrLiteral({'the_string': self.flag_values[flag]})
            for ydg_f, sql_f in self.built_in_functions.items():
                if call['predicate_name'] == ydg_f:
                    if not sql_f:
                        raise self.exception_maker(
                            'Function %s is not supported by %s dialect.' %
                            (color.Warn(ydg_f), color.Warn(
                                self.dialect.Name())))
                    if len(arguments) == 2 and ydg_f == '-':
                        continue  # '-' is the only operator with variable arity.
                    arity_range = self.BuiltInFunctionArityRange(ydg_f)
                    if not arity_range[0] <= len(arguments) <= arity_range[1]:
                        raise self.exception_maker(
                            color.Format(
                                'Built-in function {warning}{ydg_f}{end} takes {a} '
                                'arguments, but {b} arguments were given.',
                                dict(ydg_f=ydg_f,
                                     a=arity_range,
                                     b=len(arguments))))
                    return self.Function(sql_f, arguments)

            for udf, udf_sql in self.custom_udfs.items():
                if call['predicate_name'] == udf:
                    # TODO: Treatment of positional arguments should be
                    # simplified everywhere.
                    arguments = dict(
                        (k, v) if isinstance(k, str) else ('col%d' % k, v)
                        for k, v in arguments.items())
                    try:
                        result = udf_sql.format(**arguments)
                    except KeyError:
                        raise self.exception_maker(
                            'Function %s call is inconsistent with its signature %s.'
                            % (color.Warn(udf), udf_sql))
                    return result

            for ydg_op, sql_op in self.built_in_infix_operators.items():
                if call['predicate_name'] == ydg_op:
                    result = self.Infix(sql_op, arguments)
                    result = '(' + result + ')'
                    return result

        if 'subscript' in expression:
            sub = expression['subscript']
            subscript = sub['subscript']['literal']['the_symbol']['symbol']
            # TODO: Record literal and record of subscript should have
            # different keys.
            # Try to opimize and return the field from a record.
            if 'record' in sub['record']:
                for f_v in sub['record']['record']['field_value']:
                    if f_v['field'] == subscript:
                        # Optimizing and returning the field directly.
                        return self.ConvertToSql(f_v['value']['expression'])
            # Trying to optmize subscript of implication.
            if 'implication' in sub['record']:
                simplified_sub = self.SubIfStruct(sub['record']['implication'],
                                                  subscript)
                if simplified_sub:
                    return simplified_sub
            # Couldn't optimize, just return the '.' expression.
            record = self.ConvertToSql(sub['record'])
            return self.Subscript(record, subscript)

        if 'record' in expression:
            record = expression['record']
            return self.Record(record)

        if 'combine' in expression:
            return '(%s)' % (self.subquery_translator.TranslateRule(
                expression['combine'], self.vocabulary, is_combine=True))

        if 'implication' in expression:
            implication = expression['implication']
            return self.Implication(implication)

        if 'call' in expression and 'predicate_name' in expression['call']:
            raise self.exception_maker(
                color.Format(
                    'Unsupported supposedly built-in function: '
                    '{warning}{predicate}{end}.',
                    dict(predicate=expression['call']['predicate_name'])))
        assert False, (
            'Logica bug: expression %s failed to compile for unknown reason.' %
            str(expression))
Beispiel #6
0
  def CallFunctor(self, name, applicant, args_map):
    """Calling a functor applicant(args_map), storing result to 'name'."""
    bad_args = set(args_map.keys()) - set(self.args_of[applicant])
    if bad_args:
      raise FunctorError(
          'Functor %s is applied to arguments %s, which it does not '
          'have.' % (applicant, color.Warn(','.join(bad_args))),
          name)
    self.creation_count += 1
    rules = self.AllRulesOf(applicant)
    args = set(args_map)
    rules = [r for r in rules
             if ((args & self.args_of[r['head']['predicate_name']]) or
                 r['head']['predicate_name'] == applicant)]
    if not rules:
      raise FunctorError(
          'Rules for %s when making %s are not found' % (applicant, name),
          name)
    # This eventually maps all args to substiturions, as well as all predictes
    # which use one of the args into a newly created predicate names.
    extended_args_map = copy.deepcopy(args_map)
    rules_to_update = []
    cache_update = {}
    predicates_to_annotate = set()
    for r in sorted(rules, key=str):
      rule_predicate_name = r['head']['predicate_name']
      if rule_predicate_name == applicant:
        extended_args_map[rule_predicate_name] = name
        rules_to_update.append(r)
        predicates_to_annotate.add(rule_predicate_name)
      else:
        # TODO: We should also remove predicates that become unused.
        if rule_predicate_name in args_map:
          continue
        call_key = self.CallKey(rule_predicate_name, args_map)
        if call_key in self.cached_calls:
          new_predicate_name = self.cached_calls[call_key]
          extended_args_map[rule_predicate_name] = new_predicate_name
        else:
          new_predicate_name = (
              rule_predicate_name + '_f%d' % self.creation_count)
          extended_args_map[rule_predicate_name] = new_predicate_name
          cache_update[call_key] = new_predicate_name
          rules_to_update.append(r)
          predicates_to_annotate.add(rule_predicate_name)

    rules = rules_to_update
    self.cached_calls.update(cache_update)
    # Collect annotations of all involved predicates.
    # Newly created predicates inherit annotations of predicates from which
    # they were created.
    # Arguments values of the functor do not inherit annotations of the
    # arguments, as that would collide with their performance in other
    # contexts.
    # In particular it means that functors should not use @Limit and @OrderBy
    # of the arguments to express the logic. Instead they should create simple
    # predicates reading from the agument and annotate those.
    annotations = self.CollectAnnotations(list(predicates_to_annotate))
    rules.extend(annotations)
    def ReplacePredicate(x):
      if isinstance(x, dict) and 'predicate_name' in x:
        if x['predicate_name'] in extended_args_map:
          x['predicate_name'] = extended_args_map[x['predicate_name']]
      return []
    Walk(rules, ReplacePredicate, lambda _: True)
    self.extended_rules.extend(rules)
    self.UpdateStructure()