use geopackage_core::ddl;
use geopackage_core::extensions::{Extension, ExtensionScope, ExtensionSupport};
use rusqlite::{Connection, OptionalExtension};
use crate::{GeoPackage, Layer, Result, TilePyramid, table_exists};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtensionRow {
pub table_name: Option<String>,
pub column_name: Option<String>,
pub name: String,
pub definition: String,
pub scope: ExtensionScope,
}
impl ExtensionRow {
pub fn extension(&self) -> Extension {
Extension::from_name(&self.name)
}
pub fn support(&self) -> ExtensionSupport {
self.extension().support()
}
}
const SELECT_ROWS: &str = "SELECT table_name, column_name, extension_name, definition, scope \
FROM gpkg_extensions";
fn read_rows(
conn: &Connection,
sql: &str,
params: &[&dyn rusqlite::ToSql],
) -> Result<Vec<ExtensionRow>> {
if !table_exists(conn, "gpkg_extensions")? {
return Ok(Vec::new());
}
let mut stmt = conn.prepare(sql)?;
let rows = stmt.query_map(params, |r| {
Ok(ExtensionRow {
table_name: r.get(0)?,
column_name: r.get(1)?,
name: r.get(2)?,
definition: r.get(3)?,
scope: ExtensionScope::parse(&r.get::<_, String>(4)?),
})
})?;
Ok(rows.collect::<rusqlite::Result<_>>()?)
}
pub(crate) fn read_all(conn: &Connection) -> Result<Vec<ExtensionRow>> {
read_rows(
conn,
&format!("{SELECT_ROWS} ORDER BY extension_name, table_name, column_name"),
&[],
)
}
impl GeoPackage {
pub fn extensions(&self) -> Result<Vec<ExtensionRow>> {
read_all(self.connection())
}
pub fn table_extensions(&self, table_name: &str) -> Result<Vec<ExtensionRow>> {
read_rows(
self.connection(),
&format!(
"{SELECT_ROWS} WHERE lower(table_name) = lower(?1) \
ORDER BY extension_name, column_name"
),
&[&table_name],
)
}
pub fn blocking_extension(&self, table_name: &str) -> Result<Option<ExtensionRow>> {
if self.allow_unsupported_extension_writes {
return Ok(None);
}
let rows = read_rows(
self.connection(),
&format!(
"{SELECT_ROWS} WHERE lower(table_name) = lower(?1) OR table_name IS NULL \
ORDER BY extension_name, column_name"
),
&[&table_name],
)?;
Ok(rows
.into_iter()
.find(|row| row.support() == ExtensionSupport::Unrecognised))
}
pub(crate) fn check_writable(&self, table_name: &str) -> Result<()> {
match self.blocking_extension(table_name)? {
None => Ok(()),
Some(row) => Err(crate::Error::UnsupportedExtension {
table_name: table_name.to_owned(),
extension_name: row.name,
scope: row.scope.as_str().to_owned(),
}),
}
}
}
impl Layer<'_> {
pub fn extensions(&self) -> Result<Vec<ExtensionRow>> {
self.gpkg().table_extensions(self.table_name())
}
}
impl TilePyramid<'_> {
pub fn extensions(&self) -> Result<Vec<ExtensionRow>> {
self.gpkg().table_extensions(self.table_name())
}
}
pub(crate) fn register(
conn: &Connection,
table: Option<&str>,
column: Option<&str>,
name: &str,
definition: &str,
scope: &str,
) -> Result<()> {
if !table_exists(conn, "gpkg_extensions")? {
conn.execute_batch(ddl::CREATE_GPKG_EXTENSIONS)?;
}
conn.execute(
"INSERT INTO gpkg_extensions \
(table_name, column_name, extension_name, definition, scope) \
VALUES (?1, ?2, ?3, ?4, ?5)",
rusqlite::params![table, column, name, definition, scope],
)?;
Ok(())
}
pub(crate) fn register_if_absent(
conn: &Connection,
table: Option<&str>,
column: Option<&str>,
name: &str,
definition: &str,
scope: &str,
) -> Result<bool> {
if table_exists(conn, "gpkg_extensions")?
&& conn
.query_row(
"SELECT 1 FROM gpkg_extensions \
WHERE table_name IS ?1 AND column_name IS ?2 AND extension_name = ?3",
rusqlite::params![table, column, name],
|_| Ok(()),
)
.optional()?
.is_some()
{
return Ok(false);
}
register(conn, table, column, name, definition, scope)?;
Ok(true)
}
pub(crate) fn is_registered(conn: &Connection, table: Option<&str>, name: &str) -> Result<bool> {
if !table_exists(conn, "gpkg_extensions")? {
return Ok(false);
}
Ok(conn
.query_row(
"SELECT 1 FROM gpkg_extensions WHERE extension_name = ?1 AND table_name IS ?2",
rusqlite::params![name, table],
|_| Ok(()),
)
.optional()?
.is_some())
}
pub(crate) fn unregister(conn: &Connection, table: &str, column: &str, name: &str) -> Result<()> {
if !table_exists(conn, "gpkg_extensions")? {
return Ok(());
}
conn.execute(
"DELETE FROM gpkg_extensions \
WHERE table_name = ?1 AND column_name = ?2 AND extension_name = ?3",
rusqlite::params![table, column, name],
)?;
Ok(())
}