mod on_conflict;
mod values;
use std::fmt::{self, Display, Formatter};
use crate::postgres::general::{Column, Expression, OutputExpression, TableName, WithClause};
use crate::tools::{joined, IntoIteratorOfSameType, IntoNonZeroArray};
pub use on_conflict::{OnConflictClause, OnConflictClauseBuilder};
pub use values::{DefaultValues, Values, WithColumns, WithoutColumns};
pub fn insert_into(table_name: impl Into<TableName>) -> BareInsertInto {
BareInsertInto {
table_name: table_name.into(),
with: None,
}
}
pub(crate) fn insert_into_with(table_name: TableName, with: WithClause) -> BareInsertInto {
BareInsertInto {
table_name,
with: Some(with),
}
}
#[must_use = "Making a bare INSERT INTO statement is pointless"]
#[derive(Debug)]
pub struct BareInsertInto {
table_name: TableName,
with: Option<WithClause>,
}
impl BareInsertInto {
pub fn default_values(self) -> InsertInto<DefaultValues> {
InsertInto::new(self.table_name, DefaultValues, self.with)
}
pub fn values<T: IntoNonZeroArray<Expression, N>, const N: usize>(
self,
values: impl IntoIterator<Item = T>,
) -> InsertInto<WithoutColumns<N>> {
let values = values
.into_iter()
.map(IntoNonZeroArray::into_non_zero_array)
.collect();
InsertInto::new(self.table_name, WithoutColumns::new(values), self.with)
}
pub fn columns<const N: usize>(
self,
columns: impl IntoNonZeroArray<Column, N>,
) -> InsertIntoColumnsBuilder<N> {
InsertIntoColumnsBuilder {
table_name: self.table_name,
with: self.with,
columns: columns.into_non_zero_array(),
}
}
}
#[must_use = "Making a bare INSERT INTO statement with columns is pointless"]
#[derive(Debug)]
pub struct InsertIntoColumnsBuilder<const N: usize> {
table_name: TableName,
with: Option<WithClause>,
columns: [Column; N],
}
impl<const N: usize> InsertIntoColumnsBuilder<N> {
pub fn values<T: IntoNonZeroArray<Expression, N>>(
self,
values: impl IntoIterator<Item = T>,
) -> InsertInto<WithColumns<N>> {
let values = values
.into_iter()
.map(IntoNonZeroArray::into_non_zero_array)
.collect();
InsertInto::new(
self.table_name,
WithColumns::new(self.columns, values),
self.with,
)
}
}
#[must_use = "Making an INSERT INTO statement without using it is pointless"]
#[derive(Debug, Clone)]
pub struct InsertInto<V: Values> {
table_name: TableName,
with: Option<WithClause>,
values: V,
returning: Vec<OutputExpression>,
on_conflict: Option<OnConflictClause>,
}
impl<V: Values> InsertInto<V> {
fn new(table_name: TableName, values: V, with: Option<WithClause>) -> InsertInto<V> {
InsertInto {
table_name,
with,
values,
on_conflict: None,
returning: Vec::new(),
}
}
pub fn returning(mut self, expressions: impl IntoIteratorOfSameType<OutputExpression>) -> Self {
self.returning.extend(expressions.into_some_iter());
self
}
pub fn on_conflict(self) -> OnConflictClauseBuilder<V> {
OnConflictClauseBuilder::new(self)
}
}
impl<const N: usize> InsertInto<WithColumns<N>> {
pub fn values<T: IntoNonZeroArray<Expression, N>>(
mut self,
new_values: impl IntoIterator<Item = T>,
) -> Self {
self.values.add(new_values);
self
}
}
impl<const N: usize> InsertInto<WithoutColumns<N>> {
pub fn values<T: IntoNonZeroArray<Expression, N>>(
mut self,
new_values: impl IntoIterator<Item = T>,
) -> Self {
self.values.add(new_values);
self
}
}
impl<V: Values> Display for InsertInto<V> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(with_clause) = &self.with {
write!(f, "{} ", with_clause)?;
}
write!(f, "INSERT INTO {} {}", self.table_name, self.values)?;
if !self.returning.is_empty() {
write!(f, " RETURNING {}", joined(&self.returning, ", "))?;
}
if let Some(on_conflict_clause) = &self.on_conflict {
write!(f, " {}", on_conflict_clause)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::postgres::tools::tests::assert_correct_postgresql;
use crate::postgres::{insert_into, select, with, Parameters};
#[test]
fn default_values() {
let sql = insert_into("Dummy").default_values().to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy DEFAULT VALUES");
}
#[test]
fn no_columns() {
let sql = insert_into("Dummy").values(["a"]).to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy VALUES (a)");
}
#[test]
fn no_columns_multiple_values() {
let sql = insert_into("Dummy")
.values([("a", "b"), ("c", "d")])
.values([("e", "f")])
.to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy VALUES (a, b), (c, d), (e, f)");
}
#[test]
#[should_panic]
fn zero_length_columns() {
let values: [[String; 0]; 1] = [[]];
let sql = insert_into("Dummy").columns([]).values(values).to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy () VALUES ");
}
#[test]
fn single_column() {
let sql = insert_into("Dummy")
.columns("col1")
.values(["a"])
.to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy (col1) VALUES (a)");
}
#[test]
fn multiple_columns() {
let sql = insert_into("Dummy")
.columns(("col1", "col2"))
.values([("a", "b")])
.to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy (col1, col2) VALUES (a, b)");
}
#[test]
fn many_values() {
let sql = insert_into("Dummy")
.columns(("col1", "col2"))
.values([("a", "b"), ("c", "d")])
.values([("e", "f")])
.to_string();
assert_correct_postgresql(
&sql,
"INSERT INTO Dummy (col1, col2) VALUES (a, b), (c, d), (e, f)",
);
}
#[test]
fn returning() {
let sql = insert_into("Dummy")
.columns("col1")
.values(["a"])
.returning("id")
.to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy (col1) VALUES (a) RETURNING id");
}
#[test]
fn returning_two() {
let sql = insert_into("Dummy")
.columns("col1")
.values(["a"])
.returning(("id", "place"))
.to_string();
assert_correct_postgresql(
&sql,
"INSERT INTO Dummy (col1) VALUES (a) RETURNING id, place",
);
}
#[test]
fn cte() {
let sql = with("thing")
.as_(select("1 + 1"))
.insert_into("Dummy")
.values(["a"])
.to_string();
assert_correct_postgresql(
&sql,
"WITH thing AS (SELECT 1 + 1) INSERT INTO Dummy VALUES (a)",
);
}
#[test]
fn array_params_with_columns() {
let mut params = Parameters::new();
let sql = insert_into("Dummy")
.columns(("col1", "col2"))
.values([params.next_array()])
.to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy (col1, col2) VALUES ($1, $2)");
}
#[test]
fn array_params_without_columns() {
let mut params = Parameters::new();
let sql = insert_into("Dummy")
.values([params.next_array::<2>()])
.to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy VALUES ($1, $2)");
}
#[test]
fn on_conflict_do_nothing() {
let sql = insert_into("Dummy")
.values(["a"])
.on_conflict()
.do_nothing()
.to_string();
assert_correct_postgresql(&sql, "INSERT INTO Dummy VALUES (a) ON CONFLICT DO NOTHING");
}
#[test]
fn on_conflict_do_update_set() {
let sql = insert_into("Dummy")
.values(["a"])
.on_conflict()
.do_update_set([("col", "1")])
.to_string();
assert_correct_postgresql(
&sql,
"INSERT INTO Dummy VALUES (a) ON CONFLICT DO UPDATE SET col = 1",
);
}
}