#![allow(dead_code)]
use pylon_core::query;
use pylon_core::schema::{
LinkDescriptor, MultiLinkDescriptor, PropertyDescriptor, RewriteEntry, SchemaDescriptor, TriggerDescriptor,
};
use pylon_pgcon::{ExtensionOids, PgPool};
use pylon_value::DecodedValue;
use std::collections::HashMap;
pub fn test_dsn() -> String {
std::env::var("PYLON_PGCON_TEST_DSN").expect("PYLON_PGCON_TEST_DSN must be set to run live-Postgres tests")
}
pub async fn test_pool() -> PgPool {
PgPool::connect(&test_dsn(), 5).await.unwrap()
}
pub fn unique_module(prefix: &str) -> String {
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let nanos = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
format!("{prefix}_{nanos}_{seq}")
}
pub fn id_prop() -> PropertyDescriptor {
PropertyDescriptor {
name: "id".into(),
pg_type: "uuid".into(),
nullable: false,
default_sql: Some("gen_random_uuid()".into()),
default_pyql: None,
description: None,
check_constraints: vec![],
is_exclusive: true,
is_pk: true,
is_readonly: true,
rewrites: vec![],
tuple_members: None,
column_type: None,
}
}
pub fn text_prop(name: &str) -> PropertyDescriptor {
PropertyDescriptor {
name: name.into(),
pg_type: "text".into(),
nullable: false,
default_sql: None,
default_pyql: None,
description: None,
check_constraints: vec![],
is_exclusive: false,
is_pk: false,
is_readonly: false,
rewrites: vec![],
tuple_members: None,
column_type: None,
}
}
pub fn link(name: &str, target_qname: &str) -> LinkDescriptor {
LinkDescriptor {
name: name.into(),
target: target_qname.into(),
nullable: false,
through: None,
description: None,
default_pyql: None,
is_exclusive: false,
is_readonly: false,
rewrites: vec![],
on_delete: vec![],
}
}
pub fn multilink(name: &str, target_qname: &str) -> MultiLinkDescriptor {
MultiLinkDescriptor {
name: name.into(),
target: target_qname.into(),
through: None,
nullable: false,
description: None,
default_pyql: None,
on_delete: vec![],
is_exclusive: false,
}
}
pub fn multilink_through(name: &str, target_qname: &str, through_qname: &str) -> MultiLinkDescriptor {
MultiLinkDescriptor {
name: name.into(),
target: target_qname.into(),
through: Some(through_qname.into()),
nullable: false,
description: None,
default_pyql: None,
on_delete: vec![],
is_exclusive: false,
}
}
pub fn trigger(on: u8, timing: &str, handler: &str) -> TriggerDescriptor {
TriggerDescriptor {
on,
timing: timing.into(),
handler: handler.into(),
}
}
pub fn rewrite(on: u8, handler: &str) -> RewriteEntry {
RewriteEntry {
on,
handler: handler.into(),
}
}
pub async fn assert_zero_further_steps(pool: &PgPool, schema: &SchemaDescriptor) {
let live = pylon_core::introspect::introspect_db_state(pool).await.unwrap();
let steps = pylon_core::diff::diff_schema_steps(schema, &live, &HashMap::new()).unwrap();
assert!(
steps.is_empty(),
"expected zero further migration steps, got: {steps:?}"
);
}
pub async fn eval_scalar(pool: &PgPool, expr: &str) -> DecodedValue {
let schema = SchemaDescriptor::default();
let compiled = query::compile(&format!("select {expr}"), &schema).unwrap();
let rows = pool
.query_typed(&compiled.sql, &[], &ExtensionOids::default())
.await
.unwrap();
assert_eq!(
rows.len(),
1,
"scalar select should return exactly one row, got {rows:?}"
);
match rows.into_iter().next().unwrap() {
DecodedValue::Composite(mut fields) if fields.len() == 1 => fields.remove(0),
other => panic!("expected a one-element Composite wrapping the scalar, got {other:?}"),
}
}