<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def to_expression(self, lhs, rhs, op):
 """Builds a binary sql expression. At its most basic, returns 'lhs op rhs' such as '5 + 3'. However, it also specially handles the 'in' and 'between' operators. For each of these operators it is expected that rhs will be iterable. If the comparison operator is of the form 'not(op)' where op is the operator, it will result in not (lhs op rhs). This allows for doing the full range of null checks on composite types. For composite types, 'is null' only returns true when all fields are null, and 'is not null' returns true only when all fields are not null. So, for a composite type with some null fields, 'is null' and 'is not null' will both return false, making it difficult to get all rows that have composite columns with some value in them. The solution to this is to use not (composite is null) which is true when all composite fields, or only some composite fields are not null. """

    if op == "raw":
      # TODO: This is not documented
      return lhs
    elif op == "between":
      return "{0} between {1} and {2}".format(lhs, *rhs)
    elif op == "in":
      return "{0} in {1}".format(lhs, self.to_tuple(rhs))
    elif op.startswith("not(") and op.endswith(")"):
      return "not ({0} {1} {2})".format(lhs, op[4:-1].strip(), rhs)
    else:
      return "{0} {1} {2}".format(lhs, op.strip(), rhs)