///|
/// An INSERT statement over one table.
pub struct Insert {
table_name : String
columns : Array[String]
rows : Array[Array[SqlValue]]
}
///|
/// Insert one row, writing every column the table's binding covers.
pub fn[C, R] insert(t : Table[C, R], row : R) -> Insert {
insert_many(t, [row])
}
///|
/// Insert several rows in a single statement.
///
/// Emitting one statement rather than N keeps the parameters in a single
/// ordered list, which is what a driver needs to bind them.
pub fn[C, R] insert_many(t : Table[C, R], rows : Array[R]) -> Insert {
{
table_name: t.table_name,
columns: t.write.columns,
rows: rows.map(r => (t.write.write)(r)),
}
}
///|
/// Insert every column except the named ones.
///
/// Typically used to let the database assign an auto-increment primary key.
pub fn[C, R] insert_except(
t : Table[C, R],
row : R,
omit~ : Array[String],
) -> Insert {
let binding = t.write.without(omit)
{
table_name: t.table_name,
columns: binding.columns,
rows: [(binding.write)(row)],
}
}
///|
pub fn Insert::to_sql(
self : Insert,
dialect? : Dialect = Dialect::Sqlite,
) -> (String, Array[SqlValue]) raise StatementError {
guard self.rows.length() > 0 else { raise EmptyInsert(table=self.table_name) }
let em = Emitter::new(dialect)
let buf = StringBuilder()
let cols = self.columns.map(c => "\"\{c}\"").join(", ")
buf.write_string("INSERT INTO \"\{self.table_name}\" (\{cols})")
let tuples = []
for row in self.rows {
guard row.length() == self.columns.length() else {
raise ArityMismatch(
table=self.table_name,
expected=self.columns.length(),
got=row.length(),
)
}
tuples.push("(" + row.map(v => em.param(v)).join(", ") + ")")
}
buf.write_string("\n VALUES " + tuples.join(", "))
(buf.to_string(), em.params)
}