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
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)
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' ¯\_(ツ)_/¯')
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)
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))
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()