diff --git a/crates/sqllib/src/aggregates.rs b/crates/sqllib/src/aggregates.rs index 7deaa999262..1e1a04dd7c1 100644 --- a/crates/sqllib/src/aggregates.rs +++ b/crates/sqllib/src/aggregates.rs @@ -114,7 +114,6 @@ impl UnsignedWrappers { } // Conversion from U to O - // functions is the same. #[doc(hidden)] pub fn to_signed(value: U, ascending: bool, _nullsLast: bool) -> O where diff --git a/crates/sqllib/src/boolean.rs b/crates/sqllib/src/boolean.rs new file mode 100644 index 00000000000..7b5e7ff8b59 --- /dev/null +++ b/crates/sqllib/src/boolean.rs @@ -0,0 +1,188 @@ +//! Boolean operations + +use crate::some_function1; + +#[doc(hidden)] +#[inline(always)] +pub fn wrap_bool(b: Option) -> bool { + b.unwrap_or_default() +} + +#[doc(hidden)] +#[inline(always)] +pub fn or_b_b(left: bool, right: F) -> bool +where + F: Fn() -> bool, +{ + left || right() +} + +#[doc(hidden)] +#[inline(always)] +pub fn or_bN_b(left: Option, right: F) -> Option +where + F: Fn() -> bool, +{ + match left { + Some(l) => Some(l || right()), + None => match right() { + true => Some(true), + _ => None, + }, + } +} + +#[doc(hidden)] +#[inline(always)] +pub fn or_b_bN(left: bool, right: F) -> Option +where + F: Fn() -> Option, +{ + match left { + false => right(), + true => Some(true), + } +} + +#[doc(hidden)] +#[inline(always)] +pub fn or_bN_bN(left: Option, right: F) -> Option +where + F: Fn() -> Option, +{ + match left { + None => match right() { + Some(true) => Some(true), + _ => None, + }, + Some(false) => right(), + Some(true) => Some(true), + } +} + +// OR and AND are special, they can't be generated by rules + +#[doc(hidden)] +#[inline(always)] +pub fn and_b_b(left: bool, right: F) -> bool +where + F: Fn() -> bool, +{ + left && right() +} + +#[doc(hidden)] +#[inline(always)] +pub fn and_bN_b(left: Option, right: F) -> Option +where + F: Fn() -> bool, +{ + match left { + Some(false) => Some(false), + Some(true) => Some(right()), + None => match right() { + false => Some(false), + _ => None, + }, + } +} + +#[doc(hidden)] +#[inline(always)] +pub fn and_b_bN(left: bool, right: F) -> Option +where + F: Fn() -> Option, +{ + match left { + false => Some(false), + true => right(), + } +} + +#[doc(hidden)] +#[inline(always)] +pub fn and_bN_bN(left: Option, right: F) -> Option +where + F: Fn() -> Option, +{ + match left { + Some(false) => Some(false), + Some(true) => right(), + None => match right() { + Some(false) => Some(false), + _ => None, + }, + } +} + +#[doc(hidden)] +#[inline(always)] +pub const fn is_true_b_(left: bool) -> bool { + left +} + +#[doc(hidden)] +#[inline(always)] +pub const fn is_true_bN_(left: Option) -> bool { + matches!(left, Some(true)) +} + +#[doc(hidden)] +#[inline(always)] +pub fn is_false_b_(left: bool) -> bool { + !left +} + +#[doc(hidden)] +#[inline(always)] +pub const fn is_false_bN_(left: Option) -> bool { + matches!(left, Some(false)) +} + +#[doc(hidden)] +#[inline(always)] +pub const fn is_not_true_b_(left: bool) -> bool { + !left +} + +#[doc(hidden)] +#[inline(always)] +pub const fn is_not_true_bN_(left: Option) -> bool { + match left { + Some(true) => false, + Some(false) => true, + _ => true, + } +} + +#[doc(hidden)] +#[inline(always)] +pub const fn is_not_false_b_(left: bool) -> bool { + left +} + +#[doc(hidden)] +#[inline(always)] +pub const fn is_not_false_bN_(left: Option) -> bool { + match left { + Some(true) => true, + Some(false) => false, + _ => true, + } +} + +#[doc(hidden)] +#[inline(always)] +pub const fn bool_to_i8_(value: bool) -> i8 { + if value { 1 } else { 0 } +} + +some_function1!(bool_to_i8, bool, i8); + +#[doc(hidden)] +#[inline(always)] +pub const fn i8_to_bool_(value: i8) -> bool { + value != 0 +} + +some_function1!(i8_to_bool, i8, bool); diff --git a/crates/sqllib/src/error.rs b/crates/sqllib/src/error.rs index aba761c8185..b0f7b8718ed 100644 --- a/crates/sqllib/src/error.rs +++ b/crates/sqllib/src/error.rs @@ -41,3 +41,18 @@ where Err(e) => Err(SqlRuntimeError::from_string(e.to_string())), } } + +#[doc(hidden)] +// If the data is Ok(None), convert it to Err, otherwise leave it unchanged +pub fn unwrap_sql_result(data: SqlResult>) -> SqlResult { + match data { + Err(e) => Err(e), + Ok(None) => Err(SqlRuntimeError::from_strng("NULL result produced")), + Ok(Some(data)) => Ok(data), + } +} + +#[doc(hidden)] +pub fn wrap_sql_result(data: T) -> SqlResult { + Ok(data) +} diff --git a/crates/sqllib/src/interval.rs b/crates/sqllib/src/interval.rs index 31fffbad347..abc352e60c2 100644 --- a/crates/sqllib/src/interval.rs +++ b/crates/sqllib/src/interval.rs @@ -4,8 +4,8 @@ use crate::{ Date, SqlDecimal, finite_or_null, operators::{eq, gt, gte, lt, lte, neq}, - plus_Date_Date_LongInterval__, sign, some_existing_operator, some_function2, some_operator, - some_polymorphic_function1, some_polymorphic_function2, + plus_Date_Date_LongInterval__, sign, some_existing_operator, some_function1, some_function2, + some_operator, some_polymorphic_function1, some_polymorphic_function2, timestamp::{extract_epoch_Date, extract_quarter_Date}, }; use dbsp::{algebra::F64, num_entries_scalar}; @@ -1121,18 +1121,36 @@ pub fn extract_hour_LongInterval(_value: LongInterval) -> i64 { 0 } +#[doc(hidden)] +pub fn long_interval_to_integer_(value: LongInterval) -> i32 { + value.months +} + +some_function1!(long_interval_to_integer, LongInterval, i32); + +#[doc(hidden)] +pub fn integer_to_long_interval_(value: i32) -> LongInterval { + LongInterval::from_months(value) +} + +some_function1!(integer_to_long_interval, i32, LongInterval); + /////////// #[doc(hidden)] -pub fn short_interval_to_integer(value: ShortInterval) -> i64 { +pub fn short_interval_to_integer_(value: ShortInterval) -> i64 { value.microseconds } +some_function1!(short_interval_to_integer, ShortInterval, i64); + #[doc(hidden)] -pub fn integer_to_short_interval(value: i64) -> ShortInterval { +pub fn integer_to_short_interval_(value: i64) -> ShortInterval { ShortInterval::from_microseconds(value) } +some_function1!(integer_to_short_interval, i64, ShortInterval); + #[doc(hidden)] pub fn extract_day_ShortInterval(value: ShortInterval) -> i64 { value.microseconds() / 86_400_000_000_i64 diff --git a/crates/sqllib/src/lib.rs b/crates/sqllib/src/lib.rs index 31ca87e5215..211e6f07267 100644 --- a/crates/sqllib/src/lib.rs +++ b/crates/sqllib/src/lib.rs @@ -9,6 +9,9 @@ pub use array::*; pub mod binary; pub use binary::*; #[doc(hidden)] +pub mod boolean; +pub use boolean::*; +#[doc(hidden)] pub mod casts; pub use casts::*; #[doc(hidden)] @@ -691,119 +694,6 @@ macro_rules! some_operator { pub(crate) use some_operator; -#[doc(hidden)] -#[inline(always)] -pub fn wrap_bool(b: Option) -> bool { - b.unwrap_or_default() -} - -#[doc(hidden)] -#[inline(always)] -pub fn or_b_b(left: bool, right: F) -> bool -where - F: Fn() -> bool, -{ - left || right() -} - -#[doc(hidden)] -#[inline(always)] -pub fn or_bN_b(left: Option, right: F) -> Option -where - F: Fn() -> bool, -{ - match left { - Some(l) => Some(l || right()), - None => match right() { - true => Some(true), - _ => None, - }, - } -} - -#[doc(hidden)] -#[inline(always)] -pub fn or_b_bN(left: bool, right: F) -> Option -where - F: Fn() -> Option, -{ - match left { - false => right(), - true => Some(true), - } -} - -#[doc(hidden)] -#[inline(always)] -pub fn or_bN_bN(left: Option, right: F) -> Option -where - F: Fn() -> Option, -{ - match left { - None => match right() { - Some(true) => Some(true), - _ => None, - }, - Some(false) => right(), - Some(true) => Some(true), - } -} - -// OR and AND are special, they can't be generated by rules - -#[doc(hidden)] -#[inline(always)] -pub fn and_b_b(left: bool, right: F) -> bool -where - F: Fn() -> bool, -{ - left && right() -} - -#[doc(hidden)] -#[inline(always)] -pub fn and_bN_b(left: Option, right: F) -> Option -where - F: Fn() -> bool, -{ - match left { - Some(false) => Some(false), - Some(true) => Some(right()), - None => match right() { - false => Some(false), - _ => None, - }, - } -} - -#[doc(hidden)] -#[inline(always)] -pub fn and_b_bN(left: bool, right: F) -> Option -where - F: Fn() -> Option, -{ - match left { - false => Some(false), - true => right(), - } -} - -#[doc(hidden)] -#[inline(always)] -pub fn and_bN_bN(left: Option, right: F) -> Option -where - F: Fn() -> Option, -{ - match left { - Some(false) => Some(false), - Some(true) => right(), - None => match right() { - Some(false) => Some(false), - _ => None, - }, - } -} - #[doc(hidden)] #[inline(always)] pub fn is_null(value: Option) -> bool { @@ -980,62 +870,6 @@ some_polymorphic_function1!(sign, u32, u32, u32); some_polymorphic_function1!(sign, u64, u64, u64); some_polymorphic_function1!(sign, u128, u128, u128); -#[doc(hidden)] -#[inline(always)] -pub const fn is_true_b_(left: bool) -> bool { - left -} - -#[doc(hidden)] -#[inline(always)] -pub const fn is_true_bN_(left: Option) -> bool { - matches!(left, Some(true)) -} - -#[doc(hidden)] -#[inline(always)] -pub fn is_false_b_(left: bool) -> bool { - !left -} - -#[doc(hidden)] -#[inline(always)] -pub const fn is_false_bN_(left: Option) -> bool { - matches!(left, Some(false)) -} - -#[doc(hidden)] -#[inline(always)] -pub const fn is_not_true_b_(left: bool) -> bool { - !left -} - -#[doc(hidden)] -#[inline(always)] -pub const fn is_not_true_bN_(left: Option) -> bool { - match left { - Some(true) => false, - Some(false) => true, - _ => true, - } -} - -#[doc(hidden)] -#[inline(always)] -pub const fn is_not_false_b_(left: bool) -> bool { - left -} - -#[doc(hidden)] -#[inline(always)] -pub const fn is_not_false_bN_(left: Option) -> bool { - match left { - Some(true) => true, - Some(false) => false, - _ => true, - } -} - #[doc(hidden)] #[inline(always)] pub fn is_distinct__(left: T, right: T) -> bool @@ -1520,18 +1354,3 @@ impl Deref for StaticLazy { self.get() } } - -#[doc(hidden)] -// If the data is Ok(None), convert it to Err, otherwise leave it unchanged -pub fn unwrap_sql_result(data: SqlResult>) -> SqlResult { - match data { - Err(e) => Err(e), - Ok(None) => Err(SqlRuntimeError::from_strng("NULL result produced")), - Ok(Some(data)) => Ok(data), - } -} - -#[doc(hidden)] -pub fn wrap_sql_result(data: T) -> SqlResult { - Ok(data) -} diff --git a/crates/sqllib/src/uuid.rs b/crates/sqllib/src/uuid.rs index a2037f4377b..3a46da2457d 100644 --- a/crates/sqllib/src/uuid.rs +++ b/crates/sqllib/src/uuid.rs @@ -173,11 +173,11 @@ impl Display for Uuid { } #[doc(hidden)] -pub fn uuid_to_u128(u: Uuid) -> u128 { +pub fn uuid_to_u128_(u: Uuid) -> u128 { u128::from_be_bytes(*u.to_bytes()) } #[doc(hidden)] -pub fn u128_to_uuid(n: u128) -> Uuid { +pub fn u128_to_uuid_(n: u128) -> Uuid { Uuid::from_bytes(n.to_be_bytes()) } diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/backend/rust/ToRustInnerVisitor.java b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/backend/rust/ToRustInnerVisitor.java index b70447a3afe..b2510f81ec8 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/backend/rust/ToRustInnerVisitor.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/backend/rust/ToRustInnerVisitor.java @@ -1902,9 +1902,14 @@ public VisitDecision preorder(DBSPUnaryExpression expression) { return VisitDecision.STOP; } else if (expression.opcode == DBSPOpcode.INTEGER_TO_SHORT_INTERVAL || expression.opcode == DBSPOpcode.SHORT_INTERVAL_TO_INTEGER || + expression.opcode == DBSPOpcode.INTEGER_TO_LONG_INTERVAL || + expression.opcode == DBSPOpcode.LONG_INTERVAL_TO_INTEGER || expression.opcode == DBSPOpcode.INTEGER_TO_UUID || - expression.opcode == DBSPOpcode.UUID_TO_INTEGER) { + expression.opcode == DBSPOpcode.UUID_TO_INTEGER || + expression.opcode == DBSPOpcode.BOOL_TO_INTEGER || + expression.opcode == DBSPOpcode.INTEGER_TO_BOOL) { this.builder.append(expression.opcode.toString()) + .append(expression.getType().nullableUnderlineSuffix()) .append("("); expression.source.accept(this); this.builder.append(")"); diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/aggregates/RangeAggregates.java b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/aggregates/RangeAggregates.java index 5fe00f29ccd..b9a1120a895 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/aggregates/RangeAggregates.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/aggregates/RangeAggregates.java @@ -46,6 +46,7 @@ import org.dbsp.sqlCompiler.ir.type.derived.DBSPTypeRawTuple; import org.dbsp.sqlCompiler.ir.type.derived.DBSPTypeTuple; import org.dbsp.sqlCompiler.ir.type.primitive.DBSPTypeBaseType; +import org.dbsp.sqlCompiler.ir.type.primitive.DBSPTypeBool; import org.dbsp.sqlCompiler.ir.type.primitive.DBSPTypeDate; import org.dbsp.sqlCompiler.ir.type.primitive.DBSPTypeDecimal; import org.dbsp.sqlCompiler.ir.type.primitive.DBSPTypeInteger; @@ -196,6 +197,10 @@ public DBSPSimpleOperator implement(DBSPSimpleOperator input, DBSPSimpleOperator sortType = DBSPTypeInteger.getType(node, INT64, originalSortType.mayBeNull); convertToSigned = new DBSPUnaryExpression( this.node, sortType, DBSPOpcode.SHORT_INTERVAL_TO_INTEGER, var).closure(var); + } else if (originalSortType.is(DBSPTypeLongInterval.class)) { + sortType = DBSPTypeInteger.getType(node, INT32, originalSortType.mayBeNull); + convertToSigned = new DBSPUnaryExpression( + this.node, sortType, DBSPOpcode.LONG_INTERVAL_TO_INTEGER, var).closure(var); } else if (originalSortType.is(DBSPTypeUuid.class)) { if (originalSortType.mayBeNull) { // This is 128 bits, but we need one more to represent the NULL value. @@ -204,6 +209,10 @@ public DBSPSimpleOperator implement(DBSPSimpleOperator input, DBSPSimpleOperator sortType = DBSPTypeInteger.getType(node, UINT128, originalSortType.mayBeNull); convertToSigned = new DBSPUnaryExpression( this.node, sortType, DBSPOpcode.UUID_TO_INTEGER, var.applyClone()).closure(var); + } else if (originalSortType.is(DBSPTypeBool.class)) { + sortType = DBSPTypeInteger.getType(node, INT8, originalSortType.mayBeNull); + convertToSigned = new DBSPUnaryExpression( + this.node, sortType, DBSPOpcode.BOOL_TO_INTEGER, var.applyClone()).closure(var); } else { sortType = originalSortType; } @@ -311,12 +320,18 @@ public DBSPSimpleOperator implement(DBSPSimpleOperator input, DBSPSimpleOperator unwrap = unwrap.cast(this.node, i128, DBSPCastExpression.CastType.SqlUnsafe); unwrap = new DBSPUnaryExpression(this.node, originalSortType, DBSPOpcode.INTEGER_TO_DECIMAL, unwrap); + } else if (originalSortType.is(DBSPTypeLongInterval.class)) { + unwrap = new DBSPUnaryExpression(this.node, originalSortType, + DBSPOpcode.INTEGER_TO_LONG_INTERVAL, unwrap); } else if (originalSortType.is(DBSPTypeShortInterval.class)) { unwrap = new DBSPUnaryExpression(this.node, originalSortType, DBSPOpcode.INTEGER_TO_SHORT_INTERVAL, unwrap); } else if (originalSortType.is(DBSPTypeUuid.class)) { unwrap = new DBSPUnaryExpression(this.node, originalSortType, DBSPOpcode.INTEGER_TO_UUID, unwrap); + } else if (originalSortType.is(DBSPTypeBool.class)) { + unwrap = new DBSPUnaryExpression(this.node, originalSortType, + DBSPOpcode.INTEGER_TO_BOOL, unwrap); } DBSPExpression ixKey = var.field(0).deref(); diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/visitors/monotone/MonotoneTransferFunctions.java b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/visitors/monotone/MonotoneTransferFunctions.java index c15469a175d..ee41e7da1c5 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/visitors/monotone/MonotoneTransferFunctions.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/visitors/monotone/MonotoneTransferFunctions.java @@ -744,7 +744,9 @@ public void postorder(DBSPUnsignedUnwrapExpression expression) { DBSPOpcode.UNARY_PLUS, DBSPOpcode.TYPEDBOX, DBSPOpcode.DECIMAL_TO_INTEGER, DBSPOpcode.INTEGER_TO_DECIMAL, DBSPOpcode.SHORT_INTERVAL_TO_INTEGER, DBSPOpcode.INTEGER_TO_SHORT_INTERVAL, - DBSPOpcode.UUID_TO_INTEGER, DBSPOpcode.INTEGER_TO_UUID + DBSPOpcode.LONG_INTERVAL_TO_INTEGER, DBSPOpcode.INTEGER_TO_LONG_INTERVAL, + DBSPOpcode.UUID_TO_INTEGER, DBSPOpcode.INTEGER_TO_UUID, + DBSPOpcode.BOOL_TO_INTEGER, DBSPOpcode.INTEGER_TO_BOOL ); @Override diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/ir/expression/DBSPOpcode.java b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/ir/expression/DBSPOpcode.java index 958e0be6357..7bafa525c1e 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/ir/expression/DBSPOpcode.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/ir/expression/DBSPOpcode.java @@ -25,9 +25,15 @@ public enum DBSPOpcode { // Lossless, order-preserving conversion between short interval and INTEGER, used for range aggregates SHORT_INTERVAL_TO_INTEGER("short_interval_to_integer", false), INTEGER_TO_SHORT_INTERVAL("integer_to_short_interval", false), + // Lossless, order-preserving conversion between long interval and INTEGER, used for range aggregates + LONG_INTERVAL_TO_INTEGER("long_interval_to_integer", false), + INTEGER_TO_LONG_INTERVAL("integer_to_long_interval", false), // Lossless, order-preserving conversion between UUID and INTEGER, used for range aggregates UUID_TO_INTEGER("uuid_to_u128", false), INTEGER_TO_UUID("u128_to_uuid", false), + // Lossless, order-preserving conversion between Bool and INTEGER, used for range aggregates + BOOL_TO_INTEGER("bool_to_i8", false), + INTEGER_TO_BOOL("i8_to_bool", false), // Binary operations ADD("+", false), @@ -137,7 +143,8 @@ public boolean isStrict() { AGG_MAX1, AGG_MIN1, INDICATOR -> false; case NEG, DIV_INTERVAL, DIV_INTERVAL_NULL, MUL_INTERVAL, DECIMAL_TO_INTEGER, INTEGER_TO_DECIMAL, SHORT_INTERVAL_TO_INTEGER, INTEGER_TO_SHORT_INTERVAL, INTEGER_TO_UUID, UUID_TO_INTEGER, - RUST_INDEX, VARIANT_INDEX, MAP_INDEX, + LONG_INTERVAL_TO_INTEGER, INTEGER_TO_LONG_INTERVAL, + BOOL_TO_INTEGER, INTEGER_TO_BOOL, RUST_INDEX, VARIANT_INDEX, MAP_INDEX, SQL_INDEX, XOR, BW_OR, MUL_WEIGHT, BW_AND, GTE, LTE, GT, LT, NEQ, EQ, MOD, DIV_NULL, DIV, MUL, SUB, ADD, TYPEDBOX, IS_TRUE, IS_FALSE, NOT, UNARY_PLUS -> true; default -> throw new UnimplementedException(); diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/ir/expression/DBSPUnaryExpression.java b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/ir/expression/DBSPUnaryExpression.java index 500274f4ce6..bedc80b17c1 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/ir/expression/DBSPUnaryExpression.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/ir/expression/DBSPUnaryExpression.java @@ -80,9 +80,13 @@ public IIndentStream toString(IIndentStream builder) { this.opcode == DBSPOpcode.INTEGER_TO_DECIMAL || this.opcode == DBSPOpcode.SHORT_INTERVAL_TO_INTEGER || this.opcode == DBSPOpcode.INTEGER_TO_SHORT_INTERVAL || + this.opcode == DBSPOpcode.LONG_INTERVAL_TO_INTEGER || + this.opcode == DBSPOpcode.INTEGER_TO_LONG_INTERVAL || this.opcode == DBSPOpcode.REINTERPRET || this.opcode == DBSPOpcode.INTEGER_TO_UUID || - this.opcode == DBSPOpcode.UUID_TO_INTEGER) { + this.opcode == DBSPOpcode.UUID_TO_INTEGER || + this.opcode == DBSPOpcode.INTEGER_TO_BOOL || + this.opcode == DBSPOpcode.BOOL_TO_INTEGER) { return builder.append(this.opcode.toString()) .append("(") .append(this.source) diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/Regression3Tests.java b/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/Regression3Tests.java index dca6faddb19..7d9f6b17cb5 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/Regression3Tests.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/Regression3Tests.java @@ -112,4 +112,261 @@ CREATE TABLE purchase ( JOIN purchase b ON a.ts = b.ts + INTERVAL '0' SECOND;"""); } + + @Test + public void issue457a() { + String program = """ + CREATE TABLE T ( + payment_id INT, + customer_id INT, + amount INT, + seq BOOL + ); + + CREATE VIEW V AS SELECT + customer_id, + SUM(amount) OVER (PARTITION BY customer_id ORDER BY seq) AS previous + FROM T;"""; + // Validated on postgres, which is NULLS LAST, so this needs explicit NULLS FIRST + String data = """ + INSERT INTO T VALUES + -- Customer 1: increasing seq + (1, 1, 10, FALSE), + (2, 1, 20, FALSE), + (3, 1, -5, TRUE), + -- Customer 2: out‑of‑order seq + (4, 2, 7, TRUE), + (5, 2, 0, FALSE), + (6, 2, 3, FALSE), + -- Customer 3: single row partition + (7, 3, 100, FALSE), + -- Customer 4: duplicate seq values (tests deterministic ordering) + (8, 4, 1, FALSE), + (9, 4, 2, FALSE), + (10,4, 3, TRUE), + -- Null seq + (11,1, 3, NULL); + """; + String expected = """ + customer_id | previous + ------------------------ + 1 | 3 + 1 | 33 + 1 | 33 + 1 | 28 + 2 | 3 + 2 | 3 + 2 | 10 + 3 | 100 + 4 | 3 + 4 | 3 + 4 | 6"""; + + var ccs = this.getCCS(program); + ccs.stepWeightOne(data, expected); + + var program1 = program.replace("ORDER BY seq", "ORDER BY seq NULLS FIRST"); + ccs = this.getCCS(program1); + ccs.stepWeightOne(data, expected); + + var program2 = program.replace("ORDER BY seq", "ORDER BY seq NULLS LAST"); + ccs = this.getCCS(program2); + String expected2 = """ + customer_id | previous + ------------------------ + 1 | 30 + 1 | 30 + 1 | 25 + 1 | 28 + 2 | 3 + 2 | 3 + 2 | 10 + 3 | 100 + 4 | 3 + 4 | 3 + 4 | 6"""; + ccs.stepWeightOne(data, expected2); + } + + @Test + public void issue457b() { + String program = """ + CREATE TABLE T ( + payment_id INT, + customer_id INT, + amount INT, + d1 TIMESTAMP, + d2 TIMESTAMP + ); + + CREATE VIEW V AS SELECT + customer_id, + SUM(amount) OVER (PARTITION BY customer_id ORDER BY (d1 - d2) HOURS) AS previous + FROM T;"""; + // Validated on postgres, which is NULLS LAST, so this needs explicit NULLS FIRST + String data = """ + INSERT INTO T VALUES + -- Customer 1: clean increasing + (1, 1, 10, TIMESTAMP '2020-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (2, 1, 20, TIMESTAMP '2020-01-01 12:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (3, 1, -5, TIMESTAMP '2020-01-01 16:00:00', TIMESTAMP '2020-01-01 09:00:00'), + -- Customer 2: out‑of‑order + (4, 2, 7, TIMESTAMP '2020-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (5, 2, 0, TIMESTAMP '2020-01-01 8:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (6, 2, 3, TIMESTAMP '2020-01-01 3:00:00', TIMESTAMP '2020-01-01 09:00:00'), + -- Customer 3: single row partition + (7, 3, 100, TIMESTAMP '2020-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + -- Customer 4: duplicate values + (8, 4, 1, TIMESTAMP '2020-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (9, 4, 2, TIMESTAMP '2020-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (10,4, 3, TIMESTAMP '2020-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + -- Null seq + (11,1, 3, NULL, NULL); + """; + String expected = """ + customer_id | previous + ------------------------ + 1 | 3 + 1 | 13 + 1 | 33 + 1 | 28 + 2 | 3 + 2 | 3 + 2 | 10 + 3 | 100 + 4 | 6 + 4 | 6 + 4 | 6"""; + + var ccs = this.getCCS(program); + ccs.stepWeightOne(data, expected); + + var program1 = program.replace("HOURS", "HOURS NULLS FIRST"); + ccs = this.getCCS(program1); + ccs.stepWeightOne(data, expected); + + var program2 = program.replace("HOURS", "HOURS NULLS LAST"); + ccs = this.getCCS(program2); + String expected2 = """ + customer_id | previous + ------------------------ + 1 | 10 + 1 | 30 + 1 | 25 + 1 | 28 + 2 | 3 + 2 | 3 + 2 | 10 + 3 | 100 + 4 | 6 + 4 | 6 + 4 | 6"""; + ccs.stepWeightOne(data, expected2); + } + + @Test + public void issue457c() { + String program = """ + CREATE TABLE T ( + payment_id INT, + customer_id INT, + amount INT, + d1 TIMESTAMP, + d2 TIMESTAMP + ); + + CREATE VIEW V AS SELECT + customer_id, + SUM(amount) OVER (PARTITION BY customer_id ORDER BY (d1 - d2) MONTHS) AS previous + FROM T;"""; + // Validated on postgres, which is NULLS LAST, so this needs explicit NULLS FIRST + String data = """ + INSERT INTO T VALUES + -- Customer 1: clean increasing + (1, 1, 10, TIMESTAMP '2021-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (2, 1, 20, TIMESTAMP '2023-01-01 12:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (3, 1, -5, TIMESTAMP '2027-01-01 16:00:00', TIMESTAMP '2020-01-01 09:00:00'), + -- Customer 2: out‑of‑order + (4, 2, 7, TIMESTAMP '2021-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (5, 2, 0, TIMESTAMP '2019-01-01 8:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (6, 2, 3, TIMESTAMP '2013-01-01 3:00:00', TIMESTAMP '2020-01-01 09:00:00'), + -- Customer 3: single row partition + (7, 3, 100, TIMESTAMP '2021-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + -- Customer 4: duplicate values + (8, 4, 1, TIMESTAMP '2021-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (9, 4, 2, TIMESTAMP '2021-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + (10,4, 3, TIMESTAMP '2021-01-01 10:00:00', TIMESTAMP '2020-01-01 09:00:00'), + -- Null seq + (11,1, 3, NULL, NULL); + """; + String expected = """ + customer_id | previous + ------------------------ + 1 | 3 + 1 | 13 + 1 | 33 + 1 | 28 + 2 | 3 + 2 | 3 + 2 | 10 + 3 | 100 + 4 | 6 + 4 | 6 + 4 | 6"""; + + var ccs = this.getCCS(program); + ccs.stepWeightOne(data, expected); + + var program1 = program.replace("MONTHS", "MONTHS NULLS FIRST"); + ccs = this.getCCS(program1); + ccs.stepWeightOne(data, expected); + + var program2 = program.replace("MONTHS", "MONTHS NULLS LAST"); + ccs = this.getCCS(program2); + String expected2 = """ + customer_id | previous + ------------------------ + 1 | 10 + 1 | 30 + 1 | 25 + 1 | 28 + 2 | 3 + 2 | 3 + 2 | 10 + 3 | 100 + 4 | 6 + 4 | 6 + 4 | 6"""; + ccs.stepWeightOne(data, expected2); + } + + @Test + public void issue457d() { + // Test that programs sorting on non-nullable intervals and booleans compile + String root = """ + CREATE TABLE T ( + payment_id INT, + customer_id INT, + amount INT, + d1 TIMESTAMP NOT NULL, + d2 TIMESTAMP NOT NULL + ); + + CREATE VIEW V AS SELECT + customer_id, + """; + this.getCCS(root + "SUM(amount) OVER (PARTITION BY customer_id ORDER BY (d1 - d2) MONTHS) AS previous FROM T;"); + this.getCCS(root + "SUM(amount) OVER (PARTITION BY customer_id ORDER BY (d1 - d2) SECONDS) AS previous FROM T;"); + this.getCCS(""" + CREATE TABLE T ( + payment_id INT, + customer_id INT, + amount INT, + seq BOOL NOT NULL + ); + + CREATE VIEW V AS SELECT + customer_id, + SUM(amount) OVER (PARTITION BY customer_id ORDER BY seq) AS previous FROM T;"""); + } }