use scythe_codegen::{GeneratedCode, generate_with_backend, get_backend};
use scythe_core::analyzer::analyze;
use scythe_core::catalog::Catalog;
use scythe_core::dialect::SqlDialect;
use scythe_core::errors::{ErrorCode, ScytheError};
use scythe_core::parser::parse_query_with_dialect;
fn generate(schema: &str, query: &str, backend_name: &str) -> Result<GeneratedCode, ScytheError> {
let backend = get_backend(backend_name, "postgresql").expect("backend must support postgresql");
let catalog = Catalog::from_ddl_with_dialect(&[schema], &SqlDialect::PostgreSQL).expect("schema must parse");
let parsed = parse_query_with_dialect(query, &SqlDialect::PostgreSQL).expect("query must parse");
let analyzed = analyze(&catalog, &parsed).expect("query must analyze");
generate_with_backend(&analyzed, &*backend)
}
const SCHEMA_QUALIFIED_ENUM_SCHEMA: &str = "\
CREATE TYPE public.status AS ENUM ('active', 'inactive'); \
CREATE TABLE items (id INT PRIMARY KEY, status public.status NOT NULL);";
const SCHEMA_QUALIFIED_ENUM_QUERY: &str = "-- @name GetItem\n-- @returns :one\n\
SELECT id, status FROM items WHERE id = $1;";
#[test]
fn schema_qualified_enum_sanitizes_the_dot_in_rust() {
let code =
generate(SCHEMA_QUALIFIED_ENUM_SCHEMA, SCHEMA_QUALIFIED_ENUM_QUERY, "rust-sqlx").expect("codegen must succeed");
let enum_def = code.enum_def.expect("enum definition must be emitted");
assert!(
enum_def.contains("pub enum PublicStatus {"),
"expected the sanitized type name:\n{enum_def}"
);
assert!(
!enum_def.contains("Public.status"),
"the unsanitized, unparseable form must not appear:\n{enum_def}"
);
}
#[test]
fn schema_qualified_enum_sanitizes_the_dot_in_go() {
let code =
generate(SCHEMA_QUALIFIED_ENUM_SCHEMA, SCHEMA_QUALIFIED_ENUM_QUERY, "go-pgx").expect("codegen must succeed");
let enum_def = code.enum_def.expect("enum definition must be emitted");
assert!(
enum_def.contains("type PublicStatus string"),
"expected the sanitized type name:\n{enum_def}"
);
assert!(
!enum_def.contains("Public.status"),
"the unsanitized, unparseable form must not appear:\n{enum_def}"
);
}
#[test]
fn schema_qualified_enum_sanitizes_the_dot_in_python() {
let code = generate(
SCHEMA_QUALIFIED_ENUM_SCHEMA,
SCHEMA_QUALIFIED_ENUM_QUERY,
"python-asyncpg",
)
.expect("codegen must succeed");
let enum_def = code.enum_def.expect("enum definition must be emitted");
assert!(
enum_def.contains("class PublicStatus(str, Enum):"),
"expected the sanitized type name:\n{enum_def}"
);
assert!(
!enum_def.contains("Public.status"),
"the unsanitized, unparseable form must not appear:\n{enum_def}"
);
}
const DEGENERATE_LABEL_SCHEMA: &str = "\
CREATE TYPE weird_status AS ENUM ('active', '!!!'); \
CREATE TABLE items (id INT PRIMARY KEY, status weird_status NOT NULL);";
const DEGENERATE_LABEL_QUERY: &str = "-- @name GetWeirdItem\n-- @returns :one\n\
SELECT id, status FROM items WHERE id = $1;";
#[test]
fn degenerate_label_does_not_collapse_to_an_empty_variant_in_rust() {
let code = generate(DEGENERATE_LABEL_SCHEMA, DEGENERATE_LABEL_QUERY, "rust-sqlx").expect("codegen must succeed");
let enum_def = code.enum_def.expect("enum definition must be emitted");
assert!(
enum_def.contains(" ___,"),
"expected the non-empty fallback variant:\n{enum_def}"
);
assert!(
!enum_def.contains(" ,\n"),
"an empty variant must not appear:\n{enum_def}"
);
}
#[test]
fn degenerate_label_does_not_collapse_to_an_empty_variant_in_go() {
let code = generate(DEGENERATE_LABEL_SCHEMA, DEGENERATE_LABEL_QUERY, "go-pgx").expect("codegen must succeed");
let enum_def = code.enum_def.expect("enum definition must be emitted");
assert!(
enum_def.contains("WeirdStatus___ WeirdStatus = \"!!!\""),
"expected the non-empty fallback variant:\n{enum_def}"
);
}
#[test]
fn degenerate_label_was_already_non_empty_under_screaming_snake_case() {
let code =
generate(DEGENERATE_LABEL_SCHEMA, DEGENERATE_LABEL_QUERY, "python-asyncpg").expect("codegen must succeed");
let enum_def = code.enum_def.expect("enum definition must be emitted");
assert!(
enum_def.contains(" ___ = \"!!!\""),
"expected the pre-existing non-empty member:\n{enum_def}"
);
}
const TWO_ENUMS_COLLIDE_SCHEMA: &str = "\
CREATE TYPE order_status AS ENUM ('open', 'closed'); \
CREATE TYPE order__status AS ENUM ('open', 'closed'); \
CREATE TABLE orders (id INT PRIMARY KEY, a order_status NOT NULL, b order__status NOT NULL);";
const TWO_ENUMS_COLLIDE_QUERY: &str = "-- @name GetOrder\n-- @returns :one\n\
SELECT id, a, b FROM orders WHERE id = $1;";
#[test]
fn two_enums_colliding_on_the_generated_name_is_rejected_not_silently_written_twice() {
let err = generate(TWO_ENUMS_COLLIDE_SCHEMA, TWO_ENUMS_COLLIDE_QUERY, "rust-sqlx")
.expect_err("must not silently succeed");
assert_eq!(err.code, ErrorCode::DuplicateAlias);
let message = err.to_string();
assert!(message.contains("order_status"), "{message}");
assert!(message.contains("order__status"), "{message}");
assert!(message.contains("OrderStatus"), "{message}");
}
#[test]
fn two_enums_colliding_on_the_generated_name_is_rejected_under_go_too() {
let err =
generate(TWO_ENUMS_COLLIDE_SCHEMA, TWO_ENUMS_COLLIDE_QUERY, "go-pgx").expect_err("must not silently succeed");
assert_eq!(err.code, ErrorCode::DuplicateAlias);
}
const ENUM_VS_QUERY_TYPE_SCHEMA: &str = "\
CREATE TYPE get_user_row AS ENUM ('a', 'b'); \
CREATE TABLE users (id INT PRIMARY KEY, status get_user_row NOT NULL);";
const ENUM_VS_QUERY_TYPE_QUERY: &str = "-- @name GetUser\n-- @returns :one\n\
SELECT id, status FROM users WHERE id = $1;";
#[test]
fn enum_colliding_with_the_query_row_type_is_rejected_not_silently_written_twice() {
let err = generate(ENUM_VS_QUERY_TYPE_SCHEMA, ENUM_VS_QUERY_TYPE_QUERY, "rust-sqlx")
.expect_err("must not silently succeed");
assert_eq!(err.code, ErrorCode::DuplicateAlias);
let message = err.to_string();
assert!(message.contains("GetUserRow"), "{message}");
}
const VARIANT_COLLISION_SCHEMA: &str = "\
CREATE TYPE model AS ENUM ('gpt-3.5-turbo', 'gpt_3_5_turbo'); \
CREATE TABLE items (id INT PRIMARY KEY, m model NOT NULL);";
const VARIANT_COLLISION_QUERY: &str = "-- @name GetItem\n-- @returns :one\n\
SELECT id, m FROM items WHERE id = $1;";
#[test]
fn enum_variant_collision_is_rejected_not_silently_written_twice_in_rust() {
let err = generate(VARIANT_COLLISION_SCHEMA, VARIANT_COLLISION_QUERY, "rust-sqlx")
.expect_err("must not silently succeed");
assert_eq!(err.code, ErrorCode::DuplicateAlias);
let message = err.to_string();
assert!(message.contains("gpt-3.5-turbo"), "{message}");
assert!(message.contains("gpt_3_5_turbo"), "{message}");
assert!(message.contains("Gpt35Turbo"), "{message}");
assert!(message.contains("model"), "{message}");
}
#[test]
fn enum_variant_collision_is_rejected_under_go_too() {
let err =
generate(VARIANT_COLLISION_SCHEMA, VARIANT_COLLISION_QUERY, "go-pgx").expect_err("must not silently succeed");
assert_eq!(err.code, ErrorCode::DuplicateAlias);
}
#[test]
fn distinct_enum_variants_are_not_rejected() {
generate(SCHEMA_QUALIFIED_ENUM_SCHEMA, SCHEMA_QUALIFIED_ENUM_QUERY, "rust-sqlx").expect("must not be rejected");
}