Refactor
This commit is contained in:
@@ -3,7 +3,7 @@ pub mod scan;
|
||||
pub mod search;
|
||||
|
||||
pub mod pg {
|
||||
pub use tokio_postgres::{Client, Error};
|
||||
pub use tokio_postgres::{Client, Error, NoTls};
|
||||
}
|
||||
|
||||
use err::Error;
|
||||
@@ -11,9 +11,9 @@ use netpod::log::*;
|
||||
use netpod::TableSizes;
|
||||
use netpod::{Channel, Database, NodeConfigCached};
|
||||
use netpod::{ScalarType, Shape};
|
||||
use pg::{Client as PgClient, NoTls};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio_postgres::{Client, Client as PgClient, NoTls};
|
||||
|
||||
trait ErrConv<T> {
|
||||
fn err_conv(self) -> Result<T, Error>;
|
||||
@@ -49,7 +49,7 @@ pub async fn delay_io_medium() {
|
||||
delay_us(2000).await;
|
||||
}
|
||||
|
||||
pub async fn create_connection(db_config: &Database) -> Result<Client, Error> {
|
||||
pub async fn create_connection(db_config: &Database) -> Result<PgClient, Error> {
|
||||
// TODO use a common already running worker pool for these queries:
|
||||
let d = db_config;
|
||||
let uri = format!("postgresql://{}:{}@{}:{}/{}", d.user, d.pass, d.host, d.port, d.name);
|
||||
@@ -135,7 +135,7 @@ pub async fn random_channel(node_config: &NodeConfigCached) -> Result<String, Er
|
||||
Ok(rows[0].get(0))
|
||||
}
|
||||
|
||||
pub async fn insert_channel(name: String, facility: i64, dbc: &Client) -> Result<(), Error> {
|
||||
pub async fn insert_channel(name: String, facility: i64, dbc: &PgClient) -> Result<(), Error> {
|
||||
let rows = dbc
|
||||
.query(
|
||||
"select count(rowid) from channels where facility = $1 and name = $2",
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
use crate::{create_connection, delay_io_medium, delay_io_short, ErrConv};
|
||||
use async_channel::{bounded, Receiver};
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::create_connection;
|
||||
use crate::delay_io_medium;
|
||||
use crate::delay_io_short;
|
||||
use crate::ErrConv;
|
||||
use async_channel::bounded;
|
||||
use async_channel::Receiver;
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use err::Error;
|
||||
use futures_util::{FutureExt, Stream};
|
||||
use futures_util::FutureExt;
|
||||
use futures_util::Stream;
|
||||
use netpod::log::*;
|
||||
use netpod::{Database, NodeConfigCached};
|
||||
use netpod::Database;
|
||||
use netpod::NodeConfigCached;
|
||||
use parse::channelconfig::NErr;
|
||||
use pin_project::pin_project;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::future::Future;
|
||||
use std::io::ErrorKind;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::fs::{DirEntry, ReadDir};
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use tokio::fs::DirEntry;
|
||||
use tokio::fs::ReadDir;
|
||||
use tokio_postgres::Client;
|
||||
|
||||
mod updatechannelnames;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NodeDiskIdent {
|
||||
pub rowid: i64,
|
||||
@@ -89,9 +103,10 @@ pub async fn get_node_disk_ident_2(
|
||||
pub struct FindChannelNamesFromConfigReadDir {
|
||||
#[pin]
|
||||
read_dir_fut: Option<Pin<Box<dyn Future<Output = std::io::Result<ReadDir>> + Send>>>,
|
||||
read_dir: Option<ReadDir>,
|
||||
#[pin]
|
||||
dir_entry_fut: Option<Pin<Box<dyn Future<Output = std::io::Result<Option<DirEntry>>> + Send>>>,
|
||||
read_dir: Option<Pin<Box<ReadDir>>>,
|
||||
#[pin]
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl FindChannelNamesFromConfigReadDir {
|
||||
@@ -99,7 +114,7 @@ impl FindChannelNamesFromConfigReadDir {
|
||||
Self {
|
||||
read_dir_fut: Some(Box::pin(tokio::fs::read_dir(base_dir.as_ref().join("config")))),
|
||||
read_dir: None,
|
||||
dir_entry_fut: None,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,35 +124,36 @@ impl Stream for FindChannelNamesFromConfigReadDir {
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
use Poll::*;
|
||||
let span = span!(Level::INFO, "FindChNameCfgDir");
|
||||
let _spg = span.enter();
|
||||
let mut pself = self.project();
|
||||
loop {
|
||||
break if let Some(fut) = pself.dir_entry_fut.as_mut().as_pin_mut() {
|
||||
match fut.poll(cx) {
|
||||
Ready(Ok(Some(item))) => {
|
||||
let g = unsafe { &mut *(pself.read_dir.as_mut().unwrap() as *mut ReadDir) };
|
||||
let fut = g.next_entry();
|
||||
*pself.dir_entry_fut = Some(Box::pin(fut));
|
||||
Ready(Some(Ok(item)))
|
||||
}
|
||||
break if *pself.done {
|
||||
Ready(None)
|
||||
} else if let Some(mut fut) = pself.read_dir.as_mut().as_pin_mut() {
|
||||
match fut.poll_next_entry(cx) {
|
||||
Ready(Ok(Some(item))) => Ready(Some(Ok(item))),
|
||||
Ready(Ok(None)) => {
|
||||
*pself.dir_entry_fut = None;
|
||||
*pself.done = true;
|
||||
Ready(None)
|
||||
}
|
||||
Ready(Err(e)) => Ready(Some(Err(e.into()))),
|
||||
Ready(Err(e)) => {
|
||||
*pself.done = true;
|
||||
Ready(Some(Err(e.into())))
|
||||
}
|
||||
Pending => Pending,
|
||||
}
|
||||
} else if let Some(fut) = pself.read_dir_fut.as_mut().as_pin_mut() {
|
||||
match fut.poll(cx) {
|
||||
Ready(Ok(item)) => {
|
||||
*pself.read_dir_fut = None;
|
||||
*pself.read_dir = Some(item);
|
||||
//let fut = pself.read_dir.as_mut().unwrap().next_entry();
|
||||
let g = unsafe { &mut *(pself.read_dir.as_mut().unwrap() as *mut ReadDir) };
|
||||
let fut = g.next_entry();
|
||||
*pself.dir_entry_fut = Some(Box::pin(fut));
|
||||
*pself.read_dir = Some(Box::pin(item));
|
||||
continue;
|
||||
}
|
||||
Ready(Err(e)) => Ready(Some(Err(e.into()))),
|
||||
Ready(Err(e)) => {
|
||||
*pself.done = true;
|
||||
Ready(Some(Err(e.into())))
|
||||
}
|
||||
Pending => Pending,
|
||||
}
|
||||
} else {
|
||||
@@ -161,166 +177,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UpdatedDbWithChannelNames {
|
||||
msg: String,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct UpdatedDbWithChannelNamesStream {
|
||||
errored: bool,
|
||||
data_complete: bool,
|
||||
#[allow(dead_code)]
|
||||
node_config: Pin<Box<NodeConfigCached>>,
|
||||
// TODO can we pass a Pin to the async fn instead of creating static ref?
|
||||
node_config_ref: &'static NodeConfigCached,
|
||||
#[pin]
|
||||
client_fut: Option<Pin<Box<dyn Future<Output = Result<Client, Error>> + Send>>>,
|
||||
#[pin]
|
||||
client: Option<Client>,
|
||||
client_ref: Option<&'static Client>,
|
||||
#[pin]
|
||||
ident_fut: Option<Pin<Box<dyn Future<Output = Result<NodeDiskIdent, Error>> + Send>>>,
|
||||
ident: Option<NodeDiskIdent>,
|
||||
#[pin]
|
||||
find: Option<FindChannelNamesFromConfigReadDir>,
|
||||
#[pin]
|
||||
update_batch: Option<Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>>,
|
||||
channel_inp_done: bool,
|
||||
clist: Vec<String>,
|
||||
}
|
||||
|
||||
impl UpdatedDbWithChannelNamesStream {
|
||||
pub fn new(node_config: NodeConfigCached) -> Result<Self, Error> {
|
||||
let node_config = Box::pin(node_config.clone());
|
||||
let node_config_ref = unsafe { &*(&node_config as &NodeConfigCached as *const _) };
|
||||
let mut ret = Self {
|
||||
errored: false,
|
||||
data_complete: false,
|
||||
node_config,
|
||||
node_config_ref,
|
||||
client_fut: None,
|
||||
client: None,
|
||||
client_ref: None,
|
||||
ident_fut: None,
|
||||
ident: None,
|
||||
find: None,
|
||||
update_batch: None,
|
||||
channel_inp_done: false,
|
||||
clist: Vec::new(),
|
||||
};
|
||||
ret.client_fut = Some(Box::pin(create_connection(
|
||||
&ret.node_config_ref.node_config.cluster.database,
|
||||
)));
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for UpdatedDbWithChannelNamesStream {
|
||||
type Item = Result<UpdatedDbWithChannelNames, Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
use Poll::*;
|
||||
let mut pself = self.project();
|
||||
loop {
|
||||
break if *pself.errored {
|
||||
Ready(None)
|
||||
} else if *pself.data_complete {
|
||||
Ready(None)
|
||||
} else if let Some(fut) = pself.find.as_mut().as_pin_mut() {
|
||||
match fut.poll_next(cx) {
|
||||
Ready(Some(Ok(item))) => {
|
||||
pself
|
||||
.clist
|
||||
.push(String::from_utf8(item.file_name().into_vec()).unwrap());
|
||||
continue;
|
||||
}
|
||||
Ready(Some(Err(e))) => {
|
||||
*pself.errored = true;
|
||||
Ready(Some(Err(e)))
|
||||
}
|
||||
Ready(None) => {
|
||||
*pself.channel_inp_done = true;
|
||||
// Work through the collected items
|
||||
let l = std::mem::replace(pself.clist, Vec::new());
|
||||
let fut = update_db_with_channel_name_list(
|
||||
l,
|
||||
pself.ident.as_ref().unwrap().facility,
|
||||
pself.client.as_ref().get_ref().as_ref().unwrap(),
|
||||
);
|
||||
// TODO
|
||||
//pself.update_batch.replace(Box::pin(fut));
|
||||
let _ = fut;
|
||||
continue;
|
||||
}
|
||||
Pending => Pending,
|
||||
}
|
||||
} else if let Some(fut) = pself.ident_fut.as_mut().as_pin_mut() {
|
||||
match fut.poll(cx) {
|
||||
Ready(Ok(item)) => {
|
||||
*pself.ident_fut = None;
|
||||
*pself.ident = Some(item);
|
||||
let ret = UpdatedDbWithChannelNames {
|
||||
msg: format!("Got ident {:?}", pself.ident),
|
||||
count: 43,
|
||||
};
|
||||
let base_path = &pself
|
||||
.node_config
|
||||
.node
|
||||
.sf_databuffer
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::with_msg(format!("missing sf databuffer config in node")))?
|
||||
.data_base_path;
|
||||
let s = FindChannelNamesFromConfigReadDir::new(base_path);
|
||||
*pself.find = Some(s);
|
||||
Ready(Some(Ok(ret)))
|
||||
}
|
||||
Ready(Err(e)) => {
|
||||
*pself.errored = true;
|
||||
Ready(Some(Err(e)))
|
||||
}
|
||||
Pending => Pending,
|
||||
}
|
||||
} else if let Some(fut) = pself.client_fut.as_mut().as_pin_mut() {
|
||||
match fut.poll(cx) {
|
||||
Ready(Ok(item)) => {
|
||||
*pself.client_fut = None;
|
||||
//*pself.client = Some(Box::pin(item));
|
||||
//*pself.client_ref = Some(unsafe { &*(&pself.client.as_ref().unwrap() as &Client as *const _) });
|
||||
*pself.client = Some(item);
|
||||
let c2: &Client = pself.client.as_ref().get_ref().as_ref().unwrap();
|
||||
*pself.client_ref = Some(unsafe { &*(c2 as *const _) });
|
||||
|
||||
//() == pself.node_config.as_ref();
|
||||
//() == pself.client.as_ref().as_pin_ref().unwrap();
|
||||
/* *pself.ident_fut = Some(Box::pin(get_node_disk_ident_2(
|
||||
pself.node_config.as_ref(),
|
||||
pself.client.as_ref().as_pin_ref().unwrap(),
|
||||
)));*/
|
||||
*pself.ident_fut = Some(Box::pin(get_node_disk_ident(
|
||||
pself.node_config_ref,
|
||||
pself.client_ref.as_ref().unwrap(),
|
||||
)));
|
||||
let ret = UpdatedDbWithChannelNames {
|
||||
msg: format!("Client opened connection"),
|
||||
count: 42,
|
||||
};
|
||||
Ready(Some(Ok(ret)))
|
||||
}
|
||||
Ready(Err(e)) => {
|
||||
*pself.errored = true;
|
||||
Ready(Some(Err(e)))
|
||||
}
|
||||
Pending => Pending,
|
||||
}
|
||||
} else {
|
||||
Ready(None)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
async fn update_db_with_channel_name_list(list: Vec<String>, backend: i64, dbc: &Client) -> Result<(), Error> {
|
||||
delay_io_short().await;
|
||||
dbc.query("begin", &[]).await.err_conv()?;
|
||||
@@ -336,6 +193,12 @@ async fn update_db_with_channel_name_list(list: Vec<String>, backend: i64, dbc:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UpdatedDbWithChannelNames {
|
||||
msg: String,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
pub async fn update_db_with_channel_names(
|
||||
node_config: NodeConfigCached,
|
||||
db_config: &Database,
|
||||
@@ -413,8 +276,8 @@ pub async fn update_db_with_channel_names(
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
pub fn update_db_with_channel_names_3<'a>(
|
||||
node_config: &'a NodeConfigCached,
|
||||
pub fn update_db_with_channel_names_3(
|
||||
node_config: &NodeConfigCached,
|
||||
) -> impl Stream<Item = Result<UpdatedDbWithChannelNames, Error>> + 'static {
|
||||
let base_path = &node_config
|
||||
.node
|
||||
@@ -559,7 +422,7 @@ pub enum UpdateChannelConfigResult {
|
||||
/**
|
||||
Parse the config of the given channel and update database.
|
||||
*/
|
||||
pub async fn update_db_with_channel_config(
|
||||
async fn update_db_with_channel_config(
|
||||
node_config: &NodeConfigCached,
|
||||
node_disk_ident: &NodeDiskIdent,
|
||||
channel_id: i64,
|
||||
|
||||
171
dbconn/src/scan/updatechannelnames.rs
Normal file
171
dbconn/src/scan/updatechannelnames.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use super::get_node_disk_ident;
|
||||
use super::update_db_with_channel_name_list;
|
||||
use super::FindChannelNamesFromConfigReadDir;
|
||||
use super::NodeDiskIdent;
|
||||
use super::UpdatedDbWithChannelNames;
|
||||
use crate::create_connection;
|
||||
use crate::pg::Client as PgClient;
|
||||
use err::Error;
|
||||
use futures_util::Future;
|
||||
use futures_util::Stream;
|
||||
use netpod::NodeConfigCached;
|
||||
use pin_project::pin_project;
|
||||
use std::os::unix::prelude::OsStringExt;
|
||||
use std::pin::Pin;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
|
||||
#[pin_project]
|
||||
struct UpdatedDbWithChannelNamesStream {
|
||||
errored: bool,
|
||||
data_complete: bool,
|
||||
#[allow(dead_code)]
|
||||
node_config: Pin<Box<NodeConfigCached>>,
|
||||
// TODO can we pass a Pin to the async fn instead of creating static ref?
|
||||
node_config_ref: &'static NodeConfigCached,
|
||||
#[pin]
|
||||
client_fut: Option<Pin<Box<dyn Future<Output = Result<PgClient, Error>> + Send>>>,
|
||||
#[pin]
|
||||
client: Option<PgClient>,
|
||||
client_ref: Option<&'static PgClient>,
|
||||
#[pin]
|
||||
ident_fut: Option<Pin<Box<dyn Future<Output = Result<NodeDiskIdent, Error>> + Send>>>,
|
||||
ident: Option<NodeDiskIdent>,
|
||||
#[pin]
|
||||
find: Option<FindChannelNamesFromConfigReadDir>,
|
||||
#[pin]
|
||||
update_batch: Option<Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>>,
|
||||
channel_inp_done: bool,
|
||||
clist: Vec<String>,
|
||||
}
|
||||
|
||||
impl UpdatedDbWithChannelNamesStream {
|
||||
#[allow(unused)]
|
||||
fn new(node_config: NodeConfigCached) -> Result<Self, Error> {
|
||||
let node_config = Box::pin(node_config.clone());
|
||||
let node_config_ref = unsafe { &*(&node_config as &NodeConfigCached as *const _) };
|
||||
let mut ret = Self {
|
||||
errored: false,
|
||||
data_complete: false,
|
||||
node_config,
|
||||
node_config_ref,
|
||||
client_fut: None,
|
||||
client: None,
|
||||
client_ref: None,
|
||||
ident_fut: None,
|
||||
ident: None,
|
||||
find: None,
|
||||
update_batch: None,
|
||||
channel_inp_done: false,
|
||||
clist: Vec::new(),
|
||||
};
|
||||
ret.client_fut = Some(Box::pin(create_connection(
|
||||
&ret.node_config_ref.node_config.cluster.database,
|
||||
)));
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for UpdatedDbWithChannelNamesStream {
|
||||
type Item = Result<UpdatedDbWithChannelNames, Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
use Poll::*;
|
||||
let mut pself = self.project();
|
||||
loop {
|
||||
break if *pself.errored {
|
||||
Ready(None)
|
||||
} else if *pself.data_complete {
|
||||
Ready(None)
|
||||
} else if let Some(fut) = pself.find.as_mut().as_pin_mut() {
|
||||
match fut.poll_next(cx) {
|
||||
Ready(Some(Ok(item))) => {
|
||||
pself
|
||||
.clist
|
||||
.push(String::from_utf8(item.file_name().into_vec()).unwrap());
|
||||
continue;
|
||||
}
|
||||
Ready(Some(Err(e))) => {
|
||||
*pself.errored = true;
|
||||
Ready(Some(Err(e)))
|
||||
}
|
||||
Ready(None) => {
|
||||
*pself.channel_inp_done = true;
|
||||
// Work through the collected items
|
||||
let l = std::mem::replace(pself.clist, Vec::new());
|
||||
let fut = update_db_with_channel_name_list(
|
||||
l,
|
||||
pself.ident.as_ref().unwrap().facility,
|
||||
pself.client.as_ref().get_ref().as_ref().unwrap(),
|
||||
);
|
||||
// TODO
|
||||
//pself.update_batch.replace(Box::pin(fut));
|
||||
let _ = fut;
|
||||
continue;
|
||||
}
|
||||
Pending => Pending,
|
||||
}
|
||||
} else if let Some(fut) = pself.ident_fut.as_mut().as_pin_mut() {
|
||||
match fut.poll(cx) {
|
||||
Ready(Ok(item)) => {
|
||||
*pself.ident_fut = None;
|
||||
*pself.ident = Some(item);
|
||||
let ret = UpdatedDbWithChannelNames {
|
||||
msg: format!("Got ident {:?}", pself.ident),
|
||||
count: 43,
|
||||
};
|
||||
let base_path = &pself
|
||||
.node_config
|
||||
.node
|
||||
.sf_databuffer
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::with_msg(format!("missing sf databuffer config in node")))?
|
||||
.data_base_path;
|
||||
let s = FindChannelNamesFromConfigReadDir::new(base_path);
|
||||
*pself.find = Some(s);
|
||||
Ready(Some(Ok(ret)))
|
||||
}
|
||||
Ready(Err(e)) => {
|
||||
*pself.errored = true;
|
||||
Ready(Some(Err(e)))
|
||||
}
|
||||
Pending => Pending,
|
||||
}
|
||||
} else if let Some(fut) = pself.client_fut.as_mut().as_pin_mut() {
|
||||
match fut.poll(cx) {
|
||||
Ready(Ok(item)) => {
|
||||
*pself.client_fut = None;
|
||||
//*pself.client = Some(Box::pin(item));
|
||||
//*pself.client_ref = Some(unsafe { &*(&pself.client.as_ref().unwrap() as &Client as *const _) });
|
||||
*pself.client = Some(item);
|
||||
let c2: &PgClient = pself.client.as_ref().get_ref().as_ref().unwrap();
|
||||
*pself.client_ref = Some(unsafe { &*(c2 as *const _) });
|
||||
|
||||
//() == pself.node_config.as_ref();
|
||||
//() == pself.client.as_ref().as_pin_ref().unwrap();
|
||||
/* *pself.ident_fut = Some(Box::pin(get_node_disk_ident_2(
|
||||
pself.node_config.as_ref(),
|
||||
pself.client.as_ref().as_pin_ref().unwrap(),
|
||||
)));*/
|
||||
*pself.ident_fut = Some(Box::pin(get_node_disk_ident(
|
||||
pself.node_config_ref,
|
||||
pself.client_ref.as_ref().unwrap(),
|
||||
)));
|
||||
let ret = UpdatedDbWithChannelNames {
|
||||
msg: format!("Client opened connection"),
|
||||
count: 42,
|
||||
};
|
||||
Ready(Some(Ok(ret)))
|
||||
}
|
||||
Ready(Err(e)) => {
|
||||
*pself.errored = true;
|
||||
Ready(Some(Err(e)))
|
||||
}
|
||||
Pending => Pending,
|
||||
}
|
||||
} else {
|
||||
Ready(None)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,7 @@ pub async fn search_channel_scylla(
|
||||
" series, facility, channel, scalar_type, shape_dims",
|
||||
" from series_by_channel",
|
||||
" where channel ~* $1",
|
||||
" and scalar_type != -2147483647",
|
||||
" limit 400000",
|
||||
));
|
||||
let pgclient = crate::create_connection(pgconf).await?;
|
||||
|
||||
Reference in New Issue
Block a user