use crate::{Conn, Error};
pub(crate) fn ident(name: &str) -> String {
format!("\"{}\"", name.replace('"', "\"\""))
}
pub(crate) fn literal(text: &str) -> String {
format!("'{}'", text.replace('\'', "''"))
}
#[cfg(feature = "postgres")]
mod imp {
use super::*;
pub(crate) const NAME: &str = "postgres";
pub(crate) fn not_older(incoming: &str, stored: &str) -> String {
format!("{incoming} >= {stored}")
}
pub(crate) const MAX_BINDS: usize = 65_535;
pub(crate) async fn columns(
conn: &mut Conn,
schema: &str,
table: &str,
) -> Result<Vec<(String, bool, bool)>, Error> {
Ok(sqlx::query_as(
"SELECT a.attname::text, NOT a.attnotnull, a.attgenerated <> '' \
FROM pg_attribute a \
JOIN pg_class c ON c.oid = a.attrelid \
JOIN pg_namespace n ON n.oid = c.relnamespace \
WHERE n.nspname = $1 AND c.relname = $2 AND a.attnum > 0 AND NOT a.attisdropped",
)
.bind(schema)
.bind(table)
.fetch_all(&mut *conn)
.await?)
}
pub(crate) async fn primary_key(
conn: &mut Conn,
schema: &str,
table: &str,
) -> Result<Vec<String>, Error> {
Ok(sqlx::query_scalar(
"SELECT a.attname::text \
FROM pg_index i \
JOIN pg_class c ON c.oid = i.indrelid \
JOIN pg_namespace n ON n.oid = c.relnamespace \
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY (i.indkey) \
WHERE i.indisprimary AND n.nspname = $1 AND c.relname = $2 \
ORDER BY array_position(i.indkey::int2[], a.attnum)",
)
.bind(schema)
.bind(table)
.fetch_all(&mut *conn)
.await?)
}
}
#[cfg(feature = "sqlite")]
mod imp {
use super::*;
pub(crate) const NAME: &str = "sqlite";
pub(crate) const NOW: &str = "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
pub(crate) async fn pointing_from(
conn: &mut Conn,
table: &str,
) -> Result<Vec<(String, Vec<String>, Vec<String>)>, Error> {
let rows: Vec<(i64, String, String, Option<String>)> = sqlx::query_as(
r#"SELECT f.id, f."table", f."from", f."to"
FROM pragma_foreign_key_list(?1) f ORDER BY f.id, f.seq"#,
)
.bind(table)
.fetch_all(&mut *conn)
.await?;
let mut out: Vec<(i64, String, Vec<String>, Vec<String>)> = Vec::new();
for (id, parent, from, to) in rows {
if out.last().is_none_or(|(last, ..)| *last != id) {
out.push((id, parent, Vec::new(), Vec::new()));
}
let (.., from_columns, to_columns) = out.last_mut().expect("just pushed");
from_columns.push(from);
to_columns.extend(to);
}
let mut keys = Vec::with_capacity(out.len());
for (_, parent, from, to) in out {
if to.len() == from.len() {
keys.push((parent, from, to));
continue;
}
let primary: Vec<String> = sqlx::query_scalar(
"SELECT name FROM pragma_table_info(?1) WHERE pk > 0 ORDER BY pk",
)
.bind(&parent)
.fetch_all(&mut *conn)
.await?;
keys.push((parent, from, primary));
}
Ok(keys)
}
pub(crate) async fn pointing(
conn: &mut Conn,
table: &str,
key: &[&str],
) -> Result<Vec<Pointer>, Error> {
let rows: Vec<(String, i64, String, Option<String>)> = sqlx::query_as(
r#"SELECT m.name, f.id, f."from", f."to"
FROM sqlite_master m JOIN pragma_foreign_key_list(m.name) f
WHERE m.type = 'table' AND f."table" = ?1 COLLATE NOCASE
ORDER BY m.name, f.id, f.seq"#,
)
.bind(table)
.fetch_all(&mut *conn)
.await?;
let mut out: Vec<Pointer> = Vec::new();
for (child, id, from, to) in rows {
if out.last().is_none_or(|p| p.child != child || p.id != id) {
out.push(Pointer {
child,
id,
from: Vec::new(),
to: Vec::new(),
});
}
let pointer = out.last_mut().expect("just pushed");
pointer.from.push(from);
pointer.to.extend(to);
}
for pointer in &mut out {
if pointer.to.len() != pointer.from.len() {
pointer.to = key.iter().map(|k| (*k).to_owned()).collect();
}
}
Ok(out)
}
pub(crate) struct Pointer {
pub(crate) child: String,
pub(crate) id: i64,
pub(crate) from: Vec<String>,
pub(crate) to: Vec<String>,
}
pub(crate) fn points(table: &str, to: &[String], from: &str, columns: &[String]) -> String {
to.iter()
.zip(columns)
.map(|(t, f)| format!("{table}.{} = +{from}.{}", ident(t), ident(f)))
.collect::<Vec<_>>()
.join(" AND ")
}
pub(crate) async fn has_rowid(conn: &mut Conn, table: &str) -> Result<bool, Error> {
let without: Option<i64> =
sqlx::query_scalar("SELECT wr FROM pragma_table_list WHERE name = ?1")
.bind(table)
.fetch_optional(&mut *conn)
.await?;
Ok(without != Some(1))
}
pub(crate) async fn unique_indexes(
conn: &mut Conn,
table: &str,
) -> Result<Vec<(String, Option<String>)>, Error> {
Ok(sqlx::query_as(
r#"SELECT i.name, m.sql FROM pragma_index_list(?1) i
LEFT JOIN sqlite_master m ON m.type = 'index' AND m.name = i.name
WHERE i."unique" = 1 AND i.origin <> 'pk' ORDER BY i.name"#,
)
.bind(table)
.fetch_all(&mut *conn)
.await?)
}
pub(crate) const MAX_BINDS: usize = 32_766;
pub(crate) async fn columns(
conn: &mut Conn,
_schema: &str,
table: &str,
) -> Result<Vec<(String, bool, bool)>, Error> {
Ok(sqlx::query_as(
r#"SELECT name, "notnull" = 0, hidden IN (2, 3) FROM pragma_table_xinfo(?) WHERE hidden <> 1"#,
)
.bind(table)
.fetch_all(&mut *conn)
.await?)
}
pub(crate) async fn primary_key(
conn: &mut Conn,
_schema: &str,
table: &str,
) -> Result<Vec<String>, Error> {
Ok(
sqlx::query_scalar("SELECT name FROM pragma_table_info(?) WHERE pk > 0 ORDER BY pk")
.bind(table)
.fetch_all(&mut *conn)
.await?,
)
}
}
pub(crate) use imp::*;
#[cfg(feature = "postgres")]
#[doc(hidden)]
#[macro_export]
macro_rules! __qualified {
($schema:literal, $table:literal) => {
concat!($schema, ".", $table)
};
}
#[cfg(feature = "sqlite")]
#[doc(hidden)]
#[macro_export]
macro_rules! __qualified {
($schema:literal, $table:literal) => {
$table
};
}