use crate::err::TranslationErrors;
use crate::tree::ast::query::Query;
use crate::tree::ast::ParseWithErrors;
use crate::tree::tests::shared::{catalog_with_tables, type_check_with_catalog};
use crate::types::array::Array;
use crate::types::struct_type::Struct;
use crate::types::{Type, INT, STRING};
use rstest::rstest;
#[rstest]
#[case::struct_literal_creates_separate_bindings(
vec![(
"events",
vec![
("id", INT),
("name", STRING),
("ip", STRING),
]
)],
r#"
FROM events
| SET host_info = {name: name, ip: ip}
"#,
Struct::default()
.with_str("host_info", Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
.into())
.with_str("id", INT)
.with_str("name", STRING)
.with_str("ip", STRING)
)]
#[case::nested_struct_literal(
vec![(
"events",
vec![
("id", INT),
("name", STRING),
("ip", STRING),
]
)],
r#"
FROM events
| SET data = {host: {name: name, ip: ip}}
"#,
Struct::default()
.with_str("data", Struct::default()
.with_str("host", Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
.into())
.into())
.with_str("id", INT)
.with_str("name", STRING)
.with_str("ip", STRING)
)]
#[case::rebind_field_after_struct_literal(
vec![(
"events",
vec![
("id", INT),
("name", STRING),
("ip", STRING),
]
)],
r#"
FROM events
| SET host_info = {name: name, ip: ip, status: "active"}
| SET host_info.status = "inactive"
"#,
Struct::default()
.with_str("host_info", Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
.with_str("status", STRING) .into())
.with_str("id", INT)
.with_str("name", STRING)
.with_str("ip", STRING)
)]
#[case::array_element_access_terminal_struct(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("host", Type::Struct(
Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
))
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first_data = all_data[0]
"#,
Struct::default()
.with_str("first_data", Type::Struct(
Struct::default()
.with_str("host", Type::Struct(
Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
))
))
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("host", Type::Struct(
Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
))
)).into())
)]
#[case::rebind_field_after_array_access(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("host", Type::Struct(
Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
))
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first_data = all_data[0]
| SET first_data.host = first_data.host.name
"#,
Struct::default()
.with_str("first_data", Type::Struct(
Struct::default()
.with_str("host", STRING)
))
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("host", Type::Struct(
Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
))
)).into())
)]
#[case::column_reference_struct_type(
vec![(
"events",
vec![
("id", INT),
("metadata", Type::Struct(
Struct::default()
.with_str("host", STRING)
.with_str("ip", STRING)
))
]
)],
r#"
FROM events
| SET meta = metadata
"#,
Struct::default()
.with_str("meta", Type::Struct(
Struct::default()
.with_str("host", STRING)
.with_str("ip", STRING)
))
.with_str("id", INT)
.with_str("metadata", Type::Struct(
Struct::default()
.with_str("host", STRING)
.with_str("ip", STRING)
))
)]
#[case::rebind_nested_field_after_column_reference(
vec![(
"events",
vec![
("id", INT),
("metadata", Type::Struct(
Struct::default()
.with_str("host", Type::Struct(
Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
))
))
]
)],
r#"
FROM events
| SET meta = metadata
| SET meta.host = meta.host.name
"#,
Struct::default()
.with_str("meta", Type::Struct(
Struct::default()
.with_str("host", STRING)
))
.with_str("id", INT)
.with_str("metadata", Type::Struct(
Struct::default()
.with_str("host", Type::Struct(
Struct::default()
.with_str("name", STRING)
.with_str("ip", STRING)
))
))
)]
#[case::multiple_nesting_levels_with_overrides(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("level1", Type::Struct(
Struct::default()
.with_str("level2", Type::Struct(
Struct::default()
.with_str("value", STRING)
.with_str("extra", INT)
))
))
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| SET first.level1.level2 = first.level1.level2.value
"#,
Struct::default()
.with_str("first", Type::Struct(
Struct::default()
.with_str("level1", Type::Struct(
Struct::default()
.with_str("level2", STRING)
))
))
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("level1", Type::Struct(
Struct::default()
.with_str("level2", Type::Struct(
Struct::default()
.with_str("value", STRING)
.with_str("extra", INT)
))
))
)).into())
)]
#[case::field_order_preservation_many_fields(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
.with_str("d", Type::Struct(
Struct::default()
.with_str("x", STRING)
.with_str("y", STRING)
))
.with_str("e", STRING)
.with_str("f", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| SET first.d = first.d.x
"#,
Struct::default()
.with_str("first", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
.with_str("d", STRING) .with_str("e", STRING)
.with_str("f", STRING)
))
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
.with_str("d", Type::Struct(
Struct::default()
.with_str("x", STRING)
.with_str("y", STRING)
))
.with_str("e", STRING)
.with_str("f", STRING)
)).into())
)]
#[case::drop_field_from_struct_expression(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| DROP first.b
"#,
Struct::default()
.with_str("first", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("c", STRING)
))
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
)).into())
)]
#[case::drop_multiple_fields_from_struct_expression(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
.with_str("d", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| DROP first.a, first.c
"#,
Struct::default()
.with_str("first", Type::Struct(
Struct::default()
.with_str("b", STRING)
.with_str("d", STRING)
))
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
.with_str("d", STRING)
)).into())
)]
#[case::drop_nested_field_from_struct_expression(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("nested", Type::Struct(
Struct::default()
.with_str("x", STRING)
.with_str("y", STRING)
))
.with_str("b", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| DROP first.nested
"#,
Struct::default()
.with_str("first", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
))
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("nested", Type::Struct(
Struct::default()
.with_str("x", STRING)
.with_str("y", STRING)
))
.with_str("b", STRING)
)).into())
)]
#[case::multiple_drop_commands_on_struct(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
.with_str("d", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| DROP first.a
| DROP first.c
"#,
Struct::default()
.with_str("first", Type::Struct(
Struct::default()
.with_str("b", STRING)
.with_str("d", STRING)
))
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
.with_str("d", STRING)
)).into())
)]
#[case::drop_from_struct_literal(
vec![(
"events",
vec![
("id", INT),
("name", STRING),
("email", STRING),
]
)],
r#"
FROM events
| SET user = {name: name, email: email}
| DROP user.email
"#,
Struct::default()
.with_str("user", Struct::default()
.with_str("name", STRING)
.into())
.with_str("id", INT)
.with_str("name", STRING)
.with_str("email", STRING)
)]
#[case::drop_all_fields_of_struct(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| DROP first.a, first.b
"#,
Struct::default()
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
)).into())
)]
#[case::select_from_struct_expression(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| SELECT first.a, first.c
"#,
Struct::default()
.with_str("first", Struct::default()
.with_str("a", STRING)
.with_str("c", STRING)
.into())
)]
#[case::select_redefine_struct_field(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| SELECT first.a = "new_value", first.c
"#,
Struct::default()
.with_str("first", Struct::default()
.with_str("a", STRING) .with_str("c", STRING)
.into())
)]
#[case::select_add_struct_field(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| SELECT first.a, first.c, first.d = "new_field"
"#,
Struct::default()
.with_str("first", Struct::default()
.with_str("a", STRING)
.with_str("c", STRING)
.with_str("d", STRING) .into())
)]
#[case::window_to_struct_expression(
vec![(
"events",
vec![
("id", INT),
("data", Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
))
]
)],
r#"
FROM events
| AGG all_data = array_agg(data)
| SET first = all_data[0]
| WINDOW first.total = count()
"#,
Struct::default()
.with_str("first", Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
.with_str("total", INT) .into())
.with_str("all_data", Array::new(Type::Struct(
Struct::default()
.with_str("a", STRING)
.with_str("b", STRING)
.with_str("c", STRING)
)).into())
)]
#[case::struct_literal_replaces_existing_children(
vec![(
"events",
vec![
("id", INT),
]
)],
r#"
FROM events
| SET x.a = 1
| SET x = {y: 2}
"#,
Struct::default()
.with_str("x", Struct::default()
.with_str("y", INT)
.into())
.with_str("id", INT)
)]
#[case::struct_literal_replaces_deep_children(
vec![(
"events",
vec![
("id", INT),
]
)],
r#"
FROM events
| SET x.a.b = 1
| SET x.a.c = 2
| SET x = {y: 3}
"#,
Struct::default()
.with_str("x", Struct::default()
.with_str("y", INT)
.into())
.with_str("id", INT)
)]
#[case::nested_struct_literal_replaces_children(
vec![(
"events",
vec![
("id", INT),
]
)],
r#"
FROM events
| SET x.a = 1
| SET x.b = 2
| SET x = {y: {z: 3}}
"#,
Struct::default()
.with_str("x", Struct::default()
.with_str("y", Struct::default()
.with_str("z", INT)
.into())
.into())
.with_str("id", INT)
)]
#[case::struct_literal_at_nested_path_replaces_children(
vec![(
"events",
vec![
("id", INT),
]
)],
r#"
FROM events
| SET x.a.b = 1
| SET x.a.c = 2
| SET x.a = {d: 3}
"#,
Struct::default()
.with_str("x", Struct::default()
.with_str("a", Struct::default()
.with_str("d", INT)
.into())
.into())
.with_str("id", INT)
)]
#[case::struct_literal_replaces_mix_same_and_different_fields(
vec![(
"events",
vec![
("id", INT),
]
)],
r#"
FROM events
| SET x.a = 1
| SET x.b = 2
| SET x = {a: 3, c: 4}
"#,
Struct::default()
.with_str("x", Struct::default()
.with_str("a", INT)
.with_str("c", INT)
.into())
.with_str("id", INT)
)]
fn test_struct_expressions(
#[case] tables: Vec<(&str, Vec<(&str, Type)>)>,
#[case] query_str: &str,
#[case] expected_schema: Struct,
) -> Result<(), TranslationErrors> {
let query = Query::parse_result(query_str)?;
let catalog = catalog_with_tables(tables);
let typed = type_check_with_catalog(query, catalog).into_result()?;
let actual_schema = typed.schema();
assert_eq!(actual_schema, expected_schema);
Ok(())
}