Edit on GitHub

# sqlglot.optimizer.simplify

View Source

      1import datetime
      2import functools
      3import itertools
      4from collections import deque
      5from decimal import Decimal
      6
      7from sqlglot import exp
      8from sqlglot.expressions import FALSE, NULL, TRUE
      9from sqlglot.generator import Generator
     10from sqlglot.helper import first, while_changing
     11
     12GENERATOR = Generator(normalize=True, identify=True)
     13
     14
     15def simplify(expression):
     16    """
     17    Rewrite sqlglot AST to simplify expressions.
     18
     19    Example:
     20        >>> import sqlglot
     21        >>> expression = sqlglot.parse_one("TRUE AND TRUE")
     22        >>> simplify(expression).sql()
     23        'TRUE'
     24
     25    Args:
     26        expression (sqlglot.Expression): expression to simplify
     27    Returns:
     28        sqlglot.Expression: simplified expression
     29    """
     30
     31    def _simplify(expression, root=True):
     32        node = expression
     33        node = rewrite_between(node)
     34        node = uniq_sort(node)
     35        node = absorb_and_eliminate(node)
     36        exp.replace_children(node, lambda e: _simplify(e, False))
     37        node = simplify_not(node)
     38        node = flatten(node)
     39        node = simplify_connectors(node)
     40        node = remove_compliments(node)
     41        node.parent = expression.parent
     42        node = simplify_literals(node)
     43        node = simplify_parens(node)
     44        if root:
     45            expression.replace(node)
     46        return node
     47
     48    expression = while_changing(expression, _simplify)
     49    remove_where_true(expression)
     50    return expression
     51
     52
     53def rewrite_between(expression: exp.Expression) -> exp.Expression:
     54    """Rewrite x between y and z to x >= y AND x <= z.
     55
     56    This is done because comparison simplification is only done on lt/lte/gt/gte.
     57    """
     58    if isinstance(expression, exp.Between):
     59        return exp.and_(
     60            exp.GTE(this=expression.this.copy(), expression=expression.args["low"]),
     61            exp.LTE(this=expression.this.copy(), expression=expression.args["high"]),
     62        )
     63    return expression
     64
     65
     66def simplify_not(expression):
     67    """
     68    Demorgan's Law
     69    NOT (x OR y) -> NOT x AND NOT y
     70    NOT (x AND y) -> NOT x OR NOT y
     71    """
     72    if isinstance(expression, exp.Not):
     73        if isinstance(expression.this, exp.Null):
     74            return exp.null()
     75        if isinstance(expression.this, exp.Paren):
     76            condition = expression.this.unnest()
     77            if isinstance(condition, exp.And):
     78                return exp.or_(exp.not_(condition.left), exp.not_(condition.right))
     79            if isinstance(condition, exp.Or):
     80                return exp.and_(exp.not_(condition.left), exp.not_(condition.right))
     81            if isinstance(condition, exp.Null):
     82                return exp.null()
     83        if always_true(expression.this):
     84            return exp.false()
     85        if expression.this == FALSE:
     86            return exp.true()
     87        if isinstance(expression.this, exp.Not):
     88            # double negation
     89            # NOT NOT x -> x
     90            return expression.this.this
     91    return expression
     92
     93
     94def flatten(expression):
     95    """
     96    A AND (B AND C) -> A AND B AND C
     97    A OR (B OR C) -> A OR B OR C
     98    """
     99    if isinstance(expression, exp.Connector):
    100        for node in expression.args.values():
    101            child = node.unnest()
    102            if isinstance(child, expression.__class__):
    103                node.replace(child)
    104    return expression
    105
    106
    107def simplify_connectors(expression):
    108    def _simplify_connectors(expression, left, right):
    109        if isinstance(expression, exp.Connector):
    110            if left == right:
    111                return left
    112            if isinstance(expression, exp.And):
    113                if FALSE in (left, right):
    114                    return exp.false()
    115                if NULL in (left, right):
    116                    return exp.null()
    117                if always_true(left) and always_true(right):
    118                    return exp.true()
    119                if always_true(left):
    120                    return right
    121                if always_true(right):
    122                    return left
    123                return _simplify_comparison(expression, left, right)
    124            elif isinstance(expression, exp.Or):
    125                if always_true(left) or always_true(right):
    126                    return exp.true()
    127                if left == FALSE and right == FALSE:
    128                    return exp.false()
    129                if (
    130                    (left == NULL and right == NULL)
    131                    or (left == NULL and right == FALSE)
    132                    or (left == FALSE and right == NULL)
    133                ):
    134                    return exp.null()
    135                if left == FALSE:
    136                    return right
    137                if right == FALSE:
    138                    return left
    139                return _simplify_comparison(expression, left, right, or_=True)
    140        return None
    141
    142    return _flat_simplify(expression, _simplify_connectors)
    143
    144
    145LT_LTE = (exp.LT, exp.LTE)
    146GT_GTE = (exp.GT, exp.GTE)
    147
    148COMPARISONS = (
    149    *LT_LTE,
    150    *GT_GTE,
    151    exp.EQ,
    152    exp.NEQ,
    153)
    154
    155INVERSE_COMPARISONS = {
    156    exp.LT: exp.GT,
    157    exp.GT: exp.LT,
    158    exp.LTE: exp.GTE,
    159    exp.GTE: exp.LTE,
    160}
    161
    162
    163def _simplify_comparison(expression, left, right, or_=False):
    164    if isinstance(left, COMPARISONS) and isinstance(right, COMPARISONS):
    165        ll, lr = left.args.values()
    166        rl, rr = right.args.values()
    167
    168        largs = {ll, lr}
    169        rargs = {rl, rr}
    170
    171        matching = largs & rargs
    172        columns = {m for m in matching if isinstance(m, exp.Column)}
    173
    174        if matching and columns:
    175            try:
    176                l = first(largs - columns)
    177                r = first(rargs - columns)
    178            except StopIteration:
    179                return expression
    180
    181            # make sure the comparison is always of the form x > 1 instead of 1 < x
    182            if left.__class__ in INVERSE_COMPARISONS and l == ll:
    183                left = INVERSE_COMPARISONS[left.__class__](this=lr, expression=ll)
    184            if right.__class__ in INVERSE_COMPARISONS and r == rl:
    185                right = INVERSE_COMPARISONS[right.__class__](this=rr, expression=rl)
    186
    187            if l.is_number and r.is_number:
    188                l = float(l.name)
    189                r = float(r.name)
    190            elif l.is_string and r.is_string:
    191                l = l.name
    192                r = r.name
    193            else:
    194                return None
    195
    196            for (a, av), (b, bv) in itertools.permutations(((left, l), (right, r))):
    197                if isinstance(a, LT_LTE) and isinstance(b, LT_LTE):
    198                    return left if (av > bv if or_ else av <= bv) else right
    199                if isinstance(a, GT_GTE) and isinstance(b, GT_GTE):
    200                    return left if (av < bv if or_ else av >= bv) else right
    201
    202                # we can't ever shortcut to true because the column could be null
    203                if isinstance(a, exp.LT) and isinstance(b, GT_GTE):
    204                    if not or_ and av <= bv:
    205                        return exp.false()
    206                elif isinstance(a, exp.GT) and isinstance(b, LT_LTE):
    207                    if not or_ and av >= bv:
    208                        return exp.false()
    209                elif isinstance(a, exp.EQ):
    210                    if isinstance(b, exp.LT):
    211                        return exp.false() if av >= bv else a
    212                    if isinstance(b, exp.LTE):
    213                        return exp.false() if av > bv else a
    214                    if isinstance(b, exp.GT):
    215                        return exp.false() if av <= bv else a
    216                    if isinstance(b, exp.GTE):
    217                        return exp.false() if av < bv else a
    218                    if isinstance(b, exp.NEQ):
    219                        return exp.false() if av == bv else a
    220    return None
    221
    222
    223def remove_compliments(expression):
    224    """
    225    Removing compliments.
    226
    227    A AND NOT A -> FALSE
    228    A OR NOT A -> TRUE
    229    """
    230    if isinstance(expression, exp.Connector):
    231        compliment = exp.false() if isinstance(expression, exp.And) else exp.true()
    232
    233        for a, b in itertools.permutations(expression.flatten(), 2):
    234            if is_complement(a, b):
    235                return compliment
    236    return expression
    237
    238
    239def uniq_sort(expression):
    240    """
    241    Uniq and sort a connector.
    242
    243    C AND A AND B AND B -> A AND B AND C
    244    """
    245    if isinstance(expression, exp.Connector):
    246        result_func = exp.and_ if isinstance(expression, exp.And) else exp.or_
    247        flattened = tuple(expression.flatten())
    248        deduped = {GENERATOR.generate(e): e for e in flattened}
    249        arr = tuple(deduped.items())
    250
    251        # check if the operands are already sorted, if not sort them
    252        # A AND C AND B -> A AND B AND C
    253        for i, (sql, e) in enumerate(arr[1:]):
    254            if sql < arr[i][0]:
    255                expression = result_func(*(deduped[sql] for sql in sorted(deduped)))
    256                break
    257        else:
    258            # we didn't have to sort but maybe we need to dedup
    259            if len(deduped) < len(flattened):
    260                expression = result_func(*deduped.values())
    261
    262    return expression
    263
    264
    265def absorb_and_eliminate(expression):
    266    """
    267    absorption:
    268        A AND (A OR B) -> A
    269        A OR (A AND B) -> A
    270        A AND (NOT A OR B) -> A AND B
    271        A OR (NOT A AND B) -> A OR B
    272    elimination:
    273        (A AND B) OR (A AND NOT B) -> A
    274        (A OR B) AND (A OR NOT B) -> A
    275    """
    276    if isinstance(expression, exp.Connector):
    277        kind = exp.Or if isinstance(expression, exp.And) else exp.And
    278
    279        for a, b in itertools.permutations(expression.flatten(), 2):
    280            if isinstance(a, kind):
    281                aa, ab = a.unnest_operands()
    282
    283                # absorb
    284                if is_complement(b, aa):
    285                    aa.replace(exp.true() if kind == exp.And else exp.false())
    286                elif is_complement(b, ab):
    287                    ab.replace(exp.true() if kind == exp.And else exp.false())
    288                elif (set(b.flatten()) if isinstance(b, kind) else {b}) < set(a.flatten()):
    289                    a.replace(exp.false() if kind == exp.And else exp.true())
    290                elif isinstance(b, kind):
    291                    # eliminate
    292                    rhs = b.unnest_operands()
    293                    ba, bb = rhs
    294
    295                    if aa in rhs and (is_complement(ab, ba) or is_complement(ab, bb)):
    296                        a.replace(aa)
    297                        b.replace(aa)
    298                    elif ab in rhs and (is_complement(aa, ba) or is_complement(aa, bb)):
    299                        a.replace(ab)
    300                        b.replace(ab)
    301
    302    return expression
    303
    304
    305def simplify_literals(expression):
    306    if isinstance(expression, exp.Binary):
    307        return _flat_simplify(expression, _simplify_binary)
    308    elif isinstance(expression, exp.Neg):
    309        this = expression.this
    310        if this.is_number:
    311            value = this.name
    312            if value[0] == "-":
    313                return exp.Literal.number(value[1:])
    314            return exp.Literal.number(f"-{value}")
    315
    316    return expression
    317
    318
    319def _simplify_binary(expression, a, b):
    320    if isinstance(expression, exp.Is):
    321        if isinstance(b, exp.Not):
    322            c = b.this
    323            not_ = True
    324        else:
    325            c = b
    326            not_ = False
    327
    328        if c == NULL:
    329            if isinstance(a, exp.Literal):
    330                return exp.true() if not_ else exp.false()
    331            if a == NULL:
    332                return exp.false() if not_ else exp.true()
    333    elif isinstance(expression, (exp.NullSafeEQ, exp.NullSafeNEQ)):
    334        return None
    335    elif NULL in (a, b):
    336        return exp.null()
    337
    338    if a.is_number and b.is_number:
    339        a = int(a.name) if a.is_int else Decimal(a.name)
    340        b = int(b.name) if b.is_int else Decimal(b.name)
    341
    342        if isinstance(expression, exp.Add):
    343            return exp.Literal.number(a + b)
    344        if isinstance(expression, exp.Sub):
    345            return exp.Literal.number(a - b)
    346        if isinstance(expression, exp.Mul):
    347            return exp.Literal.number(a * b)
    348        if isinstance(expression, exp.Div):
    349            if isinstance(a, int) and isinstance(b, int):
    350                return exp.Literal.number(a // b)
    351            return exp.Literal.number(a / b)
    352
    353        boolean = eval_boolean(expression, a, b)
    354
    355        if boolean:
    356            return boolean
    357    elif a.is_string and b.is_string:
    358        boolean = eval_boolean(expression, a, b)
    359
    360        if boolean:
    361            return boolean
    362    elif isinstance(a, exp.Cast) and isinstance(b, exp.Interval):
    363        a, b = extract_date(a), extract_interval(b)
    364        if a and b:
    365            if isinstance(expression, exp.Add):
    366                return date_literal(a + b)
    367            if isinstance(expression, exp.Sub):
    368                return date_literal(a - b)
    369    elif isinstance(a, exp.Interval) and isinstance(b, exp.Cast):
    370        a, b = extract_interval(a), extract_date(b)
    371        # you cannot subtract a date from an interval
    372        if a and b and isinstance(expression, exp.Add):
    373            return date_literal(a + b)
    374
    375    return None
    376
    377
    378def simplify_parens(expression):
    379    if (
    380        isinstance(expression, exp.Paren)
    381        and not isinstance(expression.this, exp.Select)
    382        and (
    383            not isinstance(expression.parent, (exp.Condition, exp.Binary))
    384            or isinstance(expression.this, (exp.Is, exp.Like))
    385            or not isinstance(expression.this, exp.Binary)
    386        )
    387    ):
    388        return expression.this
    389    return expression
    390
    391
    392def remove_where_true(expression):
    393    for where in expression.find_all(exp.Where):
    394        if always_true(where.this):
    395            where.parent.set("where", None)
    396    for join in expression.find_all(exp.Join):
    397        if always_true(join.args.get("on")):
    398            join.set("kind", "CROSS")
    399            join.set("on", None)
    400
    401
    402def always_true(expression):
    403    return expression == TRUE or isinstance(expression, exp.Literal)
    404
    405
    406def is_complement(a, b):
    407    return isinstance(b, exp.Not) and b.this == a
    408
    409
    410def eval_boolean(expression, a, b):
    411    if isinstance(expression, (exp.EQ, exp.Is)):
    412        return boolean_literal(a == b)
    413    if isinstance(expression, exp.NEQ):
    414        return boolean_literal(a != b)
    415    if isinstance(expression, exp.GT):
    416        return boolean_literal(a > b)
    417    if isinstance(expression, exp.GTE):
    418        return boolean_literal(a >= b)
    419    if isinstance(expression, exp.LT):
    420        return boolean_literal(a < b)
    421    if isinstance(expression, exp.LTE):
    422        return boolean_literal(a <= b)
    423    return None
    424
    425
    426def extract_date(cast):
    427    # The "fromisoformat" conversion could fail if the cast is used on an identifier,
    428    # so in that case we can't extract the date.
    429    try:
    430        if cast.args["to"].this == exp.DataType.Type.DATE:
    431            return datetime.date.fromisoformat(cast.name)
    432        if cast.args["to"].this == exp.DataType.Type.DATETIME:
    433            return datetime.datetime.fromisoformat(cast.name)
    434    except ValueError:
    435        return None
    436
    437
    438def extract_interval(interval):
    439    try:
    440        from dateutil.relativedelta import relativedelta  # type: ignore
    441    except ModuleNotFoundError:
    442        return None
    443
    444    n = int(interval.name)
    445    unit = interval.text("unit").lower()
    446
    447    if unit == "year":
    448        return relativedelta(years=n)
    449    if unit == "month":
    450        return relativedelta(months=n)
    451    if unit == "week":
    452        return relativedelta(weeks=n)
    453    if unit == "day":
    454        return relativedelta(days=n)
    455    return None
    456
    457
    458def date_literal(date):
    459    return exp.cast(
    460        exp.Literal.string(date),
    461        "DATETIME" if isinstance(date, datetime.datetime) else "DATE",
    462    )
    463
    464
    465def boolean_literal(condition):
    466    return exp.true() if condition else exp.false()
    467
    468
    469def _flat_simplify(expression, simplifier):
    470    operands = []
    471    queue = deque(expression.flatten(unnest=False))
    472    size = len(queue)
    473
    474    while queue:
    475        a = queue.popleft()
    476
    477        for b in queue:
    478            result = simplifier(expression, a, b)
    479
    480            if result:
    481                queue.remove(b)
    482                queue.append(result)
    483                break
    484        else:
    485            operands.append(a)
    486
    487    if len(operands) < size:
    488        return functools.reduce(lambda a, b: expression.__class__(this=a, expression=b), operands)
    489    return expression

def simplify(expression): View Source

    16def simplify(expression):
    17    """
    18    Rewrite sqlglot AST to simplify expressions.
    19
    20    Example:
    21        >>> import sqlglot
    22        >>> expression = sqlglot.parse_one("TRUE AND TRUE")
    23        >>> simplify(expression).sql()
    24        'TRUE'
    25
    26    Args:
    27        expression (sqlglot.Expression): expression to simplify
    28    Returns:
    29        sqlglot.Expression: simplified expression
    30    """
    31
    32    def _simplify(expression, root=True):
    33        node = expression
    34        node = rewrite_between(node)
    35        node = uniq_sort(node)
    36        node = absorb_and_eliminate(node)
    37        exp.replace_children(node, lambda e: _simplify(e, False))
    38        node = simplify_not(node)
    39        node = flatten(node)
    40        node = simplify_connectors(node)
    41        node = remove_compliments(node)
    42        node.parent = expression.parent
    43        node = simplify_literals(node)
    44        node = simplify_parens(node)
    45        if root:
    46            expression.replace(node)
    47        return node
    48
    49    expression = while_changing(expression, _simplify)
    50    remove_where_true(expression)
    51    return expression

Rewrite sqlglot AST to simplify expressions.

###### Example:

>     >>> import sqlglot
>     >>> expression = sqlglot.parse_one("TRUE AND TRUE")
>     >>> simplify(expression).sql()
>     'TRUE'

###### Arguments:

- expression (sqlglot.Expression): expression to simplify

###### Returns:

> sqlglot.Expression: simplified expression

def rewrite_between( expression: sqlglot.expressions.Expression) -> sqlglot.expressions.Expression: View Source

    54def rewrite_between(expression: exp.Expression) -> exp.Expression:
    55    """Rewrite x between y and z to x >= y AND x <= z.
    56
    57    This is done because comparison simplification is only done on lt/lte/gt/gte.
    58    """
    59    if isinstance(expression, exp.Between):
    60        return exp.and_(
    61            exp.GTE(this=expression.this.copy(), expression=expression.args["low"]),
    62            exp.LTE(this=expression.this.copy(), expression=expression.args["high"]),
    63        )
    64    return expression

Rewrite x between y and z to x >= y AND x <= z.

This is done because comparison simplification is only done on lt/lte/gt/gte.

def simplify_not(expression): View Source

    67def simplify_not(expression):
    68    """
    69    Demorgan's Law
    70    NOT (x OR y) -> NOT x AND NOT y
    71    NOT (x AND y) -> NOT x OR NOT y
    72    """
    73    if isinstance(expression, exp.Not):
    74        if isinstance(expression.this, exp.Null):
    75            return exp.null()
    76        if isinstance(expression.this, exp.Paren):
    77            condition = expression.this.unnest()
    78            if isinstance(condition, exp.And):
    79                return exp.or_(exp.not_(condition.left), exp.not_(condition.right))
    80            if isinstance(condition, exp.Or):
    81                return exp.and_(exp.not_(condition.left), exp.not_(condition.right))
    82            if isinstance(condition, exp.Null):
    83                return exp.null()
    84        if always_true(expression.this):
    85            return exp.false()
    86        if expression.this == FALSE:
    87            return exp.true()
    88        if isinstance(expression.this, exp.Not):
    89            # double negation
    90            # NOT NOT x -> x
    91            return expression.this.this
    92    return expression

Demorgan's Law NOT (x OR y) -> NOT x AND NOT y NOT (x AND y) -> NOT x OR NOT y

def flatten(expression): View Source

     95def flatten(expression):
     96    """
     97    A AND (B AND C) -> A AND B AND C
     98    A OR (B OR C) -> A OR B OR C
     99    """
    100    if isinstance(expression, exp.Connector):
    101        for node in expression.args.values():
    102            child = node.unnest()
    103            if isinstance(child, expression.__class__):
    104                node.replace(child)
    105    return expression

A AND (B AND C) -> A AND B AND C A OR (B OR C) -> A OR B OR C

def simplify_connectors(expression): View Source

    108def simplify_connectors(expression):
    109    def _simplify_connectors(expression, left, right):
    110        if isinstance(expression, exp.Connector):
    111            if left == right:
    112                return left
    113            if isinstance(expression, exp.And):
    114                if FALSE in (left, right):
    115                    return exp.false()
    116                if NULL in (left, right):
    117                    return exp.null()
    118                if always_true(left) and always_true(right):
    119                    return exp.true()
    120                if always_true(left):
    121                    return right
    122                if always_true(right):
    123                    return left
    124                return _simplify_comparison(expression, left, right)
    125            elif isinstance(expression, exp.Or):
    126                if always_true(left) or always_true(right):
    127                    return exp.true()
    128                if left == FALSE and right == FALSE:
    129                    return exp.false()
    130                if (
    131                    (left == NULL and right == NULL)
    132                    or (left == NULL and right == FALSE)
    133                    or (left == FALSE and right == NULL)
    134                ):
    135                    return exp.null()
    136                if left == FALSE:
    137                    return right
    138                if right == FALSE:
    139                    return left
    140                return _simplify_comparison(expression, left, right, or_=True)
    141        return None
    142
    143    return _flat_simplify(expression, _simplify_connectors)

def remove_compliments(expression): View Source

    224def remove_compliments(expression):
    225    """
    226    Removing compliments.
    227
    228    A AND NOT A -> FALSE
    229    A OR NOT A -> TRUE
    230    """
    231    if isinstance(expression, exp.Connector):
    232        compliment = exp.false() if isinstance(expression, exp.And) else exp.true()
    233
    234        for a, b in itertools.permutations(expression.flatten(), 2):
    235            if is_complement(a, b):
    236                return compliment
    237    return expression

Removing compliments.

A AND NOT A -> FALSE A OR NOT A -> TRUE

def uniq_sort(expression): View Source

    240def uniq_sort(expression):
    241    """
    242    Uniq and sort a connector.
    243
    244    C AND A AND B AND B -> A AND B AND C
    245    """
    246    if isinstance(expression, exp.Connector):
    247        result_func = exp.and_ if isinstance(expression, exp.And) else exp.or_
    248        flattened = tuple(expression.flatten())
    249        deduped = {GENERATOR.generate(e): e for e in flattened}
    250        arr = tuple(deduped.items())
    251
    252        # check if the operands are already sorted, if not sort them
    253        # A AND C AND B -> A AND B AND C
    254        for i, (sql, e) in enumerate(arr[1:]):
    255            if sql < arr[i][0]:
    256                expression = result_func(*(deduped[sql] for sql in sorted(deduped)))
    257                break
    258        else:
    259            # we didn't have to sort but maybe we need to dedup
    260            if len(deduped) < len(flattened):
    261                expression = result_func(*deduped.values())
    262
    263    return expression

Uniq and sort a connector.

C AND A AND B AND B -> A AND B AND C

def absorb_and_eliminate(expression): View Source

    266def absorb_and_eliminate(expression):
    267    """
    268    absorption:
    269        A AND (A OR B) -> A
    270        A OR (A AND B) -> A
    271        A AND (NOT A OR B) -> A AND B
    272        A OR (NOT A AND B) -> A OR B
    273    elimination:
    274        (A AND B) OR (A AND NOT B) -> A
    275        (A OR B) AND (A OR NOT B) -> A
    276    """
    277    if isinstance(expression, exp.Connector):
    278        kind = exp.Or if isinstance(expression, exp.And) else exp.And
    279
    280        for a, b in itertools.permutations(expression.flatten(), 2):
    281            if isinstance(a, kind):
    282                aa, ab = a.unnest_operands()
    283
    284                # absorb
    285                if is_complement(b, aa):
    286                    aa.replace(exp.true() if kind == exp.And else exp.false())
    287                elif is_complement(b, ab):
    288                    ab.replace(exp.true() if kind == exp.And else exp.false())
    289                elif (set(b.flatten()) if isinstance(b, kind) else {b}) < set(a.flatten()):
    290                    a.replace(exp.false() if kind == exp.And else exp.true())
    291                elif isinstance(b, kind):
    292                    # eliminate
    293                    rhs = b.unnest_operands()
    294                    ba, bb = rhs
    295
    296                    if aa in rhs and (is_complement(ab, ba) or is_complement(ab, bb)):
    297                        a.replace(aa)
    298                        b.replace(aa)
    299                    elif ab in rhs and (is_complement(aa, ba) or is_complement(aa, bb)):
    300                        a.replace(ab)
    301                        b.replace(ab)
    302
    303    return expression

absorption: A AND (A OR B) -> A A OR (A AND B) -> A A AND (NOT A OR B) -> A AND B A OR (NOT A AND B) -> A OR B elimination: (A AND B) OR (A AND NOT B) -> A (A OR B) AND (A OR NOT B) -> A

def simplify_literals(expression): View Source

    306def simplify_literals(expression):
    307    if isinstance(expression, exp.Binary):
    308        return _flat_simplify(expression, _simplify_binary)
    309    elif isinstance(expression, exp.Neg):
    310        this = expression.this
    311        if this.is_number:
    312            value = this.name
    313            if value[0] == "-":
    314                return exp.Literal.number(value[1:])
    315            return exp.Literal.number(f"-{value}")
    316
    317    return expression

def simplify_parens(expression): View Source

    379def simplify_parens(expression):
    380    if (
    381        isinstance(expression, exp.Paren)
    382        and not isinstance(expression.this, exp.Select)
    383        and (
    384            not isinstance(expression.parent, (exp.Condition, exp.Binary))
    385            or isinstance(expression.this, (exp.Is, exp.Like))
    386            or not isinstance(expression.this, exp.Binary)
    387        )
    388    ):
    389        return expression.this
    390    return expression

def remove_where_true(expression): View Source

    393def remove_where_true(expression):
    394    for where in expression.find_all(exp.Where):
    395        if always_true(where.this):
    396            where.parent.set("where", None)
    397    for join in expression.find_all(exp.Join):
    398        if always_true(join.args.get("on")):
    399            join.set("kind", "CROSS")
    400            join.set("on", None)

def always_true(expression): View Source

    403def always_true(expression):
    404    return expression == TRUE or isinstance(expression, exp.Literal)

def is_complement(a, b): View Source

    407def is_complement(a, b):
    408    return isinstance(b, exp.Not) and b.this == a

def eval_boolean(expression, a, b): View Source

    411def eval_boolean(expression, a, b):
    412    if isinstance(expression, (exp.EQ, exp.Is)):
    413        return boolean_literal(a == b)
    414    if isinstance(expression, exp.NEQ):
    415        return boolean_literal(a != b)
    416    if isinstance(expression, exp.GT):
    417        return boolean_literal(a > b)
    418    if isinstance(expression, exp.GTE):
    419        return boolean_literal(a >= b)
    420    if isinstance(expression, exp.LT):
    421        return boolean_literal(a < b)
    422    if isinstance(expression, exp.LTE):
    423        return boolean_literal(a <= b)
    424    return None

def extract_date(cast): View Source

    427def extract_date(cast):
    428    # The "fromisoformat" conversion could fail if the cast is used on an identifier,
    429    # so in that case we can't extract the date.
    430    try:
    431        if cast.args["to"].this == exp.DataType.Type.DATE:
    432            return datetime.date.fromisoformat(cast.name)
    433        if cast.args["to"].this == exp.DataType.Type.DATETIME:
    434            return datetime.datetime.fromisoformat(cast.name)
    435    except ValueError:
    436        return None

def extract_interval(interval): View Source

    439def extract_interval(interval):
    440    try:
    441        from dateutil.relativedelta import relativedelta  # type: ignore
    442    except ModuleNotFoundError:
    443        return None
    444
    445    n = int(interval.name)
    446    unit = interval.text("unit").lower()
    447
    448    if unit == "year":
    449        return relativedelta(years=n)
    450    if unit == "month":
    451        return relativedelta(months=n)
    452    if unit == "week":
    453        return relativedelta(weeks=n)
    454    if unit == "day":
    455        return relativedelta(days=n)
    456    return None

def date_literal(date): View Source

    459def date_literal(date):
    460    return exp.cast(
    461        exp.Literal.string(date),
    462        "DATETIME" if isinstance(date, datetime.datetime) else "DATE",
    463    )

def boolean_literal(condition): View Source

    466def boolean_literal(condition):
    467    return exp.true() if condition else exp.false()