#![cfg(feature = "test-postgres")]
#![allow(clippy::unwrap_used, clippy::print_stderr, clippy::panic)]
use std::process::Command;
use serde_json::{Value, json};
use tempfile::TempDir;
use tokio_postgres::NoTls;
const SCHEMA: &str = "p1533_native";
async fn seed(url: &str) {
let (client, conn) = tokio_postgres::connect(url, NoTls).await.unwrap();
tokio::spawn(async move {
let _ = conn.await;
});
client
.batch_execute(&format!(
"DROP SCHEMA IF EXISTS {SCHEMA} CASCADE;
CREATE SCHEMA {SCHEMA};
CREATE TABLE {SCHEMA}.tv_word (
id uuid PRIMARY KEY, word text NOT NULL, note text, data jsonb NOT NULL);
INSERT INTO {SCHEMA}.tv_word VALUES (gen_random_uuid(), 'a', NULL,
jsonb_build_object('word', 'a', 'note', NULL));
CREATE VIEW {SCHEMA}.v_word AS SELECT id, word, note, data FROM {SCHEMA}.tv_word;"
))
.await
.unwrap();
}
fn schema() -> Value {
let query = |name: &str, source: &str| {
json!({
"name": name, "return_type": "Word", "returns_list": true, "nullable": false,
"sql_source": source,
"arguments": [
{ "name": "word", "type": "String", "nullable": true },
{ "name": "note", "type": "String", "nullable": true },
{ "name": "id", "type": "UUID", "nullable": true }
]
})
};
json!({
"types": [{
"name": "Word",
"sql_source": format!("{SCHEMA}.tv_word"),
"fields": [
{ "name": "id", "type": "UUID", "nullable": false },
{ "name": "word", "type": "String", "nullable": false },
{ "name": "note", "type": "String", "nullable": true }
]
}],
"queries": [
query("words", &format!("{SCHEMA}.tv_word")),
query("wordsView", &format!("{SCHEMA}.v_word"))
]
})
}
fn compile(url: Option<&str>) -> Value {
let dir = TempDir::new().unwrap();
let input = dir.path().join("schema.json");
std::fs::write(&input, schema().to_string()).unwrap();
let out = dir.path().join("schema.compiled.json");
let mut args = vec![
"compile".to_string(),
input.to_str().unwrap().to_string(),
"--skip-hash".to_string(),
"-o".to_string(),
out.to_str().unwrap().to_string(),
];
if let Some(url) = url {
args.extend(["--database".to_string(), url.to_string()]);
}
let output = Command::new(env!("CARGO_BIN_EXE_fraiseql-cli")).args(&args).output().unwrap();
assert!(
output.status.success(),
"compile failed:\n{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
serde_json::from_str(&std::fs::read_to_string(out).unwrap()).unwrap()
}
fn native(compiled: &Value, query: &str, column: &str) -> Value {
let query = compiled["queries"]
.as_array()
.unwrap()
.iter()
.find(|q| q["name"] == query)
.unwrap_or_else(|| panic!("no query {query}"));
query["native_columns"][column].clone()
}
#[tokio::test]
async fn a_tables_not_null_column_is_recorded_not_null_and_only_it() {
let Some(url) = fraiseql_test_support::try_database_url() else {
eprintln!("skipping #1533: DATABASE_URL not set");
return;
};
seed(&url).await;
let compiled = compile(Some(&url));
assert_eq!(
native(&compiled, "words", "word"),
json!({ "pg_type": "text", "not_null": true })
);
assert_eq!(
native(&compiled, "words", "note"),
json!({ "pg_type": "text", "not_null": false })
);
assert_eq!(native(&compiled, "words", "id"), json!({ "pg_type": "uuid", "not_null": true }));
for column in ["word", "note", "id"] {
assert_eq!(native(&compiled, "wordsView", column)["not_null"], json!(false), "{column}");
}
}
#[tokio::test]
async fn a_native_column_inferred_without_a_database_is_recorded_nullable() {
let compiled = compile(None);
assert_eq!(
native(&compiled, "words", "id"),
json!({ "pg_type": "uuid", "not_null": false })
);
}