def compileNullTest(edge): """ RETURN A MVEL EXPRESSION THAT WILL EVALUATE TO true FOR OUT-OF-BOUNDS """ if edge.domain.type not in domains.ALGEBRAIC: Log.error("can only translate time and duration domains") # IS THERE A LIMIT ON THE DOMAIN? value = edge.value if isKeyword(value): value = "doc[\"" + value + "\"].value" if not edge.domain.max: if not edge.domain.min: return False bot = value2MVEL(edge.domain.min) nullTest = "" + value + "<" + bot elif not edge.domain.min: top = value2MVEL(edge.domain.max) nullTest = "" + value + ">=" + top else: top = value2MVEL(edge.domain.max) bot = value2MVEL(edge.domain.min) nullTest = "(" + value + "<" + bot + ") or (" + value + ">=" + top + ")" return nullTest
def compileDuration2Term(edge): if edge.esscript: Log.error("edge script not supported yet") # IS THERE A LIMIT ON THE DOMAIN? numPartitions = len(edge.domain.partitions) value = edge.value if isKeyword(value): value = "doc[\"" + value + "\"].value" ref = coalesce(edge.domain.min, edge.domain.max, durations.ZERO) nullTest = compileNullTest(edge) ms = edge.domain.interval.milli if edge.domain.interval.month > 0: ms = durations.YEAR.milli / 12 * edge.domain.interval.month partition2int = "Math.floor((" + value + "-" + value2MVEL(ref) + ")/" + ms + ")" partition2int = "((" + nullTest + ") ? " + numPartitions + " : " + partition2int + ")" def int2Partition(value): if Math.round(value) == numPartitions: return edge.domain.NULL return edge.domain.getPartByKey(ref.add(edge.domain.interval.multiply(value))) return Data(toTerm={"head": "", "body": partition2int}, fromTerm=int2Partition)
def compileDuration2Term(edge): if edge.esscript: Log.error("edge script not supported yet") # IS THERE A LIMIT ON THE DOMAIN? numPartitions = len(edge.domain.partitions) value = edge.value if isKeyword(value): value = "doc[\"" + value + "\"].value" ref = coalesce(edge.domain.min, edge.domain.max, durations.ZERO) nullTest = compileNullTest(edge) ms = edge.domain.interval.milli if edge.domain.interval.month > 0: ms = durations.YEAR.milli / 12 * edge.domain.interval.month partition2int = "Math.floor((" + value + "-" + value2MVEL( ref) + ")/" + ms + ")" partition2int = "((" + nullTest + ") ? " + numPartitions + " : " + partition2int + ")" def int2Partition(value): if Math.round(value) == numPartitions: return edge.domain.NULL return edge.domain.getPartByKey( ref.add(edge.domain.interval.multiply(value))) return Data(toTerm={ "head": "", "body": partition2int }, fromTerm=int2Partition)
def compileString2Term(edge): if edge.esscript: Log.error("edge script not supported yet") value = edge.value if isKeyword(value): value = strings.expand_template("getDocValue({{path}})", {"path": convert.string2quote(value)}) else: Log.error("not handled") def fromTerm(value): return edge.domain.getPartByKey(value) return Dict(toTerm={"head": "", "body": value}, fromTerm=fromTerm)
def compileNumeric2Term(edge): if edge.script: Log.error("edge script not supported yet") if edge.domain.type != "numeric" and edge.domain.type != "count": Log.error("can only translate numeric domains") numPartitions = len(edge.domain.partitions) value = edge.value if isKeyword(value): value = "doc[\"" + value + "\"].value" if not edge.domain.max: if not edge.domain.min: ref = 0 partition2int = "Math.floor(" + value + ")/" + value2MVEL( edge.domain.interval) + ")" nullTest = "false" else: ref = value2MVEL(edge.domain.min) partition2int = "Math.floor((" + value + "-" + ref + ")/" + value2MVEL( edge.domain.interval) + ")" nullTest = "" + value + "<" + ref elif not edge.domain.min: ref = value2MVEL(edge.domain.max) partition2int = "Math.floor((" + value + "-" + ref + ")/" + value2MVEL( edge.domain.interval) + ")" nullTest = "" + value + ">=" + ref else: top = value2MVEL(edge.domain.max) ref = value2MVEL(edge.domain.min) partition2int = "Math.floor((" + value + "-" + ref + ")/" + value2MVEL( edge.domain.interval) + ")" nullTest = "(" + value + "<" + ref + ") or (" + value + ">=" + top + ")" partition2int = "((" + nullTest + ") ? " + numPartitions + " : " + partition2int + ")" offset = convert.value2int(ref) def int2Partition(value): if Math.round(value) == numPartitions: return edge.domain.NULL return edge.domain.getPartByKey((value * edge.domain.interval) + offset) return Data(toTerm={ "head": "", "body": partition2int }, fromTerm=int2Partition)
def compileString2Term(edge): if edge.esscript: Log.error("edge script not supported yet") value = edge.value if isKeyword(value): value = strings.expand_template("getDocValue({{path}})", {"path": convert.string2quote(value)}) else: Log.error("not handled") def fromTerm(value): return edge.domain.getPartByKey(value) return Data(toTerm={"head": "", "body": value}, fromTerm=fromTerm)
def compileTime2Term(edge): """ RETURN MVEL CODE THAT MAPS TIME AND DURATION DOMAINS DOWN TO AN INTEGER AND AND THE JAVASCRIPT THAT WILL TURN THAT INTEGER BACK INTO A PARTITION (INCLUDING NULLS) """ if edge.esscript: Log.error("edge script not supported yet") # IS THERE A LIMIT ON THE DOMAIN? numPartitions = len(edge.domain.partitions) value = edge.value if isKeyword(value): value = "doc[\"" + value + "\"].value" nullTest = compileNullTest(edge) ref = coalesce(edge.domain.min, edge.domain.max, datetime(2000, 1, 1)) if edge.domain.interval.month > 0: offset = ref.subtract(ref.floorMonth(), durations.DAY).milli if offset > durations.DAY.milli * 28: offset = ref.subtract(ref.ceilingMonth(), durations.DAY).milli partition2int = "milli2Month(" + value + ", " + value2MVEL( offset) + ")" partition2int = "((" + nullTest + ") ? 0 : " + partition2int + ")" def int2Partition(value): if Math.round(value) == 0: return edge.domain.NULL d = datetime(str(value)[:4:], str(value)[-2:], 1) d = d.addMilli(offset) return edge.domain.getPartByKey(d) else: partition2int = "Math.floor((" + value + "-" + value2MVEL( ref) + ")/" + edge.domain.interval.milli + ")" partition2int = "((" + nullTest + ") ? " + numPartitions + " : " + partition2int + ")" def int2Partition(value): if Math.round(value) == numPartitions: return edge.domain.NULL return edge.domain.getPartByKey( ref.add(edge.domain.interval.multiply(value))) return Data(toTerm={ "head": "", "body": partition2int }, fromTerm=int2Partition)
def compileTime2Term(edge): """ RETURN MVEL CODE THAT MAPS TIME AND DURATION DOMAINS DOWN TO AN INTEGER AND AND THE JAVASCRIPT THAT WILL TURN THAT INTEGER BACK INTO A PARTITION (INCLUDING NULLS) """ if edge.esscript: Log.error("edge script not supported yet") # IS THERE A LIMIT ON THE DOMAIN? numPartitions = len(edge.domain.partitions) value = edge.value if isKeyword(value): value = 'doc["' + value + '"].value' nullTest = compileNullTest(edge) ref = coalesce(edge.domain.min, edge.domain.max, datetime(2000, 1, 1)) if edge.domain.interval.month > 0: offset = ref.subtract(ref.floorMonth(), durations.DAY).milli if offset > durations.DAY.milli * 28: offset = ref.subtract(ref.ceilingMonth(), durations.DAY).milli partition2int = "milli2Month(" + value + ", " + value2MVEL(offset) + ")" partition2int = "((" + nullTest + ") ? 0 : " + partition2int + ")" def int2Partition(value): if Math.round(value) == 0: return edge.domain.NULL d = datetime(str(value)[:4:], str(value)[-2:], 1) d = d.addMilli(offset) return edge.domain.getPartByKey(d) else: partition2int = "Math.floor((" + value + "-" + value2MVEL(ref) + ")/" + edge.domain.interval.milli + ")" partition2int = "((" + nullTest + ") ? " + numPartitions + " : " + partition2int + ")" def int2Partition(value): if Math.round(value) == numPartitions: return edge.domain.NULL return edge.domain.getPartByKey(ref.add(edge.domain.interval.multiply(value))) return Dict(toTerm={"head": "", "body": partition2int}, fromTerm=int2Partition)
def compileNumeric2Term(edge): if edge.script: Log.error("edge script not supported yet") if edge.domain.type != "numeric" and edge.domain.type != "count": Log.error("can only translate numeric domains") numPartitions = len(edge.domain.partitions) value = edge.value if isKeyword(value): value = "doc[\"" + value + "\"].value" if not edge.domain.max: if not edge.domain.min: ref = 0 partition2int = "Math.floor(" + value + ")/" + value2MVEL(edge.domain.interval) + ")" nullTest = "false" else: ref = value2MVEL(edge.domain.min) partition2int = "Math.floor((" + value + "-" + ref + ")/" + value2MVEL(edge.domain.interval) + ")" nullTest = "" + value + "<" + ref elif not edge.domain.min: ref = value2MVEL(edge.domain.max) partition2int = "Math.floor((" + value + "-" + ref + ")/" + value2MVEL(edge.domain.interval) + ")" nullTest = "" + value + ">=" + ref else: top = value2MVEL(edge.domain.max) ref = value2MVEL(edge.domain.min) partition2int = "Math.floor((" + value + "-" + ref + ")/" + value2MVEL(edge.domain.interval) + ")" nullTest = "(" + value + "<" + ref + ") or (" + value + ">=" + top + ")" partition2int = "((" + nullTest + ") ? " + numPartitions + " : " + partition2int + ")" offset = convert.value2int(ref) def int2Partition(value): if Math.round(value) == numPartitions: return edge.domain.NULL return edge.domain.getPartByKey((value * edge.domain.interval) + offset) return Data(toTerm={"head": "", "body": partition2int}, fromTerm=int2Partition)
def compileEdges2Term(mvel_compiler, edges, constants): """ TERMS ARE ALWAYS ESCAPED SO THEY CAN BE COMPOUNDED WITH PIPE (|) GIVE MVEL CODE THAT REDUCES A UNIQUE TUPLE OF PARTITIONS DOWN TO A UNIQUE TERM GIVE LAMBDA THAT WILL CONVERT THE TERM BACK INTO THE TUPLE RETURNS TUPLE OBJECT WITH "type" and "value" ATTRIBUTES. "type" CAN HAVE A VALUE OF "script", "field" OR "count" CAN USE THE constants (name, value pairs) """ # IF THE QUERY IS SIMPLE ENOUGH, THEN DO NOT USE TERM PACKING edge0 = edges[0] if len(edges) == 1 and edge0.domain.type in ["set", "default"]: # THE TERM RETURNED WILL BE A MEMBER OF THE GIVEN SET def temp(term): return FlatList([edge0.domain.getPartByKey(term)]) if edge0.value and isKeyword(edge0.value): return Data( field=edge0.value, term2parts=temp ) elif COUNT(edge0.domain.dimension.fields) == 1: return Data( field=edge0.domain.dimension.fields[0], term2parts=temp ) elif not edge0.value and edge0.domain.partitions: script = mvel_compiler.Parts2TermScript(edge0.domain) return Data( expression=script, term2parts=temp ) else: return Data( expression=mvel_compiler.compile_expression(edge0.value, constants), term2parts=temp ) mvel_terms = [] # FUNCTION TO PACK TERMS fromTerm2Part = [] # UNPACK TERMS BACK TO PARTS for e in edges: domain = e.domain fields = domain.dimension.fields if not e.value and fields: code, decode = mvel_compiler.Parts2Term(e.domain) t = Data( toTerm=code, fromTerm=decode ) elif fields: Log.error("not expected") elif e.domain.type == "time": t = compileTime2Term(e) elif e.domain.type == "duration": t = compileDuration2Term(e) elif e.domain.type in domains.ALGEBRAIC: t = compileNumeric2Term(e) elif e.domain.type == "set" and not fields: def fromTerm(term): return e.domain.getPartByKey(term) code, decode = mvel_compiler.Parts2Term(e.domain) t = Data( toTerm=code, fromTerm=decode ) else: t = compileString2Term(e) if not t.toTerm.body: mvel_compiler.Parts2Term(e.domain) Log.unexpected("what?") fromTerm2Part.append(t.fromTerm) mvel_terms.append(t.toTerm.body) # REGISTER THE DECODE FUNCTION def temp(term): terms = term.split('|') output = FlatList([t2p(t) for t, t2p in zip(terms, fromTerm2Part)]) return output return Data( expression=mvel_compiler.compile_expression("+'|'+".join(mvel_terms), constants), term2parts=temp )
def compileEdges2Term(mvel_compiler, edges, constants): """ TERMS ARE ALWAYS ESCAPED SO THEY CAN BE COMPOUNDED WITH PIPE (|) GIVE MVEL CODE THAT REDUCES A UNIQUE TUPLE OF PARTITIONS DOWN TO A UNIQUE TERM GIVE LAMBDA THAT WILL CONVERT THE TERM BACK INTO THE TUPLE RETURNS TUPLE OBJECT WITH "type" and "value" ATTRIBUTES. "type" CAN HAVE A VALUE OF "script", "field" OR "count" CAN USE THE constants (name, value pairs) """ # IF THE QUERY IS SIMPLE ENOUGH, THEN DO NOT USE TERM PACKING edge0 = edges[0] if len(edges) == 1 and edge0.domain.type in ["set", "default"]: # THE TERM RETURNED WILL BE A MEMBER OF THE GIVEN SET def temp(term): return FlatList([edge0.domain.getPartByKey(term)]) if edge0.value and isKeyword(edge0.value): return Data(field=edge0.value, term2parts=temp) elif COUNT(edge0.domain.dimension.fields) == 1: return Data(field=edge0.domain.dimension.fields[0], term2parts=temp) elif not edge0.value and edge0.domain.partitions: script = mvel_compiler.Parts2TermScript(edge0.domain) return Data(expression=script, term2parts=temp) else: return Data(expression=mvel_compiler.compile_expression( edge0.value, constants), term2parts=temp) mvel_terms = [] # FUNCTION TO PACK TERMS fromTerm2Part = [] # UNPACK TERMS BACK TO PARTS for e in edges: domain = e.domain fields = domain.dimension.fields if not e.value and fields: code, decode = mvel_compiler.Parts2Term(e.domain) t = Data(toTerm=code, fromTerm=decode) elif fields: Log.error("not expected") elif e.domain.type == "time": t = compileTime2Term(e) elif e.domain.type == "duration": t = compileDuration2Term(e) elif e.domain.type in domains.ALGEBRAIC: t = compileNumeric2Term(e) elif e.domain.type == "set" and not fields: def fromTerm(term): return e.domain.getPartByKey(term) code, decode = mvel_compiler.Parts2Term(e.domain) t = Data(toTerm=code, fromTerm=decode) else: t = compileString2Term(e) if not t.toTerm.body: mvel_compiler.Parts2Term(e.domain) Log.unexpected("what?") fromTerm2Part.append(t.fromTerm) mvel_terms.append(t.toTerm.body) # REGISTER THE DECODE FUNCTION def temp(term): terms = term.split('|') output = FlatList([t2p(t) for t, t2p in zip(terms, fromTerm2Part)]) return output return Data(expression=mvel_compiler.compile_expression( "+'|'+".join(mvel_terms), constants), term2parts=temp)