// The runtime value model of the executor: the Python objects that the code generated by
// sqlglot's Python executor manipulates (see sqlglot/executor/env.py).
///|
/// A Python exception raised while evaluating generated code, e.g.
/// `PyException("ZeroDivisionError", "division by zero")`.
pub(all) suberror PyException {
PyException(String, String)
}
///|
pub fn PyException::kind(self : PyException) -> String {
match self {
PyException(k, _) => k
}
}
///|
/// Python `str(exception)`.
pub fn PyException::message(self : PyException) -> String {
match self {
PyException(_, m) => m
}
}
///|
fn type_error(msg : String) -> PyException {
PyException("TypeError", msg)
}
///|
fn value_error(msg : String) -> PyException {
PyException("ValueError", msg)
}
///|
/// A Python callable (an ENV function, a lambda or a builtin).
pub(all) struct PyFunc {
name : String
call : (Array[Value]) -> Value raise
}
///|
/// A runtime value (a Python object).
pub(all) enum Value {
/// `None` (SQL NULL)
Null
Bool(Bool)
Int(Int64)
Float(Double)
Str(String)
List(Array[Value])
Tuple(Array[Value])
/// An insertion-ordered `dict`.
Dict(Array[(Value, Value)])
/// A `set`, in CPython's iteration order (see pyset.mbt).
Set(Array[Value])
Date(PyDate)
Time(PyTime)
DateTime(PyDateTime)
TimeDelta(PyTimeDelta)
/// A generator / iterator (materialized).
Iter(Array[Value])
Func(PyFunc)
/// A module or namespace object: `exp` or `exp.DType`.
Module(String)
/// A member of `exp.DType`.
DTypeV(@core.DType)
/// The `scope` mapping of table names to readers.
Readers(Map[String?, Reader])
/// A table reader (`RowReader` or `RangeReader`).
ReaderV(Reader)
/// `env.reverse_key`
RevKey(Value)
}
///|
/// A reader of the current row(s) of a table.
pub enum Reader {
Row(RowReader)
Range(RangeReader)
}
///|
/// Python `type(value).__name__` (as used in error messages).
pub fn Value::type_name(self : Value) -> String {
match self {
Null => "NoneType"
Bool(_) => "bool"
Int(_) => "int"
Float(_) => "float"
Str(_) => "str"
List(_) => "list"
Tuple(_) => "tuple"
Dict(_) => "dict"
Set(_) => "set"
Date(_) => "datetime.date"
Time(_) => "datetime.time"
DateTime(_) => "datetime.datetime"
TimeDelta(_) => "datetime.timedelta"
Iter(_) => "generator"
Func(_) => "function"
Module(_) => "module"
DTypeV(_) => "DType"
Readers(_) => "dict"
ReaderV(Row(_)) => "RowReader"
ReaderV(Range(_)) => "RangeReader"
RevKey(_) => "reverse_key"
}
}
///|
/// Python truthiness (`bool(value)`).
pub fn Value::truthy(self : Value) -> Bool {
match self {
Null => false
Bool(b) => b
Int(i) => i != 0L
Float(d) => d != 0.0
Str(s) => !s.is_empty()
List(l) | Tuple(l) | Set(l) => !l.is_empty()
Dict(d) => !d.is_empty()
TimeDelta(td) => !td.is_zero()
Readers(m) => !m.is_empty()
_ => true
}
}
///|
pub fn Value::is_null(self : Value) -> Bool {
self is Null
}
///|
/// Python `repr(value)`.
pub fn Value::repr(self : Value) -> String {
match self {
Null => "None"
Bool(b) => if b { "True" } else { "False" }
Int(i) => i.to_string()
Float(d) => float_repr(d)
Str(s) => @core.py_repr_str(s)
List(l) => "[" + l.map(x => x.repr()).join(", ") + "]"
Iter(l) => " x.repr()).join(", ") + "]>"
Tuple(l) =>
if l.length() == 1 {
"(" + l[0].repr() + ",)"
} else {
"(" + l.map(x => x.repr()).join(", ") + ")"
}
Set(l) =>
if l.is_empty() {
"set()"
} else {
"{" + l.map(x => x.repr()).join(", ") + "}"
}
Dict(d) =>
"{" + d.map(kv => kv.0.repr() + ": " + kv.1.repr()).join(", ") + "}"
Date(d) => d.repr()
Time(t) => t.repr()
DateTime(dt) => dt.repr()
TimeDelta(td) => td.repr()
Func(f) => ""
Module(m) => ""
DTypeV(t) => ""
Readers(_) => ""
ReaderV(_) => ""
RevKey(v) => ""
}
}
///|
/// Python `str(value)`.
pub fn Value::py_str(self : Value) -> String {
match self {
Str(s) => s
Date(d) => d.isoformat()
Time(t) => t.isoformat()
DateTime(dt) => dt.isoformat(sep=" ")
TimeDelta(td) => td.py_str()
DTypeV(t) => "DType.\{t.name()}"
_ => self.repr()
}
}
///|
pub impl Show for Value with fn output(self, logger) {
logger.write_string(self.repr())
}
///|
/// Python `==` (never raises).
pub fn py_eq(a : Value, b : Value) -> Bool {
match (a, b) {
(Null, Null) => true
(Null, _) | (_, Null) => false
(Str(x), Str(y)) => x == y
(Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
num_cmp_eq(a, b)
(List(x), List(y)) | (Tuple(x), Tuple(y)) => seq_eq(x, y)
(Set(x), Set(y)) =>
x.length() == y.length() &&
x.iter().all(v => y.iter().any(w => py_eq(v, w)))
(Dict(x), Dict(y)) =>
x.length() == y.length() &&
x
.iter()
.all(kv => y.iter().any(kw => py_eq(kv.0, kw.0) && py_eq(kv.1, kw.1)))
(Date(x), Date(y)) => x == y
(Time(x), Time(y)) => x == y
(DateTime(x), DateTime(y)) => x.eq(y)
(TimeDelta(x), TimeDelta(y)) => x == y
(DTypeV(x), DTypeV(y)) => x == y
(RevKey(x), RevKey(y)) => py_eq(y, x)
(Module(x), Module(y)) => x == y
(Func(x), Func(y)) => physical_equal(x, y)
(Iter(x), Iter(y)) => physical_equal(x, y)
_ => false
}
}
///|
fn seq_eq(x : Array[Value], y : Array[Value]) -> Bool {
if x.length() != y.length() {
return false
}
for i in 0.. (Bool, Int64, Double) {
match v {
Bool(b) => (false, if b { 1L } else { 0L }, if b { 1.0 } else { 0.0 })
Int(i) => (false, i, i.to_double())
Float(d) => (true, 0L, d)
_ => (false, 0L, 0.0)
}
}
///|
/// Exact comparison of an integer with a double (Python semantics): -1, 0, 1, or
/// `None` when the double is NaN.
fn cmp_int_double(i : Int64, d : Double) -> Int? {
if d.is_nan() {
return None
}
if d.is_pos_inf() {
return Some(-1)
}
if d.is_neg_inf() {
return Some(1)
}
let f = d.floor()
// |i| < 2^63 so a double >= 2^63 is larger
if f >= 9223372036854775808.0 {
return Some(-1)
}
if f < -9223372036854775808.0 {
return Some(1)
}
let fi = f.to_int64()
if i < fi {
Some(-1)
} else if i > fi {
Some(1)
} else if d > f {
Some(-1)
} else {
Some(0)
}
}
///|
/// Python numeric comparison: -1, 0, 1 or `None` (NaN involved).
fn num_cmp(a : Value, b : Value) -> Int? {
let (af, ai, ad) = num_parts(a)
let (bf, bi, bd) = num_parts(b)
match (af, bf) {
(false, false) => Some(ai.compare(bi))
(true, true) =>
if ad.is_nan() || bd.is_nan() {
None
} else if ad < bd {
Some(-1)
} else if ad > bd {
Some(1)
} else {
Some(0)
}
(false, true) => cmp_int_double(ai, bd)
(true, false) => cmp_int_double(bi, ad).map(c => -c)
}
}
///|
fn num_cmp_eq(a : Value, b : Value) -> Bool {
num_cmp(a, b) is Some(0)
}
///|
fn unorderable(op : String, a : Value, b : Value) -> PyException {
type_error(
"'\{op}' not supported between instances of '\{a.type_name()}' and '\{b.type_name()}'",
)
}
///|
/// Python `a < b`.
pub fn py_lt(a : Value, b : Value) -> Bool raise PyException {
match (a, b) {
(Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
num_cmp(a, b) is Some(-1)
(Str(x), Str(y)) => str_cmp(x, y) < 0
(List(x), List(y)) | (Tuple(x), Tuple(y)) => seq_lt(x, y)
(Date(x), Date(y)) => x.compare(y) < 0
(Time(x), Time(y)) => x.compare(y) < 0
(DateTime(x), DateTime(y)) => x.compare(y) < 0
(TimeDelta(x), TimeDelta(y)) => x.compare(y) < 0
(RevKey(x), RevKey(y)) => py_lt(y, x)
_ => raise unorderable("<", a, b)
}
}
///|
/// Python `a > b`.
pub fn py_gt(a : Value, b : Value) -> Bool raise PyException {
match (a, b) {
(RevKey(_), RevKey(_)) => py_lt(b, a)
(Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
num_cmp(a, b) is Some(1)
_ =>
py_lt(b, a) catch {
PyException("TypeError", _) => raise unorderable(">", a, b)
e => raise e
}
}
}
///|
/// Python `a <= b`.
pub fn py_le(a : Value, b : Value) -> Bool raise PyException {
match (a, b) {
(Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
num_cmp(a, b) is Some(-1 | 0)
(List(x), List(y)) | (Tuple(x), Tuple(y)) => seq_le(x, y)
_ =>
(py_eq(a, b) && comparable_types(a, b)) ||
(py_lt(a, b) catch {
PyException("TypeError", _) => raise unorderable("<=", a, b)
e => raise e
})
}
}
///|
/// Python `a >= b`.
pub fn py_ge(a : Value, b : Value) -> Bool raise PyException {
match (a, b) {
(Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
num_cmp(a, b) is Some(1 | 0)
_ =>
py_le(b, a) catch {
PyException("TypeError", _) => raise unorderable(">=", a, b)
e => raise e
}
}
}
///|
fn comparable_types(a : Value, b : Value) -> Bool {
match (a, b) {
(Str(_), Str(_))
| (Date(_), Date(_))
| (Time(_), Time(_))
| (DateTime(_), DateTime(_))
| (TimeDelta(_), TimeDelta(_)) => true
_ => false
}
}
///|
/// Lexicographic comparison of two sequences with Python's rich comparison rules.
fn seq_lt(x : Array[Value], y : Array[Value]) -> Bool raise PyException {
let n = if x.length() < y.length() { x.length() } else { y.length() }
for i in 0.. Bool raise PyException {
let n = if x.length() < y.length() { x.length() } else { y.length() }
for i in 0.. Int {
let ai = a.iter()
let bi = b.iter()
for ;; {
match (ai.next(), bi.next()) {
(None, None) => return 0
(None, Some(_)) => return -1
(Some(_), None) => return 1
(Some(x), Some(y)) => if x != y { return if x < y { -1 } else { 1 } }
}
}
}
///|
/// Python `iter(value)`, materialized.
pub fn py_iter(v : Value) -> Array[Value] raise PyException {
match v {
List(l) | Tuple(l) | Set(l) | Iter(l) => l
Dict(d) => d.map(kv => kv.0)
Str(s) => s.iter().map(c => Str(c.to_string())).collect()
_ => raise type_error("'\{v.type_name()}' object is not iterable")
}
}
///|
/// Python `len(value)`.
pub fn py_len(v : Value) -> Int raise PyException {
match v {
Str(s) => s.char_length()
List(l) | Tuple(l) | Set(l) => l.length()
Dict(d) => d.length()
Readers(m) => m.length()
_ => raise type_error("object of type '\{v.type_name()}' has no len()")
}
}
///|
/// Python `x[key]` for dicts, lists, tuples and strings.
pub fn py_getitem(container : Value, key : Value) -> Value raise PyException {
match container {
Dict(d) => {
for kv in d {
if py_eq(kv.0, key) {
return kv.1
}
}
raise PyException("KeyError", key.repr())
}
Readers(m) => {
let k : String? = match key {
Null => None
Str(s) => Some(s)
_ => raise PyException("KeyError", key.repr())
}
match m.get(k) {
Some(r) => ReaderV(r)
None => raise PyException("KeyError", key.repr())
}
}
ReaderV(r) =>
match key {
Str(column) => r.get(column)
_ => raise PyException("KeyError", key.repr())
}
List(l) | Tuple(l) => {
let i = index_of(key, container)
let n = l.length()
let j = if i < 0 { i + n } else { i }
if j < 0 || j >= n {
raise PyException(
"IndexError",
"\{container.type_name()} index out of range",
)
}
l[j]
}
Str(s) => {
let chars = s.to_array()
let i = index_of(key, container)
let n = chars.length()
let j = if i < 0 { i + n } else { i }
if j < 0 || j >= n {
raise PyException("IndexError", "string index out of range")
}
Str(chars[j].to_string())
}
_ =>
raise type_error("'\{container.type_name()}' object is not subscriptable")
}
}
///|
fn index_of(key : Value, container : Value) -> Int raise PyException {
match key {
Int(i) => i.to_int()
Bool(b) => if b { 1 } else { 0 }
_ =>
raise type_error(
"\{container.type_name()} indices must be integers or slices, not \{key.type_name()}",
)
}
}
///|
/// Python slicing `x[start:stop:step]` of a string, list or tuple.
pub fn py_slice(
v : Value,
start : Value,
stop : Value,
step? : Int = 1,
) -> Value raise PyException {
let items : Array[Value] = match v {
Str(s) => s.iter().map(c => Str(c.to_string())).collect()
List(l) | Tuple(l) => l
_ => raise type_error("'\{v.type_name()}' object is not subscriptable")
}
let n = items.length()
fn bound(x : Value, default : Int) -> Int raise PyException {
match x {
Null => default
Int(_) | Bool(_) => {
let i = index_of(x, v)
if step > 0 {
if i < 0 {
@cmp.maximum(i + n, 0)
} else {
@cmp.minimum(i, n)
}
} else if i < 0 {
@cmp.maximum(i + n, -1)
} else {
@cmp.minimum(i, n - 1)
}
}
_ =>
raise type_error(
"slice indices must be integers or None or have an __index__ method",
)
}
}
let out = []
if step > 0 {
let s = bound(start, 0)
let e = bound(stop, n)
for i = s; i < e; i = i + step {
out.push(items[i])
}
} else {
let s = bound(start, n - 1)
let e = bound(stop, -1)
for i = s; i > e; i = i + step {
out.push(items[i])
}
}
match v {
Str(_) => Str(out.map(x => x.py_str()).join(""))
Tuple(_) => Tuple(out)
_ => List(out)
}
}
///|
/// Python `==`.
pub impl Eq for Value with fn equal(self, other) {
py_eq(self, other)
}