diff --git a/Cargo.lock b/Cargo.lock index 7f3a314..8ed5d35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2380,6 +2380,7 @@ dependencies = [ "chrono", "derive_builder", "nom 8.0.0", + "phono-pestgros", "regex", "serde", "sqlx", @@ -2397,6 +2398,7 @@ dependencies = [ "derive_builder", "futures", "phono-backends", + "phono-pestgros", "redact", "regex", "serde", @@ -2456,6 +2458,7 @@ dependencies = [ "phono-backends", "phono-models", "phono-namegen", + "phono-pestgros", "rand 0.8.5", "redact", "regex", diff --git a/phono-backends/Cargo.toml b/phono-backends/Cargo.toml index 690a063..26f66d5 100644 --- a/phono-backends/Cargo.toml +++ b/phono-backends/Cargo.toml @@ -7,6 +7,7 @@ version.workspace = true chrono = { workspace = true } derive_builder = { workspace = true } nom = "8.0.0" +phono-pestgros = { workspace = true } regex = { workspace = true } serde = { workspace = true } sqlx = { workspace = true } diff --git a/phono-backends/src/client.rs b/phono-backends/src/client.rs index 7fb1ec7..2411b3a 100644 --- a/phono-backends/src/client.rs +++ b/phono-backends/src/client.rs @@ -1,7 +1,6 @@ +use phono_pestgros::escape_identifier; use sqlx::{PgConnection, Postgres, Row as _, pool::PoolConnection, query}; -use crate::escape_identifier; - /// Newtype to differentiate between workspace and application database /// connections. #[derive(Debug)] diff --git a/phono-backends/src/lib.rs b/phono-backends/src/lib.rs index 74699bf..8a0a0ae 100644 --- a/phono-backends/src/lib.rs +++ b/phono-backends/src/lib.rs @@ -22,6 +22,3 @@ pub mod pg_database; pub mod pg_namespace; pub mod pg_role; pub mod rolnames; -mod utils; - -pub use utils::escape_identifier; diff --git a/phono-backends/src/pg_class.rs b/phono-backends/src/pg_class.rs index 6c80cde..e9c0db6 100644 --- a/phono-backends/src/pg_class.rs +++ b/phono-backends/src/pg_class.rs @@ -1,10 +1,9 @@ use std::fmt::Display; +use phono_pestgros::escape_identifier; use sqlx::{Encode, Postgres, postgres::types::Oid, query_as, query_as_unchecked}; -use crate::{ - client::WorkspaceClient, escape_identifier, pg_acl::PgAclItem, pg_namespace::PgNamespace, -}; +use crate::{client::WorkspaceClient, pg_acl::PgAclItem, pg_namespace::PgNamespace}; #[derive(Clone, Debug)] pub struct PgClass { diff --git a/phono-backends/src/utils.rs b/phono-backends/src/utils.rs deleted file mode 100644 index e87aacf..0000000 --- a/phono-backends/src/utils.rs +++ /dev/null @@ -1,25 +0,0 @@ -/// Given a raw identifier (such as a table name, column name, etc.), format it -/// so that it may be safely interpolated into a SQL query. -pub fn escape_identifier(identifier: &str) -> String { - // Escaping identifiers for Postgres is fairly easy, provided that the input is - // already known to contain no invalid multi-byte sequences. Backslashes may - // remain as-is, and embedded double quotes are escaped simply by doubling - // them (`"` becomes `""`). Refer to the PQescapeInternal() function in - // libpq (fe-exec.c) and Diesel's PgQueryBuilder::push_identifier(). - format!("\"{}\"", identifier.replace('"', "\"\"")) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_escape_identifier() { - assert_eq!(escape_identifier("hello"), r#""hello""#); - assert_eq!(escape_identifier("hello world"), r#""hello world""#); - assert_eq!( - escape_identifier(r#""hello" "world""#), - r#""""hello"" ""world""""# - ); - } -} diff --git a/phono-models/Cargo.toml b/phono-models/Cargo.toml index f298ca8..1b53bbf 100644 --- a/phono-models/Cargo.toml +++ b/phono-models/Cargo.toml @@ -9,6 +9,7 @@ chrono = { workspace = true } derive_builder = { workspace = true } futures = { workspace = true } phono-backends = { workspace = true } +phono-pestgros = { workspace = true } redact = { workspace = true } regex = { workspace = true } serde = { workspace = true } diff --git a/phono-models/src/datum.rs b/phono-models/src/datum.rs deleted file mode 100644 index b56e27e..0000000 --- a/phono-models/src/datum.rs +++ /dev/null @@ -1,67 +0,0 @@ -use bigdecimal::BigDecimal; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use sqlx::{Postgres, QueryBuilder}; -use uuid::Uuid; - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(tag = "t", content = "c")] -pub enum Datum { - // BigDecimal is used because a user may insert a value directly via SQL - // which overflows the representational space of `rust_decimal::Decimal`. - // Note that by default, [`BigDecimal`] serializes to JSON as a string. This - // behavior can be modified, but it's a pain when paired with the [`Option`] - // type. String representation should be acceptable for the UI, as [`Datum`] - // values should always be parsed through Zod, which can coerce the value to - // a number transparently. - Numeric(Option), - Text(Option), - Timestamp(Option>), - Uuid(Option), -} - -impl Datum { - // TODO: Can something similar be achieved with a generic return type? - /// Bind this as a parameter to a sqlx query. - pub fn bind_onto<'a>( - self, - query: sqlx::query::Query<'a, Postgres, ::Arguments<'a>>, - ) -> sqlx::query::Query<'a, Postgres, ::Arguments<'a>> { - match self { - Self::Numeric(value) => query.bind(value), - Self::Text(value) => query.bind(value), - Self::Timestamp(value) => query.bind(value), - Self::Uuid(value) => query.bind(value), - } - } - - /// Push this as a parameter to a [`QueryBuilder`]. - pub fn push_bind_onto(self, builder: &mut QueryBuilder<'_, Postgres>) { - match self { - Self::Numeric(value) => builder.push_bind(value), - Self::Text(value) => builder.push_bind(value), - Self::Timestamp(value) => builder.push_bind(value), - Self::Uuid(value) => builder.push_bind(value), - }; - } - - /// Transform the contained value into a serde_json::Value. - pub fn inner_as_value(&self) -> serde_json::Value { - let serialized = serde_json::to_value(self).unwrap(); - #[derive(Deserialize)] - struct Tagged { - c: serde_json::Value, - } - let deserialized: Tagged = serde_json::from_value(serialized).unwrap(); - deserialized.c - } - - pub fn is_none(&self) -> bool { - match self { - Self::Numeric(None) | Self::Text(None) | Self::Timestamp(None) | Self::Uuid(None) => { - true - } - Self::Numeric(_) | Self::Text(_) | Self::Timestamp(_) | Self::Uuid(_) => false, - } - } -} diff --git a/phono-models/src/expression.rs b/phono-models/src/expression.rs index 7fdcd90..fb65ef4 100644 --- a/phono-models/src/expression.rs +++ b/phono-models/src/expression.rs @@ -1,10 +1,8 @@ use std::fmt::Display; -use phono_backends::escape_identifier; +use phono_pestgros::{Datum, QueryFragment, escape_identifier}; use serde::{Deserialize, Serialize}; -use crate::{datum::Datum, query_builders::QueryFragment}; - /// Building block of a syntax tree for a constrained subset of SQL that can be /// statically analyzed, to validate that user-provided expressions perform only /// operations that are read-only and otherwise safe to execute. diff --git a/phono-models/src/field.rs b/phono-models/src/field.rs index 443cc20..666a4e0 100644 --- a/phono-models/src/field.rs +++ b/phono-models/src/field.rs @@ -2,6 +2,7 @@ use bigdecimal::BigDecimal; use chrono::{DateTime, Utc}; use derive_builder::Builder; use phono_backends::pg_attribute::PgAttribute; +use phono_pestgros::Datum; use serde::{Deserialize, Serialize}; use sqlx::Acquire as _; use sqlx::{ @@ -11,9 +12,7 @@ use sqlx::{ use thiserror::Error; use uuid::Uuid; -use crate::client::AppDbClient; -use crate::datum::Datum; -use crate::presentation::Presentation; +use crate::{client::AppDbClient, presentation::Presentation}; /// A materialization of a database column, fit for consumption by an end user. /// diff --git a/phono-models/src/lib.rs b/phono-models/src/lib.rs index 5a37ea4..869931d 100644 --- a/phono-models/src/lib.rs +++ b/phono-models/src/lib.rs @@ -17,7 +17,6 @@ pub mod accessors; pub mod client; pub mod cluster; -pub mod datum; pub mod errors; pub mod expression; pub mod field; @@ -25,7 +24,6 @@ pub mod language; mod macros; pub mod portal; pub mod presentation; -pub mod query_builders; pub mod service_cred; pub mod user; pub mod workspace; diff --git a/phono-models/src/query_builders.rs b/phono-models/src/query_builders.rs deleted file mode 100644 index c2863f5..0000000 --- a/phono-models/src/query_builders.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Assorted utilities for dynamically constructing and manipulating [`sqlx`] -//! queries. - -use sqlx::{Postgres, QueryBuilder}; - -use crate::datum::Datum; - -/// Representation of a partial, parameterized SQL query. Allows callers to -/// build queries iteratively and dynamically, handling parameter numbering -/// (`$1`, `$2`, `$3`, ...) automatically. -/// -/// This is similar to [`sqlx::QueryBuilder`], except that [`QueryFragment`] -/// objects are composable and may be concatenated to each other. -#[derive(Clone, Debug, PartialEq)] -pub struct QueryFragment { - /// SQL string, split wherever there is a query parameter. For example, - /// `select * from foo where id = $1 and status = $2` is represented along - /// the lines of `["select * from foo where id = ", " and status = ", ""]`. - /// `plain_sql` should always have exactly one more element than `params`. - plain_sql: Vec, - params: Vec, -} - -impl QueryFragment { - /// Validate invariants. Should be run immediately before returning any - /// useful output. - fn gut_checks(&self) { - assert!(self.plain_sql.len() == self.params.len() + 1); - } - - /// Parse from a SQL string with no parameters. - pub fn from_sql(sql: &str) -> Self { - Self { - plain_sql: vec![sql.to_owned()], - params: vec![], - } - } - - /// Convenience function to construct an empty value. - pub fn empty() -> Self { - Self::from_sql("") - } - - /// Parse from a parameter value with no additional SQL. (Renders as `$n`, - /// where`n` is the appropriate parameter index.) - pub fn from_param(param: Datum) -> Self { - Self { - plain_sql: vec!["".to_owned(), "".to_owned()], - params: vec![param], - } - } - - /// Append another query fragment to this one. - pub fn push(&mut self, mut other: QueryFragment) { - let tail = self - .plain_sql - .pop() - .expect("already asserted that vec contains at least 1 item"); - let head = other - .plain_sql - .first() - .expect("already asserted that vec contains at least 1 item"); - self.plain_sql.push(format!("{tail}{head}")); - for value in other.plain_sql.drain(1..) { - self.plain_sql.push(value); - } - self.params.append(&mut other.params); - } - - /// Combine multiple QueryFragments with a separator, similar to - /// [`Vec::join`]. - pub fn join>(fragments: I, sep: Self) -> Self { - let mut acc = QueryFragment::from_sql(""); - let mut iter = fragments.into_iter(); - let mut fragment = match iter.next() { - Some(value) => value, - None => return acc, - }; - for next_fragment in iter { - acc.push(fragment); - acc.push(sep.clone()); - fragment = next_fragment; - } - acc.push(fragment); - acc - } - - /// Convenience method equivalent to: - /// `QueryFragment::concat(fragments, QueryFragment::from_sql(""))` - pub fn concat>(fragments: I) -> Self { - Self::join(fragments, Self::from_sql("")) - } - - /// Checks whether value is empty. A value is considered empty if the - /// resulting SQL code is 0 characters long. - pub fn is_empty(&self) -> bool { - self.gut_checks(); - self.plain_sql.len() == 1 - && self - .plain_sql - .first() - .expect("already checked that len == 1") - .is_empty() - } -} - -impl From for QueryBuilder<'_, Postgres> { - fn from(value: QueryFragment) -> Self { - value.gut_checks(); - let mut builder = QueryBuilder::new(""); - let mut param_iter = value.params.into_iter(); - for plain_sql in value.plain_sql { - builder.push(plain_sql); - if let Some(param) = param_iter.next() { - param.push_bind_onto(&mut builder); - } - } - builder - } -} - -/// Helper type to make it easier to build and reason about multiple related SQL -/// queries. -#[derive(Clone, Debug)] -pub struct SelectQuery { - /// Query fragment following (not including) "select ". - pub selection: QueryFragment, - - /// Query fragment following (not including) "from ". - pub source: QueryFragment, - - /// Query fragment following (not including) "where ", or empty if not - /// applicable. - pub filters: QueryFragment, - - /// Query fragment following (not including) "order by ", or empty if not - /// applicable. - pub order: QueryFragment, - - /// Query fragment following (not including) "limit ", or empty if not - /// applicable. - pub limit: QueryFragment, -} - -impl From for QueryFragment { - fn from(value: SelectQuery) -> Self { - let mut result = QueryFragment::from_sql("select "); - result.push(value.selection); - result.push(QueryFragment::from_sql(" from ")); - result.push(value.source); - if !value.filters.is_empty() { - result.push(QueryFragment::from_sql(" where ")); - result.push(value.filters); - } - if !value.order.is_empty() { - result.push(QueryFragment::from_sql(" order by ")); - result.push(value.order); - } - if !value.limit.is_empty() { - result.push(QueryFragment::from_sql(" limit ")); - result.push(value.limit); - } - result - } -} - -impl From for QueryBuilder<'_, Postgres> { - fn from(value: SelectQuery) -> Self { - QueryFragment::from(value).into() - } -} diff --git a/phono-pestgros/src/lib.rs b/phono-pestgros/src/lib.rs index 5462dab..be7404f 100644 --- a/phono-pestgros/src/lib.rs +++ b/phono-pestgros/src/lib.rs @@ -35,7 +35,7 @@ use pest::{ }; use pest_derive::Parser; -pub use crate::datum::Datum; +pub use crate::{datum::Datum, query_builders::QueryFragment}; mod datum; mod query_builders; diff --git a/phono-pestgros/src/query_builders.rs b/phono-pestgros/src/query_builders.rs index a8669e6..239abeb 100644 --- a/phono-pestgros/src/query_builders.rs +++ b/phono-pestgros/src/query_builders.rs @@ -220,54 +220,3 @@ impl From for QueryBuilder<'_, Postgres> { Self::from(QueryFragment::from(value)) } } - -/// Helper type to make it easier to build and reason about multiple related SQL -/// queries. -#[derive(Clone, Debug)] -pub struct SelectQuery { - /// Query fragment following (not including) "select ". - pub selection: QueryFragment, - - /// Query fragment following (not including) "from ". - pub source: QueryFragment, - - /// Query fragment following (not including) "where ", or empty if not - /// applicable. - pub filters: QueryFragment, - - /// Query fragment following (not including) "order by ", or empty if not - /// applicable. - pub order: QueryFragment, - - /// Query fragment following (not including) "limit ", or empty if not - /// applicable. - pub limit: QueryFragment, -} - -impl From for QueryFragment { - fn from(value: SelectQuery) -> Self { - let mut result = QueryFragment::from_sql("select "); - result.push(value.selection); - result.push(QueryFragment::from_sql(" from ")); - result.push(value.source); - if !value.filters.is_empty() { - result.push(QueryFragment::from_sql(" where ")); - result.push(value.filters); - } - if !value.order.is_empty() { - result.push(QueryFragment::from_sql(" order by ")); - result.push(value.order); - } - if !value.limit.is_empty() { - result.push(QueryFragment::from_sql(" limit ")); - result.push(value.limit); - } - result - } -} - -impl From for QueryBuilder<'_, Postgres> { - fn from(value: SelectQuery) -> Self { - QueryFragment::from(value).into() - } -} diff --git a/phono-server/Cargo.toml b/phono-server/Cargo.toml index 92b5567..673fdc2 100644 --- a/phono-server/Cargo.toml +++ b/phono-server/Cargo.toml @@ -23,6 +23,7 @@ percent-encoding = "2.3.1" phono-backends = { workspace = true } phono-models = { workspace = true } phono-namegen = { workspace = true } +phono-pestgros = { workspace = true } rand = { workspace = true } redact = { workspace = true } regex = { workspace = true } diff --git a/phono-server/src/permissions.rs b/phono-server/src/permissions.rs index 526036d..61b9083 100644 --- a/phono-server/src/permissions.rs +++ b/phono-server/src/permissions.rs @@ -7,7 +7,6 @@ use anyhow::anyhow; use askama::Template; use phono_backends::{ client::WorkspaceClient, - escape_identifier, pg_acl::{PgAclItem, PgPrivilegeType}, pg_class::PgClass, pg_role::RoleTree, @@ -17,6 +16,7 @@ use phono_backends::{ }, }; use phono_models::{accessors::Actor, service_cred::ServiceCred, user::User}; +use phono_pestgros::escape_identifier; use serde::{Deserialize, Serialize}; use sqlx::{postgres::types::Oid, prelude::FromRow, query, query_as}; use tracing::{Instrument, info_span}; diff --git a/phono-server/src/routes/relations_single/add_field_handler.rs b/phono-server/src/routes/relations_single/add_field_handler.rs index 853465d..ec34968 100644 --- a/phono-server/src/routes/relations_single/add_field_handler.rs +++ b/phono-server/src/routes/relations_single/add_field_handler.rs @@ -6,12 +6,13 @@ use axum::{ // [`axum_extra`]'s form extractor is preferred: // https://docs.rs/axum-extra/0.10.1/axum_extra/extract/struct.Form.html#differences-from-axumextractform use axum_extra::extract::Form; -use phono_backends::{escape_identifier, pg_class::PgClass}; +use phono_backends::pg_class::PgClass; use phono_models::{ accessors::{Accessor as _, Actor, portal::PortalAccessor}, field::Field, presentation::Presentation, }; +use phono_pestgros::escape_identifier; use serde::Deserialize; use sqlx::{postgres::types::Oid, query}; use uuid::Uuid; diff --git a/phono-server/src/routes/relations_single/get_data_handler.rs b/phono-server/src/routes/relations_single/get_data_handler.rs index 66faa34..0ad2e46 100644 --- a/phono-server/src/routes/relations_single/get_data_handler.rs +++ b/phono-server/src/routes/relations_single/get_data_handler.rs @@ -5,19 +5,16 @@ use axum::{ extract::{Path, State}, response::{IntoResponse as _, Response}, }; -use phono_backends::{ - escape_identifier, pg_acl::PgPrivilegeType, pg_attribute::PgAttribute, pg_class::PgClass, -}; +use phono_backends::{pg_acl::PgPrivilegeType, pg_attribute::PgAttribute, pg_class::PgClass}; use phono_models::{ accessors::{Accessor, Actor, portal::PortalAccessor}, - datum::Datum, expression::PgExpressionAny, field::Field, - query_builders::{QueryFragment, SelectQuery}, }; +use phono_pestgros::{Datum, QueryFragment, escape_identifier}; use serde::{Deserialize, Serialize}; use sqlx::{ - QueryBuilder, + Postgres, QueryBuilder, postgres::{PgRow, types::Oid}, }; use tracing::debug; @@ -47,6 +44,57 @@ pub(super) struct FormBody { const FRONTEND_ROW_LIMIT: i64 = 1000; +/// Helper type to make it easier to build and reason about multiple related SQL +/// queries. +#[derive(Clone, Debug)] +pub struct SelectQuery { + /// Query fragment following (not including) "select ". + pub selection: QueryFragment, + + /// Query fragment following (not including) "from ". + pub source: QueryFragment, + + /// Query fragment following (not including) "where ", or empty if not + /// applicable. + pub filters: QueryFragment, + + /// Query fragment following (not including) "order by ", or empty if not + /// applicable. + pub order: QueryFragment, + + /// Query fragment following (not including) "limit ", or empty if not + /// applicable. + pub limit: QueryFragment, +} + +impl From for QueryFragment { + fn from(value: SelectQuery) -> Self { + let mut result = QueryFragment::from_sql("select "); + result.push(value.selection); + result.push(QueryFragment::from_sql(" from ")); + result.push(value.source); + if !value.filters.is_empty() { + result.push(QueryFragment::from_sql(" where ")); + result.push(value.filters); + } + if !value.order.is_empty() { + result.push(QueryFragment::from_sql(" order by ")); + result.push(value.order); + } + if !value.limit.is_empty() { + result.push(QueryFragment::from_sql(" limit ")); + result.push(value.limit); + } + result + } +} + +impl From for QueryBuilder<'_, Postgres> { + fn from(value: SelectQuery) -> Self { + QueryFragment::from(value).into() + } +} + /// HTTP GET handler for an API endpoint returning a JSON encoding of portal /// data to display in a table or similar form. If the `subfilter` URL parameter /// is specified, it is `&&`-ed with the portal's stored filter. diff --git a/phono-server/src/routes/relations_single/insert_handler.rs b/phono-server/src/routes/relations_single/insert_handler.rs index 3f33fba..d8024b9 100644 --- a/phono-server/src/routes/relations_single/insert_handler.rs +++ b/phono-server/src/routes/relations_single/insert_handler.rs @@ -8,11 +8,9 @@ use axum::{ // [`axum_extra`]'s form extractor is required to support repeated keys: // https://docs.rs/axum-extra/0.10.1/axum_extra/extract/struct.Form.html#differences-from-axumextractform use axum_extra::extract::Form; -use phono_backends::{escape_identifier, pg_acl::PgPrivilegeType, pg_class::PgClass}; -use phono_models::{ - accessors::{Accessor as _, Actor, portal::PortalAccessor}, - datum::Datum, -}; +use phono_backends::{pg_acl::PgPrivilegeType, pg_class::PgClass}; +use phono_models::accessors::{Accessor as _, Actor, portal::PortalAccessor}; +use phono_pestgros::{Datum, escape_identifier}; use serde::Deserialize; use sqlx::{postgres::types::Oid, query}; use uuid::Uuid; diff --git a/phono-server/src/routes/relations_single/remove_field_handler.rs b/phono-server/src/routes/relations_single/remove_field_handler.rs index bbe3c1c..e309e47 100644 --- a/phono-server/src/routes/relations_single/remove_field_handler.rs +++ b/phono-server/src/routes/relations_single/remove_field_handler.rs @@ -3,11 +3,12 @@ use axum::{ extract::{Path, State}, response::Response, }; -use phono_backends::{escape_identifier, pg_class::PgClass}; +use phono_backends::pg_class::PgClass; use phono_models::{ accessors::{Accessor, Actor, portal::PortalAccessor}, field::Field, }; +use phono_pestgros::escape_identifier; use serde::Deserialize; use sqlx::{postgres::types::Oid, query}; use uuid::Uuid; diff --git a/phono-server/src/routes/relations_single/update_rel_name_handler.rs b/phono-server/src/routes/relations_single/update_rel_name_handler.rs index 66f47ee..5887b0a 100644 --- a/phono-server/src/routes/relations_single/update_rel_name_handler.rs +++ b/phono-server/src/routes/relations_single/update_rel_name_handler.rs @@ -5,7 +5,8 @@ use axum::{ extract::{Path, State}, response::Response, }; -use phono_backends::{escape_identifier, pg_class::PgClass}; +use phono_backends::pg_class::PgClass; +use phono_pestgros::escape_identifier; use regex::Regex; use serde::Deserialize; use sqlx::{postgres::types::Oid, query}; diff --git a/phono-server/src/routes/relations_single/update_values_handler.rs b/phono-server/src/routes/relations_single/update_values_handler.rs index 6deea7b..7ee25ce 100644 --- a/phono-server/src/routes/relations_single/update_values_handler.rs +++ b/phono-server/src/routes/relations_single/update_values_handler.rs @@ -5,13 +5,9 @@ use axum::{ extract::{Path, State}, response::{IntoResponse as _, Response}, }; -use phono_backends::{ - escape_identifier, pg_acl::PgPrivilegeType, pg_attribute::PgAttribute, pg_class::PgClass, -}; -use phono_models::{ - accessors::{Accessor, Actor, portal::PortalAccessor}, - datum::Datum, -}; +use phono_backends::{pg_acl::PgPrivilegeType, pg_attribute::PgAttribute, pg_class::PgClass}; +use phono_models::accessors::{Accessor, Actor, portal::PortalAccessor}; +use phono_pestgros::{Datum, escape_identifier}; use serde::Deserialize; use serde_json::json; use sqlx::{Acquire as _, postgres::types::Oid, query}; diff --git a/phono-server/src/routes/workspaces_multi/add_handlers.rs b/phono-server/src/routes/workspaces_multi/add_handlers.rs index 50074c0..44f40d0 100644 --- a/phono-server/src/routes/workspaces_multi/add_handlers.rs +++ b/phono-server/src/routes/workspaces_multi/add_handlers.rs @@ -1,9 +1,10 @@ use axum::{extract::State, response::IntoResponse}; -use phono_backends::{client::WorkspaceClient, escape_identifier, rolnames::ROLE_PREFIX_USER}; +use phono_backends::{client::WorkspaceClient, rolnames::ROLE_PREFIX_USER}; use phono_models::{ client::AppDbClient, cluster::Cluster, user::User, workspace::Workspace, workspace_user_perm::WorkspaceMembership, }; +use phono_pestgros::escape_identifier; use sqlx::{Connection as _, PgConnection, query}; use crate::{ diff --git a/phono-server/src/routes/workspaces_single/add_service_credential_handler.rs b/phono-server/src/routes/workspaces_single/add_service_credential_handler.rs index 341dffe..be479ef 100644 --- a/phono-server/src/routes/workspaces_single/add_service_credential_handler.rs +++ b/phono-server/src/routes/workspaces_single/add_service_credential_handler.rs @@ -3,14 +3,14 @@ use axum::{ extract::{Path, State}, response::IntoResponse, }; -use phono_backends::{ - escape_identifier, - rolnames::{ROLE_PREFIX_SERVICE_CRED, SERVICE_CRED_CONN_LIMIT, SERVICE_CRED_SUFFIX_LEN}, +use phono_backends::rolnames::{ + ROLE_PREFIX_SERVICE_CRED, SERVICE_CRED_CONN_LIMIT, SERVICE_CRED_SUFFIX_LEN, }; use phono_models::{ accessors::{Accessor as _, Actor, workspace::WorkspaceAccessor}, service_cred::ServiceCred, }; +use phono_pestgros::escape_identifier; use rand::distributions::{Alphanumeric, DistString}; use redact::Secret; use serde::Deserialize; diff --git a/phono-server/src/routes/workspaces_single/add_table_handler.rs b/phono-server/src/routes/workspaces_single/add_table_handler.rs index 662b9a6..7878c86 100644 --- a/phono-server/src/routes/workspaces_single/add_table_handler.rs +++ b/phono-server/src/routes/workspaces_single/add_table_handler.rs @@ -2,14 +2,11 @@ use axum::{ extract::{Path, State}, response::IntoResponse, }; -use phono_backends::{ - escape_identifier, - rolnames::{ - ROLE_PREFIX_TABLE_OWNER, ROLE_PREFIX_TABLE_READER, ROLE_PREFIX_TABLE_WRITER, - ROLE_PREFIX_USER, - }, +use phono_backends::rolnames::{ + ROLE_PREFIX_TABLE_OWNER, ROLE_PREFIX_TABLE_READER, ROLE_PREFIX_TABLE_WRITER, ROLE_PREFIX_USER, }; use phono_models::accessors::{Accessor as _, Actor, workspace::WorkspaceAccessor}; +use phono_pestgros::escape_identifier; use serde::Deserialize; use sqlx::{Acquire as _, query}; use uuid::Uuid; diff --git a/phono-server/src/routes/workspaces_single/grant_workspace_privilege_handler.rs b/phono-server/src/routes/workspaces_single/grant_workspace_privilege_handler.rs index 6134434..193049b 100644 --- a/phono-server/src/routes/workspaces_single/grant_workspace_privilege_handler.rs +++ b/phono-server/src/routes/workspaces_single/grant_workspace_privilege_handler.rs @@ -3,12 +3,13 @@ use axum::{ extract::{Path, State}, response::IntoResponse, }; -use phono_backends::{escape_identifier, pg_database::PgDatabase, rolnames::ROLE_PREFIX_USER}; +use phono_backends::{pg_database::PgDatabase, rolnames::ROLE_PREFIX_USER}; use phono_models::{ accessors::{Accessor as _, Actor, workspace::WorkspaceAccessor}, user::User, workspace_user_perm::WorkspaceMembership, }; +use phono_pestgros::escape_identifier; use serde::Deserialize; use sqlx::query; use uuid::Uuid;