From ea715033d30fabbe80d6d878014b820083f4ac3a Mon Sep 17 00:00:00 2001 From: Kyle Lahnakoski Date: Sun, 10 Mar 2024 15:04:42 -0400 Subject: [PATCH] updates from other projects --- jx_base/expressions/__init__.py | 2 ++ jx_base/expressions/_utils.py | 1 + jx_base/expressions/base_multi_op.py | 2 +- jx_base/expressions/case_op.py | 2 +- jx_base/expressions/literal.py | 5 ++- jx_base/expressions/sql_and_op.py | 13 ++++---- jx_base/expressions/sql_concat_op.py | 48 +++++++++++++++++++++++++++ jx_base/expressions/sql_eq_op.py | 6 ++++ jx_base/expressions/sql_is_null_op.py | 2 +- jx_base/expressions/sql_or_op.py | 2 +- jx_base/expressions/sql_substr_op.py | 5 ++- jx_base/expressions/to_text_op.py | 2 ++ 12 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 jx_base/expressions/sql_concat_op.py diff --git a/jx_base/expressions/__init__.py b/jx_base/expressions/__init__.py index 33ef76e..bf93a15 100644 --- a/jx_base/expressions/__init__.py +++ b/jx_base/expressions/__init__.py @@ -102,6 +102,7 @@ from jx_base.expressions.sql_alias_op import SqlAliasOp from jx_base.expressions.sql_and_op import SqlAndOp from jx_base.expressions.sql_cast_op import SqlCastOp +from jx_base.expressions.sql_concat_op import SqlConcatOp from jx_base.expressions.sql_eq_op import SqlEqOp from jx_base.expressions.sql_group_by_op import SqlGroupByOp from jx_base.expressions.sql_gt_op import SqlGtOp @@ -229,6 +230,7 @@ "split": SplitOp, "sql.and": SqlAndOp, "sql.alias": SqlAliasOp, + "sql.concat": SqlConcatOp, "sql.eq": SqlEqOp, "sql.group_by": SqlGroupByOp, "sql.inner_join": SqlInnerJoinOp, diff --git a/jx_base/expressions/_utils.py b/jx_base/expressions/_utils.py index e38fb02..28c5838 100644 --- a/jx_base/expressions/_utils.py +++ b/jx_base/expressions/_utils.py @@ -152,6 +152,7 @@ def merge_types(jx_types): "min": lambda *v: min(*v), "most": lambda *v: max(*v), "least": lambda *v: min(*v), + "sql.concat": lambda *v: "".join(*v) } operators = {} diff --git a/jx_base/expressions/base_multi_op.py b/jx_base/expressions/base_multi_op.py index 1934a0f..d28b5c7 100644 --- a/jx_base/expressions/base_multi_op.py +++ b/jx_base/expressions/base_multi_op.py @@ -66,7 +66,7 @@ def partial_eval(self, lang): literal_acc = None terms = [] for t in self.terms: - simple = ToNumberOp(t).partial_eval(lang) + simple = t.partial_eval(lang) if simple is NULL: if self.decisive: pass diff --git a/jx_base/expressions/case_op.py b/jx_base/expressions/case_op.py index 752ab47..884a445 100644 --- a/jx_base/expressions/case_op.py +++ b/jx_base/expressions/case_op.py @@ -38,7 +38,7 @@ def __init__(self, *terms): for w in self._whens: if not is_op(w, WhenOp) or w.els_ is not NULL: - Log.error("case expression does not allow `else` clause in `when` sub-clause") + Log.error("case expression does not allow `else` clause in `when` sub-clause {case}", case=self.__data__()) @property def whens(self): diff --git a/jx_base/expressions/literal.py b/jx_base/expressions/literal.py index 6825285..3f04e61 100644 --- a/jx_base/expressions/literal.py +++ b/jx_base/expressions/literal.py @@ -15,14 +15,14 @@ from mo_imports import expect, export from mo_json import value2json, value_to_jx_type -(DateOp, - FALSE, TRUE, NULL) = expect("DateOp", "FALSE", "TRUE", "NULL") +(DateOp, FALSE, TRUE, NULL) = expect("DateOp", "FALSE", "TRUE", "NULL") class Literal(Expression): """ A literal JSON document """ + simplified = True def __new__(cls, term): if term == None: @@ -44,7 +44,6 @@ def __new__(cls, term): def __init__(self, value): Expression.__init__(self) - self.simplified = True self._value = value @classmethod diff --git a/jx_base/expressions/sql_and_op.py b/jx_base/expressions/sql_and_op.py index 18c72f2..b4879a4 100644 --- a/jx_base/expressions/sql_and_op.py +++ b/jx_base/expressions/sql_and_op.py @@ -9,18 +9,17 @@ # -from jx_base.expressions.expression import Expression -from jx_base.language import is_op +from jx_base.expressions.base_multi_op import BaseMultiOp from jx_base.expressions.or_op import OrOp +from jx_base.language import is_op from mo_json.types import JX_BOOLEAN -class SqlAndOp(Expression): - _data_type = JX_BOOLEAN +class SqlAndOp(BaseMultiOp): + _jx_type = JX_BOOLEAN - def __init__(self, *terms): - Expression.__init__(self, *terms) - self.terms = terms + def __init__(self, *args): + super().__init__(*args) def __data__(self): return {"sql.and": [t.__data__() for t in self.terms]} diff --git a/jx_base/expressions/sql_concat_op.py b/jx_base/expressions/sql_concat_op.py new file mode 100644 index 0000000..e9dbfde --- /dev/null +++ b/jx_base/expressions/sql_concat_op.py @@ -0,0 +1,48 @@ +# encoding: utf-8 +# +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http:# mozilla.org/MPL/2.0/. +# +# Contact: Kyle Lahnakoski (kyle@lahnakoski.com) +# + + +from jx_base.expressions.expression import Expression +from jx_base.expressions.or_op import OrOp +from jx_base.language import is_op +from mo_json.types import JX_TEXT + + +class SqlConcatOp(Expression): + """ + conservative string concatenation + """ + op = "sql.concat" + _jx_type = JX_TEXT + + def __init__(self, *terms): + Expression.__init__(self, *terms) + self.terms = terms + + def __data__(self): + return {"sql.concat": [t.__data__() for t in self.terms]} + + def missing(self, lang): + return OrOp(*(t.missing(lang) for t in self.terms)) + + def __eq__(self, other): + if not is_op(other, SqlConcatOp): + return False + if len(self.terms) != len(other.terms): + return False + return all(t==o for t, o in zip(self.terms, other.terms)) + + def partial_eval(self, lang): + if not self.terms: + return NULL + elif len(self.terms) == 1: + return self.terms[0].partial_eval(lang) + else: + return lang.SqlConcatOp(*(t.partial_eval(lang) for t in self.terms)) diff --git a/jx_base/expressions/sql_eq_op.py b/jx_base/expressions/sql_eq_op.py index 90c7f6e..e8231bf 100644 --- a/jx_base/expressions/sql_eq_op.py +++ b/jx_base/expressions/sql_eq_op.py @@ -12,6 +12,8 @@ from jx_base.expressions.expression import Expression from jx_base.expressions.false_op import FALSE from jx_base.expressions.literal import ZERO, ONE, is_literal +from jx_base.expressions.null_op import NULL +from jx_base.expressions.sql_is_null_op import SqlIsNullOp from jx_base.language import is_op, value_compare from mo_json.types import JX_BOOLEAN @@ -38,5 +40,9 @@ def partial_eval(self, lang): if is_literal(lhs) and is_literal(rhs): return ZERO if value_compare(lhs.value, rhs.value) else ONE + elif lhs is NULL: + return SqlIsNullOp(self.rhs) + elif rhs is NULL: + return SqlIsNullOp(self.lhs) else: return lang.SqlEqOp(lhs, rhs) diff --git a/jx_base/expressions/sql_is_null_op.py b/jx_base/expressions/sql_is_null_op.py index 93c954c..6a62692 100644 --- a/jx_base/expressions/sql_is_null_op.py +++ b/jx_base/expressions/sql_is_null_op.py @@ -16,7 +16,7 @@ class SqlIsNullOp(Expression): - _data_type = JX_BOOLEAN + _jx_type = JX_BOOLEAN def __init__(self, term): Expression.__init__(self, term) diff --git a/jx_base/expressions/sql_or_op.py b/jx_base/expressions/sql_or_op.py index ba21de5..f9a81fe 100644 --- a/jx_base/expressions/sql_or_op.py +++ b/jx_base/expressions/sql_or_op.py @@ -22,7 +22,7 @@ class SqlOrOp(Expression): """ A CONSERVATIVE OR OPERATION """ - _data_type = JX_BOOLEAN + _jx_type = JX_BOOLEAN def __init__(self, *terms): Expression.__init__(self, *terms) diff --git a/jx_base/expressions/sql_substr_op.py b/jx_base/expressions/sql_substr_op.py index 2d105ee..2a9c1be 100644 --- a/jx_base/expressions/sql_substr_op.py +++ b/jx_base/expressions/sql_substr_op.py @@ -8,16 +8,15 @@ # Contact: Kyle Lahnakoski (kyle@lahnakoski.com) # - +from jx_base.expressions import NULL, FALSE from jx_base.expressions.expression import Expression -from jx_base.expressions.false_op import FALSE from mo_json import JX_INTEGER class SqlSubstrOp(Expression): _jx_type = JX_INTEGER - def __init__(self, value, start, length): + def __init__(self, value, start, length=NULL): Expression.__init__(self, value, start, length) self.value, self.start, self.length = value, start, length diff --git a/jx_base/expressions/to_text_op.py b/jx_base/expressions/to_text_op.py index b286909..b56acf8 100644 --- a/jx_base/expressions/to_text_op.py +++ b/jx_base/expressions/to_text_op.py @@ -54,6 +54,8 @@ def partial_eval(self, lang): return term.term.partial_eval(lang) elif is_op(term, CoalesceOp): return lang.CoalesceOp(*(ToTextOp(t).partial_eval(lang) for t in term.terms)) + elif term.jx_type == JX_TEXT: + return term elif is_literal(term): if term.jx_type == JX_TEXT: return term