use super::*;
use crate::binding::{self, fixture};
#[derive(serde::Deserialize)]
struct Reference {
sql: String,
collations: Vec<u32>,
}
fn context() -> binding::BindingContext<'static> {
let mut columns = vec![
column("c", ColumnType::Text),
column("p", ColumnType::Text),
column("n", ColumnType::Integer),
];
columns[0].attribute_number = Some(1);
columns[1].attribute_number = Some(4);
columns[0].collation_oid = Some(950);
columns[1].collation_oid = Some(951);
let table = fixture::table_definition(columns);
binding::BindingContext {
catalog: fixture::catalog_with_foreign_tables(
BTreeMap::from([
(RelationIdentity::new("public", "local"), table.clone()),
(RelationIdentity::new("public", "t"), table.clone()),
]),
BTreeMap::from([(RelationIdentity::new("public", "remote"), table)]),
),
..fixture::empty_binding_context()
}
}
#[test]
fn declared_collations_follow_native_foreign_and_bound_source_aliases() {
let context = context();
for table in ["local", "remote"] {
for bound in [false, true] {
let mut source = SourcePlan::Table {
name: table.into(),
qualifier: table.into(),
alias: Some("q".into()),
column_aliases: vec!["first".into(), "second".into(), "number".into()],
bound_columns: bound.then(|| vec!["p".into(), "c".into(), "n".into()]),
include_descendants: true,
};
let bound_schema = binding::bind_source_plan_schema(
&EmptyRoutineResolution,
&source,
&[],
&context,
None,
)
.unwrap();
let analyzed = binding::analyze_source_plan_schema(
&EmptyRoutineResolution,
&source,
&[],
&context,
None,
)
.unwrap();
let executing = binding::bind_source_plan_schema_for_execution(
&EmptyRoutineResolution,
&mut source,
&[],
&context,
None,
)
.unwrap();
for schema in [bound_schema, analyzed, executing] {
let expected = if bound { [951, 950, 0] } else { [950, 951, 0] };
for (position, oid) in expected.into_iter().enumerate() {
assert_eq!(schema.column_collation(position), Some(oid), "{table}");
}
assert_eq!(schema.qualified_collation("q", "first"), Some(expected[0]));
assert_eq!(schema.collation_of("second"), Some(expected[1]));
assert_eq!(schema.columns(), ["first", "second", "number"]);
assert_eq!(schema.physical_slot(1), Some(1));
}
}
}
}
#[test]
fn query_outputs_preserve_direct_collations_through_ctes_and_subqueries() {
let context = context();
let references: BTreeMap<String, Vec<Reference>> =
serde_json::from_str(include_str!("query_collations.json")).unwrap();
for case in &references["direct_queries"] {
let sql = &case.sql;
let expected = &case.collations;
let crate::plan::UnifiedPlan::Query(query) =
crate::plan::UnifiedPlan::lower(crate::compile(sql).unwrap().remove(0))
else {
panic!("query")
};
for schema in [
binding::bind_query_plan_schema(&EmptyRoutineResolution, &query, &[], &context, None),
binding::analyze_query_plan_schema(
&EmptyRoutineResolution,
&query,
&[],
&context,
None,
),
] {
let schema = schema.unwrap_or_else(|error| panic!("{sql}: {error}"));
assert_eq!(schema.len(), expected.len(), "{sql}");
for (position, oid) in expected.iter().enumerate() {
assert_eq!(schema.column_collation(position), Some(*oid), "{sql}");
}
}
}
}
#[test]
fn operator_join_inputs_retain_each_relation_declaration() {
let mut left = column("value", ColumnType::Text);
left.collation_oid = Some(950);
let mut right = left.clone();
right.collation_oid = Some(951);
let catalog = fixture::catalog(BTreeMap::from([
(
RelationIdentity::new("public", "a"),
fixture::table_definition(vec![left]),
),
(
RelationIdentity::new("public", "b"),
fixture::table_definition(vec![right]),
),
]));
let resolution = fixture::resolution(vec!["public".into()], "pg_temp_fixture".into());
let (left, right) = binding::catalog_sources::operator_join_relation_schemas(
&catalog,
&resolution,
Some(&crate::ast::OperatorJoinRelations {
left: "a".into(),
right: "b".into(),
}),
)
.unwrap();
assert_eq!(left.qualified_collation("a", "value"), Some(950));
assert_eq!(right.qualified_collation("b", "value"), Some(951));
}
#[test]
fn dml_output_collations_match_postgresql_for_row_images_and_command_ctes() {
let context = context();
let references: BTreeMap<String, Vec<Reference>> =
serde_json::from_str(include_str!("dml_collations.json")).unwrap();
for case in references.values().flatten() {
let plan = crate::plan::UnifiedPlan::lower(crate::compile(&case.sql).unwrap().remove(0));
let schemas = match plan {
crate::plan::UnifiedPlan::Command(command) => {
vec![binding::analyze_prepared_command_schema(
&EmptyRoutineResolution,
&command,
&[],
&context,
)
.unwrap_or_else(|error| panic!("{}: {error}", case.sql))
.unwrap()]
}
crate::plan::UnifiedPlan::Query(query) => vec![
binding::bind_query_plan_schema(
&EmptyRoutineResolution,
&query,
&[],
&context,
None,
)
.unwrap_or_else(|error| panic!("{}: {error}", case.sql)),
binding::analyze_query_plan_schema(
&EmptyRoutineResolution,
&query,
&[],
&context,
None,
)
.unwrap_or_else(|error| panic!("{}: {error}", case.sql)),
],
};
for schema in schemas {
assert_eq!(
schema.len(),
case.collations.len(),
"{}: {:?}",
case.sql,
schema.columns()
);
for (position, oid) in case.collations.iter().enumerate() {
assert_eq!(
schema.column_collation(position),
Some(*oid),
"{}",
case.sql
);
}
}
}
}