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
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
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