use crate::gen::rust::shared_psql::*;
use crate::gen::rust::GeneratorSupplement;
use crate::gen::rust::RustCodeGenerator;
use crate::model::rust::PlainEnum;
use crate::model::rust::{DataEnum, Field};
use crate::model::sql::Sql;
use crate::model::sql::ToSql;
use crate::model::Definition;
use crate::model::Model;
use crate::model::Rust;
use crate::model::RustType;
use codegen::Block;
use codegen::Function;
use codegen::Impl;
use codegen::Scope;
const ERROR_TYPE: &str = "PsqlError";
const ROW_TYPE: &str = "PsqlRow";
const TRAIT_PSQL_REPRESENTABLE: &str = "PsqlRepresentable";
const TRAIT_PSQL_INSERTABLE: &str = "PsqlInsertable";
const TRAIT_PSQL_QUERYABLE: &str = "PsqlQueryable";
#[allow(clippy::module_name_repetitions)]
pub struct PsqlInserter;
impl GeneratorSupplement<Rust> for PsqlInserter {
fn add_imports(&self, scope: &mut Scope) {
scope.import("asn1rs::io::psql", &format!("Error as {}", ERROR_TYPE));
scope.import("asn1rs::io::psql", &format!("Row as {}", ROW_TYPE));
scope.import(
"asn1rs::io::psql",
&format!("Representable as {}", TRAIT_PSQL_REPRESENTABLE),
);
scope.import(
"asn1rs::io::psql",
&format!("Insertable as {}", TRAIT_PSQL_INSERTABLE),
);
scope.import(
"asn1rs::io::psql",
&format!("Queryable as {}", TRAIT_PSQL_QUERYABLE),
);
scope.import("asn1rs::io::psql", "Transaction");
}
fn impl_supplement(&self, scope: &mut Scope, definition: &Definition<Rust>) {
Self::impl_representable(scope, definition);
Self::impl_insertable(scope, definition);
Self::impl_queryable(scope, definition);
}
}
impl PsqlInserter {
fn new_representable_impl<'a>(scope: &'a mut Scope, name: &str) -> &'a mut Impl {
scope.new_impl(name).impl_trait(TRAIT_PSQL_REPRESENTABLE)
}
fn new_insertable_impl<'a>(scope: &'a mut Scope, name: &str) -> &'a mut Impl {
scope.new_impl(name).impl_trait(TRAIT_PSQL_INSERTABLE)
}
fn new_queryable_impl<'a>(scope: &'a mut Scope, name: &str) -> &'a mut Impl {
scope.new_impl(name).impl_trait(TRAIT_PSQL_QUERYABLE)
}
fn impl_representable(scope: &mut Scope, Definition(name, _rust): &Definition<Rust>) {
let implementation = Self::new_representable_impl(scope, name);
Self::impl_table_name(Self::new_table_name_fn(implementation), name);
}
fn impl_insertable(scope: &mut Scope, Definition(name, rust): &Definition<Rust>) {
let implementation = Self::new_insertable_impl(scope, name);
match rust {
Rust::Struct {
fields,
tag: _,
extension_after: _,
ordering: _,
} => {
Self::impl_struct_insert_statement(
Self::new_insert_statement_fn(implementation),
name,
fields,
);
Self::impl_struct_insert_fn(
Self::new_insert_fn(implementation, true),
name,
fields.iter().map(Field::fallback_representation),
);
}
Rust::DataEnum(enumeration) => {
Self::impl_data_enum_insert_statement(
Self::new_insert_statement_fn(implementation),
name,
enumeration,
);
Self::impl_data_enum_insert_fn(
Self::new_insert_fn(implementation, true),
name,
enumeration,
);
}
Rust::Enum(_) => {
Self::impl_enum_insert_statement(Self::new_insert_statement_fn(implementation));
Self::impl_enum_insert_fn(Self::new_insert_fn(implementation, false));
}
Rust::TupleStruct { r#type: rust, .. } => {
Self::impl_tuple_insert_statement(
Self::new_insert_statement_fn(implementation),
name,
);
Self::impl_tuple_insert_fn(Self::new_insert_fn(implementation, true), name, rust);
}
}
}
fn new_table_name_fn(implementation: &mut Impl) -> &mut Function {
implementation
.new_fn("table_name")
.arg_ref_self()
.ret("&'static str")
}
fn new_insert_statement_fn(implementation: &mut Impl) -> &mut Function {
implementation
.new_fn("insert_statement")
.arg_ref_self()
.ret("&'static str")
}
fn new_insert_fn(implementation: &mut Impl, using_transaction: bool) -> &mut Function {
implementation
.new_fn("insert_with")
.arg_ref_self()
.arg(
if using_transaction {
"transaction"
} else {
"_"
},
"&mut Transaction",
)
.ret(&format!("Result<i32, {}>", ERROR_TYPE))
}
fn impl_table_name(function: &mut Function, name: &str) {
function.line(&format!("\"{}\"", name));
}
fn impl_struct_insert_statement(function: &mut Function, name: &str, fields: &[Field]) {
if fields.is_empty() {
Self::impl_tuple_insert_statement(function, name);
} else {
function.line(&format!("\"{}\"", struct_insert_statement(name, fields)));
}
}
fn impl_data_enum_insert_statement(
function: &mut Function,
name: &str,
enumeration: &DataEnum,
) {
if enumeration.is_empty() {
Self::impl_tuple_insert_statement(function, name);
} else {
function.line(&format!(
"\"{}\"",
data_enum_insert_statement(name, enumeration)
));
}
}
fn impl_enum_insert_statement(function: &mut Function) {
function.line("\"\"");
}
fn impl_tuple_insert_statement(function: &mut Function, name: &str) {
function.line(&format!("\"{}\"", tuple_struct_insert_statement(name)));
}
fn impl_struct_insert_fn<'a>(
function: &mut Function,
struct_name: &str,
fields: impl ExactSizeIterator<Item = &'a (String, RustType)>,
) {
let mut variables = Vec::with_capacity(fields.len());
let mut vecs = Vec::new();
for (name, rust) in fields {
let name = RustCodeGenerator::rust_field_name(name, true);
let sql_primitive = Model::<Sql>::is_primitive(rust);
let is_vec = rust.is_vec();
if is_vec {
vecs.push((name.clone(), rust.clone()));
continue;
} else {
variables.push(format!("&{}", name));
}
if sql_primitive {
function.line(&format!(
"let {} = {}self.{};",
name,
if rust.is_primitive() { "" } else { "&" },
name,
));
if let Some(wrap) = Self::wrap_for_insert_in_as_or_from_if_required(&name, rust) {
function.line(format!("let {} = {};", name, wrap,));
}
} else if !is_vec {
function.line(&format!(
"let {} = {}self.{}{}.insert_with(transaction)?{};",
name,
if let RustType::Option(_) = rust {
"if let Some(ref value) = "
} else {
""
},
name,
if let RustType::Option(_) = rust {
" { Some(value"
} else {
""
},
if let RustType::Option(_) = rust {
") } else { None }"
} else {
""
}
));
};
}
function.line("let statement = transaction.prepare(self.insert_statement())?;");
function.line(format!(
"let result = transaction.query(&statement, &[{}])?;",
variables.join(", ")
));
if vecs.is_empty() {
function.line(&format!(
"{}::expect_returned_index_in_rows(&result)",
ERROR_TYPE
));
} else {
function.line(&format!(
"let index = {}::expect_returned_index_in_rows(&result)?;",
ERROR_TYPE
));
for (name, field) in vecs {
let mut block = Block::new("");
block.line(&format!(
"let statement = transaction.prepare(\"{}\")?;",
&struct_list_entry_insert_statement(struct_name, &name),
));
block.push_block(Self::list_insert_for_each(&name, &field, "index"));
function.push_block(block);
}
function.line("Ok(index)");
}
}
fn wrap_for_insert_in_as_or_from_if_required(name: &str, rust: &RustType) -> Option<String> {
let inner_sql = rust.as_inner_type().to_sql();
let inner_rust = rust.as_inner_type();
if inner_sql.to_rust().as_inner_type().similar(&inner_rust) {
None
} else {
Some({
let rust_from_sql = inner_sql.to_rust().into_inner_type();
let as_target = rust_from_sql.to_string();
let use_from_instead_of_as =
rust_from_sql.is_primitive() && rust_from_sql > *inner_rust;
if let RustType::Option(_) = rust {
if use_from_instead_of_as {
format!("{}.map({}::from)", name, as_target)
} else {
format!("{}.map(|v| v as {})", name, as_target)
}
} else if use_from_instead_of_as {
format!("{}::from({})", as_target, name)
} else {
format!("{} as {}", name, as_target)
}
})
}
}
fn wrap_for_query_in_as_or_from_if_required(name: &str, rust: &RustType) -> Option<String> {
let inner_sql = rust.as_inner_type().to_sql();
let inner_rust = rust.as_inner_type();
if inner_sql.to_rust().as_inner_type().similar(&inner_rust) {
None
} else {
Some({
let as_target = inner_rust.to_string();
if let RustType::Option(_) = rust {
format!("{}.map(|v| v as {})", name, as_target)
} else {
format!("{} as {}", name, as_target)
}
})
}
}
fn impl_data_enum_insert_fn(function: &mut Function, name: &str, enumeration: &DataEnum) {
let mut variables = Vec::with_capacity(enumeration.len());
for variant in enumeration.variants() {
let variable = RustCodeGenerator::rust_field_name(
&RustCodeGenerator::rust_module_name(variant.name()),
true,
);
let sql_primitive = Model::<Sql>::is_primitive(variant.r#type());
variables.push(format!("&{}", variable));
let mut block_if = Block::new(&format!(
"let {} = if let {}::{}(value) = self",
variable,
name,
variant.name()
));
if sql_primitive {
block_if.line(format!(
"Some({})",
Self::wrap_for_insert_in_as_or_from_if_required("*value", variant.r#type())
.unwrap_or_else(|| "value".to_string())
));
} else {
block_if.line("Some(value.insert_with(transaction)?)");
};
block_if.after(" else { None };");
function.push_block(block_if);
}
function.line("let statement = transaction.prepare(self.insert_statement())?;");
function.line(format!(
"let result = transaction.query(&statement, &[{}])?;",
variables.join(", ")
));
function.line(&format!(
"{}::expect_returned_index_in_rows(&result)",
ERROR_TYPE
));
}
fn impl_enum_insert_fn(function: &mut Function) {
function.line("Ok(self.value_index() as i32)");
}
fn impl_tuple_insert_fn(function: &mut Function, name: &str, rust: &RustType) {
function.line("let statement = transaction.prepare(self.insert_statement())?;");
function.line("let result = transaction.query(&statement, &[])?;");
function.line(&format!(
"let list = {}::expect_returned_index_in_rows(&result)?;",
ERROR_TYPE
));
function.line(format!(
"let statement = transaction.prepare(\"{}\")?;",
list_entry_insert_statement(name)
));
function.push_block(Self::list_insert_for_each("0", rust, "list"));
function.line("Ok(list)");
}
fn list_insert_for_each(name: &str, rust: &RustType, list: &str) -> Block {
let mut block_for = if rust.as_no_option().is_vec() {
Block::new(&if let RustType::Option(_) = rust {
format!("for value in self.{}.iter().flatten()", name)
} else {
format!("for value in &self.{}", name)
})
} else if rust.is_option() {
Block::new(&format!("if let Some(value) = &self.{}", name))
} else {
let mut block = Block::new("");
block.line(format!("let value = &self.{};", name));
block
};
if Model::<Sql>::is_primitive(rust) {
let inner_sql = rust.as_inner_type().to_sql();
let inner_rust = rust.as_inner_type();
if !inner_sql.to_rust().as_inner_type().similar(&inner_rust) {
let rust_from_sql = inner_sql.to_rust().into_inner_type();
let as_target = rust_from_sql.to_string();
let use_from_instead_of_as =
rust_from_sql.is_primitive() && rust_from_sql > *inner_rust;
block_for.line(format!(
"let value = {};",
if use_from_instead_of_as {
format!("{}::from(*value)", as_target)
} else {
format!("(*value) as {}", as_target)
},
));
}
} else {
block_for.line("let value = value.insert_with(transaction)?;");
}
block_for.line(format!(
"transaction.execute(&statement, &[&{}, &value])?;",
list
));
block_for
}
fn impl_queryable(scope: &mut Scope, Definition(name, rust): &Definition<Rust>) {
let implementation = Self::new_queryable_impl(scope, name);
match rust {
Rust::Struct {
fields,
tag: _,
extension_after: _,
ordering: _,
} => {
Self::impl_query_statement(Self::new_query_statement_fn(implementation), name);
Self::impl_struct_query_fn(Self::new_query_fn(implementation, true), name);
Self::impl_struct_load_fn(
Self::new_load_fn(
implementation,
fields.iter().any(|field| {
!Model::<Sql>::is_primitive(field.r#type()) || field.r#type().is_vec()
}),
),
name,
fields.iter().map(Field::fallback_representation),
);
}
Rust::DataEnum(enumeration) => {
Self::impl_query_statement(Self::new_query_statement_fn(implementation), name);
Self::impl_data_enum_query_fn(Self::new_query_fn(implementation, true), name);
Self::impl_data_enum_load_fn(
Self::new_load_fn(implementation, true),
name,
enumeration,
);
}
Rust::Enum(r_enum) => {
Self::impl_empty_query_statement(Self::new_query_statement_fn(implementation));
Self::impl_enum_query_fn(Self::new_query_fn(implementation, false), name, r_enum);
Self::impl_enum_load_fn(Self::new_load_fn(implementation, true), name);
}
Rust::TupleStruct { r#type: rust, .. } => {
Self::impl_tupl_query_statement(
Self::new_query_statement_fn(implementation),
name,
rust,
);
Self::impl_tupl_struct_query_fn(
Self::new_query_fn(implementation, true),
name,
rust,
);
Self::impl_tupl_struct_load_fn(Self::new_load_fn(implementation, true), name);
}
}
}
fn impl_query_statement(func: &mut Function, name: &str) {
func.line(&format!("\"{}\"", select_statement_single(name)));
}
fn impl_empty_query_statement(func: &mut Function) {
func.line("\"\"");
}
fn impl_tupl_query_statement(func: &mut Function, name: &str, inner: &RustType) {
func.line(&format!("\"{}\"", list_entry_query_statement(name, inner)));
}
fn impl_struct_query_fn(func: &mut Function, name: &str) {
func.line("let statement = transaction.prepare(Self::query_statement())?;");
func.line("let rows = transaction.query(&statement, &[&id])?;");
func.line(&format!(
"{}::load_from(transaction, rows.get(0).ok_or_else({}::no_result)?)",
name, ERROR_TYPE,
));
}
fn impl_struct_load_fn<'a>(
func: &mut Function,
struct_name: &str,
variants: impl ExactSizeIterator<Item = &'a (String, RustType)>,
) {
let mut block = Block::new(&format!("Ok({}", struct_name));
let mut index_negative_offset = 0;
for (index, (name, rust)) in variants.enumerate() {
let index = index - index_negative_offset;
if Model::<Sql>::has_no_column_in_embedded_struct(rust) {
index_negative_offset += 1;
}
if rust.is_vec() {
let mut load_block = Block::new(&format!(
"{}:",
RustCodeGenerator::rust_field_name(name, true)
));
load_block.line("let mut vec = Vec::default();");
load_block.line(&format!(
"let rows = transaction.query(\"{}\", &[&{}::expect_returned_index(&row)?])?;",
if let RustType::Complex(complex, _tag) = rust.as_inner_type() {
struct_list_entry_select_referenced_value_statement(
struct_name,
name,
complex,
)
} else {
struct_list_entry_select_value_statement(struct_name, name)
},
ERROR_TYPE
));
let mut rows_foreach = Block::new("for row in rows.iter()");
if let RustType::Complex(complex, _tag) = rust.as_inner_type() {
rows_foreach.line(&format!(
"vec.push({}::load_from(transaction, &row)?);",
complex
));
} else {
let rust = rust.as_inner_type();
let sql = rust.to_sql();
rows_foreach.line(&format!(
"let value = {}::value_at_column::<{}>(&row, 0)?;",
ERROR_TYPE,
sql.to_rust().to_string(),
));
if *rust < sql.to_rust() {
rows_foreach.line(&format!("let value = value as {};", rust.to_string()));
}
rows_foreach.line("vec.push(value);");
}
load_block.push_block(rows_foreach);
if let RustType::Option(_) = rust {
load_block.line("if vec.is_empty() { None } else { Some(vec) }");
} else {
load_block.line("vec");
}
load_block.after(",");
block.push_block(load_block);
} else if Model::<Sql>::is_primitive(rust) {
let load = format!(
"{}::value_at_column::<{}>(&row, {})?",
ERROR_TYPE,
if rust.to_sql().to_rust().similar(&rust) {
rust.to_string()
} else {
rust.to_sql().to_rust().to_string()
},
index + 1,
);
block.line(&format!(
"{}: {},",
RustCodeGenerator::rust_field_name(name, true),
Self::wrap_for_query_in_as_or_from_if_required(&load, rust,).unwrap_or(load)
));
} else {
let inner = rust.as_inner_type();
let load = if let RustType::Option(_) = rust {
format!(
"{}::value_at_column::<Option<i32>>(&row, {})?\
.map(|id| {}::query_with(transaction, id)).transpose()?",
ERROR_TYPE,
index + 1,
inner.to_string(),
)
} else {
format!(
"{}::query_with(transaction, {}::value_at_column::<i32>(&row, {})?)?",
inner.to_string(),
ERROR_TYPE,
index + 1,
)
};
block.line(&format!(
"{}: {},",
RustCodeGenerator::rust_field_name(name, true),
load
));
}
}
block.after(")");
func.push_block(block);
}
fn impl_data_enum_query_fn(func: &mut Function, name: &str) {
func.line("let statement = transaction.prepare(Self::query_statement())?;");
func.line("let rows = transaction.query(&statement, &[&id])?;");
func.line(&format!(
"{}::load_from(transaction, rows.get(0).ok_or_else({}::no_result)?)",
name, ERROR_TYPE
));
}
fn impl_data_enum_load_fn(func: &mut Function, name: &str, enumeration: &DataEnum) {
func.line(format!(
"let index = {}::first_present(row, &[{}])?;",
ERROR_TYPE,
enumeration
.variants()
.enumerate()
.map(|e| format!("{}", e.0 + 1))
.collect::<Vec<String>>()
.join(", ")
));
let mut block = Block::new("match index");
for (index, variant) in enumeration.variants().enumerate() {
let mut block_case = Block::new(&format!(
"{} => Ok({}::{}(",
index + 1,
name,
variant.name()
));
if Model::<Sql>::is_primitive(variant.r#type().as_inner_type()) {
if let Some(wrap) =
Self::wrap_for_query_in_as_or_from_if_required("", variant.r#type())
{
block_case.line(format!(
"{}::value_at_column::<{}>(&row, {})?{}",
ERROR_TYPE,
variant
.r#type()
.as_inner_type()
.to_sql()
.to_rust()
.to_string(),
index + 1,
wrap
));
} else {
block_case.line(format!(
"{}::value_at_column::<{}>(&row, {})?",
ERROR_TYPE,
variant
.r#type()
.as_inner_type()
.to_sql()
.to_rust()
.to_string(),
index + 1
));
}
} else {
block_case.line(&format!(
"{}::query_with(transaction, row.get({}))?",
variant.r#type().clone().as_inner_type().to_string(),
index + 1
));
}
block_case.after(")),");
block.push_block(block_case);
}
block.line(&format!("_ => Err({}::no_result()),", ERROR_TYPE));
func.push_block(block);
}
fn impl_enum_query_fn(func: &mut Function, name: &str, r_enum: &PlainEnum) {
let mut block = Block::new("match id");
for (index, variant) in r_enum.variants().enumerate() {
block.line(&format!("{} => Ok({}::{}),", index, name, variant));
}
block.line(&format!("_ => Err({}::no_result()),", ERROR_TYPE));
func.push_block(block);
}
fn impl_enum_load_fn(func: &mut Function, name: &str) {
func.line(&format!(
"{}::query_with(transaction, {}::expect_returned_index(&row)?)",
name, ERROR_TYPE,
));
}
fn impl_tupl_struct_query_fn(func: &mut Function, name: &str, rust: &RustType) {
func.line("let statement = transaction.prepare(Self::query_statement())?;");
func.line("let rows = transaction.query(&statement, &[&id])?;");
let inner = rust.as_inner_type();
if Model::<Sql>::is_primitive(inner) {
let from_sql = inner.to_sql().to_rust();
let load = format!(
"{}::value_at_column::<{}>(&row, 0)?",
ERROR_TYPE,
from_sql.to_string(),
);
let load_wrapped =
Self::wrap_for_query_in_as_or_from_if_required(&load, rust).unwrap_or(load);
if rust.is_vec() {
func.line("let mut values = Vec::with_capacity(rows.len());");
let mut block = Block::new("for row in rows.iter()");
block.line(&format!("values.push({});", load_wrapped));
func.push_block(block);
} else {
func.line(format!(
"let row = rows.get(0).ok_or_else({}::no_result)?;",
ERROR_TYPE
));
func.line(format!("let values = {};", load_wrapped));
}
} else if rust.is_vec() {
func.line("let mut values = Vec::with_capacity(rows.len());");
let mut block = Block::new("for row in rows.iter()");
block.line(&format!(
"values.push({}::load_from(transaction, &row)?);",
inner.to_string()
));
func.push_block(block);
} else {
func.line(format!(
"let row = rows.get(0).ok_or_else({}::no_result)?;",
ERROR_TYPE
));
func.line(format!(
"let values = {}::load_from(transaction, &row)?;",
inner.to_string()
));
}
func.line(&format!("Ok({}(values))", name));
}
fn impl_tupl_struct_load_fn(func: &mut Function, name: &str) {
func.line(&format!(
"{}::query_with(transaction, {}::expect_returned_index(&row)?)",
name, ERROR_TYPE,
));
}
fn new_query_statement_fn(implementation: &mut Impl) -> &mut Function {
implementation.new_fn("query_statement").ret("&'static str")
}
fn new_query_fn(implementation: &mut Impl, using_transaction: bool) -> &mut Function {
implementation
.new_fn("query_with")
.arg(
if using_transaction {
"transaction"
} else {
"_"
},
"&mut Transaction",
)
.arg("id", "i32")
.ret(&format!("Result<Self, {}>", ERROR_TYPE))
}
fn new_load_fn(implementation: &mut Impl, using_transaction: bool) -> &mut Function {
implementation
.new_fn("load_from")
.arg(
if using_transaction {
"transaction"
} else {
"_"
},
"&mut Transaction",
)
.arg("row", &format!("&{}", ROW_TYPE))
.ret(&format!("Result<Self, {}>", ERROR_TYPE))
}
}