mod common;
use common::*;
use pylon_core::export::export_schema;
use pylon_core::query;
use pylon_core::schema::{
FunctionDescriptor, GlobalDescriptor, LinkDescriptor, PropertyDescriptor, SchemaDescriptor, TypeDescriptor,
};
use pylon_pgcon::ExtensionOids;
use pylon_value::DecodedValue;
fn ty(name: &str, module: &str, properties: Vec<PropertyDescriptor>, links: Vec<LinkDescriptor>) -> TypeDescriptor {
TypeDescriptor {
name: name.into(),
module: module.into(),
table: name.into(),
abstract_: false,
materialized: true,
description: None,
parents: vec![],
interfaces: vec![],
bases: vec![],
properties,
links,
multilinks: vec![],
computed: vec![],
constraints: vec![],
indexes: vec![],
partition: None,
vector_indexes: vec![],
search_indexes: vec![],
triggers: vec![],
junction: false,
signals: vec![],
}
}
fn field(row: &DecodedValue, i: usize) -> &DecodedValue {
match row {
DecodedValue::Composite(fields) => fields.get(i).unwrap_or(&DecodedValue::Null),
other => panic!("expected a Composite-shaped row, got {other:?}"),
}
}
fn schema_with_a_global_reading_link_default(module: &str) -> SchemaDescriptor {
let account = ty("Account", module, vec![id_prop(), text_prop("name")], vec![]);
let mut created_by = link("created_by", &format!("{module}::Account"));
created_by.nullable = true;
created_by.default_pyql = Some(format!("{module}::account_of_transaction()"));
let widget = ty("Widget", module, vec![id_prop(), text_prop("name")], vec![created_by]);
SchemaDescriptor {
types: vec![account, widget],
globals: vec![GlobalDescriptor {
name: "current_account_id".into(),
module: module.into(),
scalar_type: "std::uuid".into(),
required: false,
default_expr: None,
computed_expr: None,
}],
functions: vec![FunctionDescriptor {
name: "account_of_transaction".into(),
module: module.into(),
params: vec![],
return_pg_type: format!("{module}::Account"),
return_is_object: true,
return_is_set: false,
return_is_polymorphic: false,
volatility: "stable".into(),
body: format!("select {module}::Account filter .id = global {module}::current_account_id"),
}],
..Default::default()
}
}
async fn bootstrap(pool: &pylon_pgcon::PgPool, sd: &SchemaDescriptor) {
pool.batch_execute(&pylon_core::stdlib::export_stdlib()).await.unwrap();
pool.batch_execute(&export_schema(sd).unwrap()).await.unwrap();
}
#[tokio::test]
#[ignore = "requires a live Postgres via PYLON_PGCON_TEST_DSN"]
async fn a_link_default_reading_a_global_populates_the_column() {
let module = unique_module("live_def_global");
let sd = schema_with_a_global_reading_link_default(&module);
let pool = test_pool().await;
bootstrap(&pool, &sd).await;
let compiled = query::compile(&format!("insert {module}::Account {{ name := 'Ada' }}"), &sd).unwrap();
pool.execute_typed(&compiled.sql, &[]).await.unwrap();
let compiled = query::compile(&format!("select {module}::Account {{ id }}"), &sd).unwrap();
let accounts = pool
.query_typed(&compiled.sql, &[], &ExtensionOids::default())
.await
.unwrap();
let account_id = field(&accounts[0], 1).clone();
let compiled = query::compile(&format!("insert {module}::Widget {{ name := 'w' }}"), &sd).unwrap();
assert_eq!(
compiled.param_names,
vec![format!("__global__{module}::current_account_id")]
);
pool.execute_typed(&compiled.sql, std::slice::from_ref(&account_id))
.await
.unwrap();
let compiled = query::compile(&format!("select {module}::Widget {{ created_by: {{ name }} }}"), &sd).unwrap();
let rows = pool
.query_typed(&compiled.sql, &[], &ExtensionOids::default())
.await
.unwrap();
assert_eq!(rows.len(), 1);
let creator = field(&rows[0], 2);
assert!(
matches!(creator, DecodedValue::Composite(_)),
"created_by must be populated, got {creator:?}"
);
assert_eq!(field(creator, 2), &DecodedValue::Str("Ada".to_string()));
}
#[tokio::test]
#[ignore = "requires a live Postgres via PYLON_PGCON_TEST_DSN"]
async fn an_explicit_value_wins_over_the_default() {
let module = unique_module("live_def_explicit");
let sd = schema_with_a_global_reading_link_default(&module);
let pool = test_pool().await;
bootstrap(&pool, &sd).await;
let compiled = query::compile(&format!("insert {module}::Account {{ name := 'Ada' }}"), &sd).unwrap();
pool.execute_typed(&compiled.sql, &[]).await.unwrap();
let compiled = query::compile(
&format!("insert {module}::Widget {{ name := 'w', created_by := {{}} }}"),
&sd,
)
.unwrap();
assert!(compiled.param_names.is_empty(), "{:?}", compiled.param_names);
pool.execute_typed(&compiled.sql, &[]).await.unwrap();
let compiled = query::compile(&format!("select {module}::Widget {{ created_by: {{ name }} }}"), &sd).unwrap();
let rows = pool
.query_typed(&compiled.sql, &[], &ExtensionOids::default())
.await
.unwrap();
assert_eq!(field(&rows[0], 2), &DecodedValue::Null);
}
#[tokio::test]
#[ignore = "requires a live Postgres via PYLON_PGCON_TEST_DSN"]
async fn the_column_carries_no_default_of_its_own() {
let module = unique_module("live_def_nocol");
let sd = schema_with_a_global_reading_link_default(&module);
let pool = test_pool().await;
bootstrap(&pool, &sd).await;
let rows = pool
.query_typed(
&format!(
"SELECT column_default FROM information_schema.columns \
WHERE table_schema = '{module}' AND table_name = 'Widget' \
AND column_name = 'created_by_id'"
),
&[],
&ExtensionOids::default(),
)
.await
.unwrap();
assert_eq!(rows.len(), 1, "the column must exist");
assert_eq!(rows[0], DecodedValue::Null, "the column must carry no default");
}