move relevant common types to phono-pestgros
This commit is contained in:
parent
17ccd80764
commit
36a0c27ad4
26 changed files with 90 additions and 364 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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""""#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<BigDecimal>),
|
||||
Text(Option<String>),
|
||||
Timestamp(Option<DateTime<Utc>>),
|
||||
Uuid(Option<Uuid>),
|
||||
}
|
||||
|
||||
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, <sqlx::Postgres as sqlx::Database>::Arguments<'a>>,
|
||||
) -> sqlx::query::Query<'a, Postgres, <sqlx::Postgres as sqlx::Database>::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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
params: Vec<Datum>,
|
||||
}
|
||||
|
||||
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<I: IntoIterator<Item = Self>>(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<I: IntoIterator<Item = Self>>(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<QueryFragment> 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<SelectQuery> 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<SelectQuery> for QueryBuilder<'_, Postgres> {
|
||||
fn from(value: SelectQuery) -> Self {
|
||||
QueryFragment::from(value).into()
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -220,54 +220,3 @@ impl From<Expr> 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<SelectQuery> 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<SelectQuery> for QueryBuilder<'_, Postgres> {
|
||||
fn from(value: SelectQuery) -> Self {
|
||||
QueryFragment::from(value).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<SelectQuery> 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<SelectQuery> 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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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::{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue