mod common;
use common::*;
use pylon_core::export::export_schema;
use pylon_core::query;
use pylon_core::schema::{PropertyDescriptor, SchemaDescriptor, TypeDescriptor};
use pylon_pgcon::ExtensionOids;
use pylon_value::DecodedValue;
use std::collections::HashSet;
fn ty(name: &str, module: &str, properties: Vec<PropertyDescriptor>) -> 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: vec![],
multilinks: vec![],
computed: vec![],
constraints: vec![],
indexes: vec![],
partition: None,
vector_indexes: vec![],
search_indexes: vec![],
triggers: vec![],
junction: false,
signals: vec![],
}
}
fn int_prop(name: &str) -> PropertyDescriptor {
let mut p = text_prop(name);
p.pg_type = "int8".into();
p
}
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();
}
async fn exec(pool: &pylon_pgcon::PgPool, sd: &SchemaDescriptor, pyql: &str) {
let compiled = query::compile(pyql, sd).unwrap();
pool.execute_typed(&compiled.sql, &[]).await.unwrap();
}
async fn rows_of(pool: &pylon_pgcon::PgPool, sd: &SchemaDescriptor, pyql: &str) -> Vec<DecodedValue> {
let compiled = query::compile(pyql, sd).unwrap();
pool.query_typed(&compiled.sql, &[], &ExtensionOids::default())
.await
.unwrap()
}
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 as_str(v: &DecodedValue) -> &str {
match v {
DecodedValue::Str(s) => s,
other => panic!("expected Str, got {other:?}"),
}
}
fn as_i64(v: &DecodedValue) -> i64 {
match v {
DecodedValue::I64(n) => *n,
other => panic!("expected I64, got {other:?}"),
}
}
fn as_array(v: &DecodedValue) -> &[DecodedValue] {
match v {
DecodedValue::Array(items) => items,
other => panic!("expected Array, got {other:?}"),
}
}
#[tokio::test]
#[ignore = "requires a live Postgres via PYLON_PGCON_TEST_DSN"]
async fn insert_link_value_from_a_nested_insert_subquery() {
let module = unique_module("live_insert_nested_link");
let person = ty("Person", &module, vec![id_prop(), text_prop("name"), int_prop("age")]);
let mut post = ty("Post", &module, vec![id_prop(), text_prop("title")]);
post.links = vec![link("author", &format!("{module}::Person"))];
let sd = SchemaDescriptor {
types: vec![person, post],
..Default::default()
};
let pool = test_pool().await;
bootstrap(&pool, &sd).await;
exec(
&pool,
&sd,
&format!(
"insert {module}::Post {{ \
title := 'Hello', \
author := (select (insert {module}::Person {{ name := 'Alice', age := 30 }}) {{ id }}) \
}}"
),
)
.await;
let people = rows_of(&pool, &sd, &format!("select {module}::Person {{ name, age }}")).await;
assert_eq!(
people.len(),
1,
"the nested insert must have actually created the Person row"
);
assert_eq!(as_str(field(&people[0], 2)), "Alice");
assert_eq!(as_i64(field(&people[0], 3)), 30);
let posts = rows_of(
&pool,
&sd,
&format!("select {module}::Post {{ title, author: {{ name }} }}"),
)
.await;
assert_eq!(posts.len(), 1);
let author = field(&posts[0], 3);
assert_eq!(
as_str(field(author, 2)),
"Alice",
"Post.author must point at the row the nested insert created"
);
}
#[tokio::test]
#[ignore = "requires a live Postgres via PYLON_PGCON_TEST_DSN"]
async fn select_insert_shape_chaining_reads_the_newly_inserted_rows_fields() {
let module = unique_module("live_insert_select_chain");
let person = ty("Person", &module, vec![id_prop(), text_prop("name"), int_prop("age")]);
let sd = SchemaDescriptor {
types: vec![person],
..Default::default()
};
let pool = test_pool().await;
bootstrap(&pool, &sd).await;
let rows = rows_of(
&pool,
&sd,
&format!("select (insert {module}::Person {{ name := 'Bob', age := 25 }}) {{ name, age }}"),
)
.await;
assert_eq!(rows.len(), 1);
assert_eq!(as_str(field(&rows[0], 2)), "Bob");
assert_eq!(as_i64(field(&rows[0], 3)), 25);
let after = rows_of(&pool, &sd, &format!("select {module}::Person {{ name }}")).await;
assert_eq!(after.len(), 1);
assert_eq!(as_str(field(&after[0], 2)), "Bob");
}
#[tokio::test]
#[ignore = "requires a live Postgres via PYLON_PGCON_TEST_DSN"]
async fn insert_assigns_a_multilink_directly_not_via_append() {
let module = unique_module("live_insert_multilink_assign");
let person = ty("Person", &module, vec![id_prop(), text_prop("name")]);
let mut team = ty("Team", &module, vec![id_prop(), text_prop("name")]);
team.multilinks = vec![multilink("members", &format!("{module}::Person"))];
let sd = SchemaDescriptor {
types: vec![person, team],
..Default::default()
};
let pool = test_pool().await;
bootstrap(&pool, &sd).await;
exec(&pool, &sd, &format!("insert {module}::Person {{ name := 'Alice' }}")).await;
exec(&pool, &sd, &format!("insert {module}::Person {{ name := 'Bob' }}")).await;
exec(
&pool,
&sd,
&format!(
"insert {module}::Team {{ \
name := 'Alpha', \
members := {module}::Person \
}}"
),
)
.await;
let teams = rows_of(&pool, &sd, &format!("select {module}::Team {{ members: {{ name }} }}")).await;
assert_eq!(teams.len(), 1);
let members = as_array(field(&teams[0], 2));
let names: HashSet<String> = members.iter().map(|m| as_str(field(m, 2)).to_string()).collect();
assert_eq!(names, HashSet::from(["Alice".to_string(), "Bob".to_string()]));
}