fix(sql macro): permit code to compile
This commit is contained in:
@@ -3,10 +3,18 @@
|
|||||||
//! RFC 4918 §4.2 defines "dead properties" as those stored verbatim by the
|
//! RFC 4918 §4.2 defines "dead properties" as those stored verbatim by the
|
||||||
//! server without interpreting their value. Properties are persisted to
|
//! server without interpreting their value. Properties are persisted to
|
||||||
//! `storage.webdav_dead_properties` and survive server restarts.
|
//! `storage.webdav_dead_properties` and survive server restarts.
|
||||||
|
//!
|
||||||
|
//! Queries here use `sqlx::query()` (runtime-bound) rather than the
|
||||||
|
//! compile-time-checked `sqlx::query!()` macro. The macro would require either
|
||||||
|
//! a live DB at compile time OR committed `.sqlx/` offline metadata; the rest
|
||||||
|
//! of this codebase consistently uses the runtime variant (see
|
||||||
|
//! `user_pg_repository.rs` for the canonical style), so a fresh checkout
|
||||||
|
//! compiles without any DB connection. Trading the macro's compile-time column
|
||||||
|
//! check for that bootstrap-friendliness is the project's standing convention.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use sqlx::PgPool;
|
use sqlx::{PgPool, Row};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::application::adapters::webdav_adapter::QualifiedName;
|
use crate::application::adapters::webdav_adapter::QualifiedName;
|
||||||
@@ -29,7 +37,7 @@ impl DeadPropertyStore {
|
|||||||
name: QualifiedName,
|
name: QualifiedName,
|
||||||
value: Option<String>,
|
value: Option<String>,
|
||||||
) -> Result<(), DomainError> {
|
) -> Result<(), DomainError> {
|
||||||
sqlx::query!(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO storage.webdav_dead_properties
|
INSERT INTO storage.webdav_dead_properties
|
||||||
(resource_path, user_id, namespace, local_name, value)
|
(resource_path, user_id, namespace, local_name, value)
|
||||||
@@ -37,12 +45,12 @@ impl DeadPropertyStore {
|
|||||||
ON CONFLICT (resource_path, user_id, namespace, local_name)
|
ON CONFLICT (resource_path, user_id, namespace, local_name)
|
||||||
DO UPDATE SET value = EXCLUDED.value, updated_at = CURRENT_TIMESTAMP
|
DO UPDATE SET value = EXCLUDED.value, updated_at = CURRENT_TIMESTAMP
|
||||||
"#,
|
"#,
|
||||||
path,
|
|
||||||
user_id,
|
|
||||||
name.namespace,
|
|
||||||
name.name,
|
|
||||||
value,
|
|
||||||
)
|
)
|
||||||
|
.bind(path)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(&name.namespace)
|
||||||
|
.bind(&name.name)
|
||||||
|
.bind(&value)
|
||||||
.execute(&*self.pool)
|
.execute(&*self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DomainError::internal_error("DeadPropertyStore", format!("set: {e}")))?;
|
.map_err(|e| DomainError::internal_error("DeadPropertyStore", format!("set: {e}")))?;
|
||||||
@@ -56,15 +64,15 @@ impl DeadPropertyStore {
|
|||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
name: &QualifiedName,
|
name: &QualifiedName,
|
||||||
) -> Result<(), DomainError> {
|
) -> Result<(), DomainError> {
|
||||||
sqlx::query!(
|
sqlx::query(
|
||||||
"DELETE FROM storage.webdav_dead_properties
|
"DELETE FROM storage.webdav_dead_properties
|
||||||
WHERE resource_path = $1 AND user_id = $2
|
WHERE resource_path = $1 AND user_id = $2
|
||||||
AND namespace = $3 AND local_name = $4",
|
AND namespace = $3 AND local_name = $4",
|
||||||
path,
|
|
||||||
user_id,
|
|
||||||
name.namespace,
|
|
||||||
name.name,
|
|
||||||
)
|
)
|
||||||
|
.bind(path)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(&name.namespace)
|
||||||
|
.bind(&name.name)
|
||||||
.execute(&*self.pool)
|
.execute(&*self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DomainError::internal_error("DeadPropertyStore", format!("remove: {e}")))?;
|
.map_err(|e| DomainError::internal_error("DeadPropertyStore", format!("remove: {e}")))?;
|
||||||
@@ -77,20 +85,25 @@ impl DeadPropertyStore {
|
|||||||
path: &str,
|
path: &str,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
) -> Result<Vec<(QualifiedName, Option<String>)>, DomainError> {
|
) -> Result<Vec<(QualifiedName, Option<String>)>, DomainError> {
|
||||||
let rows = sqlx::query!(
|
let rows = sqlx::query(
|
||||||
"SELECT namespace, local_name, value
|
"SELECT namespace, local_name, value
|
||||||
FROM storage.webdav_dead_properties
|
FROM storage.webdav_dead_properties
|
||||||
WHERE resource_path = $1 AND user_id = $2",
|
WHERE resource_path = $1 AND user_id = $2",
|
||||||
path,
|
|
||||||
user_id,
|
|
||||||
)
|
)
|
||||||
|
.bind(path)
|
||||||
|
.bind(user_id)
|
||||||
.fetch_all(&*self.pool)
|
.fetch_all(&*self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DomainError::internal_error("DeadPropertyStore", format!("get_all: {e}")))?;
|
.map_err(|e| DomainError::internal_error("DeadPropertyStore", format!("get_all: {e}")))?;
|
||||||
|
|
||||||
Ok(rows
|
Ok(rows
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r| (QualifiedName::new(r.namespace, r.local_name), r.value))
|
.map(|r| {
|
||||||
|
let namespace: String = r.get("namespace");
|
||||||
|
let local_name: String = r.get("local_name");
|
||||||
|
let value: Option<String> = r.get("value");
|
||||||
|
(QualifiedName::new(namespace, local_name), value)
|
||||||
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,30 +115,30 @@ impl DeadPropertyStore {
|
|||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
name: &QualifiedName,
|
name: &QualifiedName,
|
||||||
) -> Result<Option<Option<String>>, DomainError> {
|
) -> Result<Option<Option<String>>, DomainError> {
|
||||||
let row = sqlx::query!(
|
let row = sqlx::query(
|
||||||
"SELECT value FROM storage.webdav_dead_properties
|
"SELECT value FROM storage.webdav_dead_properties
|
||||||
WHERE resource_path = $1 AND user_id = $2
|
WHERE resource_path = $1 AND user_id = $2
|
||||||
AND namespace = $3 AND local_name = $4",
|
AND namespace = $3 AND local_name = $4",
|
||||||
path,
|
|
||||||
user_id,
|
|
||||||
name.namespace,
|
|
||||||
name.name,
|
|
||||||
)
|
)
|
||||||
|
.bind(path)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(&name.namespace)
|
||||||
|
.bind(&name.name)
|
||||||
.fetch_optional(&*self.pool)
|
.fetch_optional(&*self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DomainError::internal_error("DeadPropertyStore", format!("get: {e}")))?;
|
.map_err(|e| DomainError::internal_error("DeadPropertyStore", format!("get: {e}")))?;
|
||||||
|
|
||||||
Ok(row.map(|r| r.value))
|
Ok(row.map(|r| r.get::<Option<String>, _>("value")))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete all dead properties for `path` (called on DELETE).
|
/// Delete all dead properties for `path` (called on DELETE).
|
||||||
pub async fn remove_resource(&self, path: &str, user_id: Uuid) -> Result<(), DomainError> {
|
pub async fn remove_resource(&self, path: &str, user_id: Uuid) -> Result<(), DomainError> {
|
||||||
sqlx::query!(
|
sqlx::query(
|
||||||
"DELETE FROM storage.webdav_dead_properties
|
"DELETE FROM storage.webdav_dead_properties
|
||||||
WHERE resource_path = $1 AND user_id = $2",
|
WHERE resource_path = $1 AND user_id = $2",
|
||||||
path,
|
|
||||||
user_id,
|
|
||||||
)
|
)
|
||||||
|
.bind(path)
|
||||||
|
.bind(user_id)
|
||||||
.execute(&*self.pool)
|
.execute(&*self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -146,26 +159,26 @@ impl DeadPropertyStore {
|
|||||||
DomainError::internal_error("DeadPropertyStore", format!("rename_resource tx: {e}"))
|
DomainError::internal_error("DeadPropertyStore", format!("rename_resource tx: {e}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query(
|
||||||
"DELETE FROM storage.webdav_dead_properties
|
"DELETE FROM storage.webdav_dead_properties
|
||||||
WHERE resource_path = $1 AND user_id = $2",
|
WHERE resource_path = $1 AND user_id = $2",
|
||||||
new_path,
|
|
||||||
user_id,
|
|
||||||
)
|
)
|
||||||
|
.bind(new_path)
|
||||||
|
.bind(user_id)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
DomainError::internal_error("DeadPropertyStore", format!("rename_resource delete: {e}"))
|
DomainError::internal_error("DeadPropertyStore", format!("rename_resource delete: {e}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query(
|
||||||
"UPDATE storage.webdav_dead_properties
|
"UPDATE storage.webdav_dead_properties
|
||||||
SET resource_path = $2
|
SET resource_path = $2
|
||||||
WHERE resource_path = $1 AND user_id = $3",
|
WHERE resource_path = $1 AND user_id = $3",
|
||||||
old_path,
|
|
||||||
new_path,
|
|
||||||
user_id,
|
|
||||||
)
|
)
|
||||||
|
.bind(old_path)
|
||||||
|
.bind(new_path)
|
||||||
|
.bind(user_id)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
|||||||
@@ -487,7 +487,10 @@ async fn handle_propfind(
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
Ok(ResolvedResource::File(file)) => {
|
Ok(ResolvedResource::File(file)) => {
|
||||||
let dead_props = state.webdav_dead_props.get_all(&path, user.id).await
|
let dead_props = state
|
||||||
|
.webdav_dead_props
|
||||||
|
.get_all(&path, user.id)
|
||||||
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let file_href = webdav_href(&client_path);
|
let file_href = webdav_href(&client_path);
|
||||||
let mut buf = Vec::with_capacity(1024);
|
let mut buf = Vec::with_capacity(1024);
|
||||||
@@ -541,7 +544,10 @@ async fn handle_propfind(
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||||
let dead_props = state.webdav_dead_props.get_all(&path, user.id).await
|
let dead_props = state
|
||||||
|
.webdav_dead_props
|
||||||
|
.get_all(&path, user.id)
|
||||||
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let file_href = webdav_href(&client_path);
|
let file_href = webdav_href(&client_path);
|
||||||
let mut buf = Vec::with_capacity(1024);
|
let mut buf = Vec::with_capacity(1024);
|
||||||
@@ -785,13 +791,18 @@ async fn handle_proppatch(
|
|||||||
for op in &ops {
|
for op in &ops {
|
||||||
match op {
|
match op {
|
||||||
PropPatchOp::Set(pv) => {
|
PropPatchOp::Set(pv) => {
|
||||||
dead_props.set(&path, user.id, pv.name.clone(), pv.value.clone()).await
|
dead_props
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to store dead property: {e}")))?;
|
.set(&path, user.id, pv.name.clone(), pv.value.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
AppError::internal_error(format!("Failed to store dead property: {e}"))
|
||||||
|
})?;
|
||||||
results.push((&pv.name, true));
|
results.push((&pv.name, true));
|
||||||
}
|
}
|
||||||
PropPatchOp::Remove(name) => {
|
PropPatchOp::Remove(name) => {
|
||||||
dead_props.remove(&path, user.id, name).await
|
dead_props.remove(&path, user.id, name).await.map_err(|e| {
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to remove dead property: {e}")))?;
|
AppError::internal_error(format!("Failed to remove dead property: {e}"))
|
||||||
|
})?;
|
||||||
results.push((name, true));
|
results.push((name, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1112,7 +1123,9 @@ fn enforce_native_lock(
|
|||||||
if p.is_empty() {
|
if p.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if let Some(e) = lock_store.get_by_path(p) && e.info.depth.eq_ignore_ascii_case("infinity") {
|
if let Some(e) = lock_store.get_by_path(p)
|
||||||
|
&& e.info.depth.eq_ignore_ascii_case("infinity")
|
||||||
|
{
|
||||||
return Some(e);
|
return Some(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user