use crate::create_connection; use crate::worker::PgQueue; use crate::ErrConv; use err::Error; use netpod::log::*; use netpod::ChannelArchiver; use netpod::ChannelSearchQuery; use netpod::ChannelSearchResult; use netpod::ChannelSearchSingleResult; use netpod::Database; use netpod::NodeConfigCached; use netpod::ScalarType; use netpod::Shape; use serde_json::Value as JsVal; use tokio_postgres::Client as PgClient; pub async fn search_channel_databuffer( query: ChannelSearchQuery, backend: &str, node_config: &NodeConfigCached, ) -> Result { let empty = if !query.name_regex.is_empty() { false } else if !query.source_regex.is_empty() { false } else if !query.description_regex.is_empty() { false } else { true }; if empty { let ret = ChannelSearchResult { channels: Vec::new() }; return Ok(ret); } let sql = concat!( "select", " channel_id, channel_name, source_name,", " dtype, shape, unit, description, channel_backend", " from searchext($1, $2, $3, $4)", " where channel_backend = $5" ); let (pg, _pgjh) = create_connection(&node_config.node_config.cluster.database).await?; let rows = pg .query( sql, &[ &query.name_regex, &query.source_regex, &query.description_regex, &"asc", &backend, ], ) .await .err_conv()?; let mut res = Vec::new(); for row in rows { let shapedb: Option = row.get(4); let shape = match &shapedb { Some(top) => match top { serde_json::Value::Null => Vec::new(), serde_json::Value::Array(items) => { let mut a = Vec::new(); for item in items { match item { serde_json::Value::Number(n) => match n.as_i64() { Some(n) => { a.push(n as u32); } None => return Err(Error::with_msg(format!("can not understand shape {:?}", shapedb))), }, _ => return Err(Error::with_msg(format!("can not understand shape {:?}", shapedb))), } } a } _ => return Err(Error::with_msg(format!("can not understand shape {:?}", shapedb))), }, None => Vec::new(), }; let ty: String = row.get(3); let k = ChannelSearchSingleResult { backend: row.get(7), name: row.get(1), series: row.get::<_, i64>(0) as u64, source: row.get(2), ty, shape: shape, unit: row.get(5), description: row.get(6), is_api_0: None, }; res.push(k); } let res = if let Some(backend) = query.backend.as_ref() { res.into_iter().filter(|x| x.backend == *backend).collect() } else { res }; let ret = ChannelSearchResult { channels: res }; Ok(ret) } pub(super) async fn search_channel_scylla( query: ChannelSearchQuery, pgc: &PgClient, ) -> Result { let empty = if !query.name_regex.is_empty() { false } else { true }; if empty { let ret = ChannelSearchResult { channels: Vec::new() }; return Ok(ret); } let ch_kind: i16 = query.kind.to_db_i16(); let (cb1, cb2) = if let Some(x) = query.backend.as_ref() { (false, x.as_str()) } else { (false, "----------") }; let regop = if query.icase { "~*" } else { "~" }; let sql = &format!( concat!( "select", " series, facility, channel, scalar_type, shape_dims", " from series_by_channel", " where kind = $1", " and channel {} $2", " and ($3 or facility = $4)", " limit 400000", ), regop ); let params: &[&(dyn tokio_postgres::types::ToSql + Sync)] = &[&ch_kind, &query.name_regex, &cb1, &cb2]; info!("search_channel_scylla {:?}", params); let rows = pgc.query(sql, params).await.err_conv()?; let mut res = Vec::new(); for row in rows { let series: i64 = row.get(0); let series = series as u64; let backend: String = row.get(1); let channel: String = row.get(2); let a: i32 = row.get(3); // TODO count the failure cases if let Ok(scalar_type) = ScalarType::from_scylla_i32(a) { let a: Vec = row.get(4); if let Ok(shape) = Shape::from_scylla_shape_dims(&a) { let k = ChannelSearchSingleResult { backend, name: channel, series, source: "".into(), ty: scalar_type.to_variant_str().into(), shape: shape.to_scylla_vec().into_iter().map(|x| x as u32).collect(), unit: "".into(), description: "".into(), is_api_0: None, }; res.push(k); } else { netpod::log::warn!("unknown shape {a:?}"); } } else { netpod::log::warn!("unknown scalar_type {a:?}"); } } let ret = ChannelSearchResult { channels: res }; Ok(ret) } async fn search_channel_archeng( query: ChannelSearchQuery, backend: String, _conf: &ChannelArchiver, database: &Database, ) -> Result { // Channel archiver provides only channel name. Also, search criteria are currently ANDed. // Therefore search only if user only provides a name criterion. let empty = if !query.source_regex.is_empty() { true } else if !query.description_regex.is_empty() { true } else if query.name_regex.is_empty() { true } else { false }; if empty { let ret = ChannelSearchResult { channels: Vec::new() }; return Ok(ret); } let sql = format!(concat!( "select c.name, c.config", " from channels c", " where c.name ~* $1", " order by c.name", " limit 100" )); let (cl, _pgjh) = create_connection(database).await?; let rows = cl.query(sql.as_str(), &[&query.name_regex]).await.err_conv()?; let mut res = Vec::new(); for row in rows { let name: String = row.get(0); let config: JsVal = row.get(1); let st = match config.get("scalarType") { Some(k) => match k { JsVal::String(k) => match k.as_str() { "U8" => "Uint8", "U16" => "Uint16", "U32" => "Uint32", "U64" => "Uint64", "I8" => "Int8", "I16" => "Int16", "I32" => "Int32", "I64" => "Int64", "F32" => "Float32", "F64" => "Float64", _ => k, } .into(), _ => "", }, None => "", }; let shape = match config.get("shape") { Some(k) => match k { JsVal::String(k) => { if k == "Scalar" { Vec::new() } else { return Err(Error::with_msg_no_trace(format!( "search_channel_archeng can not understand {:?}", config ))); } } JsVal::Object(k) => match k.get("Wave") { Some(k) => match k { JsVal::Number(k) => { vec![k.as_i64().unwrap_or(u32::MAX as i64) as u32] } _ => { return Err(Error::with_msg_no_trace(format!( "search_channel_archeng can not understand {:?}", config ))); } }, None => { return Err(Error::with_msg_no_trace(format!( "search_channel_archeng can not understand {:?}", config ))); } }, _ => { return Err(Error::with_msg_no_trace(format!( "search_channel_archeng can not understand {:?}", config ))); } }, None => Vec::new(), }; let k = ChannelSearchSingleResult { backend: backend.clone(), name, // TODO provide a unique id also within this backend: series: 0, source: String::new(), ty: st.into(), shape, unit: String::new(), description: String::new(), is_api_0: None, }; res.push(k); } let ret = ChannelSearchResult { channels: res }; Ok(ret) } pub async fn search_channel( query: ChannelSearchQuery, pgqueue: &PgQueue, ncc: &NodeConfigCached, ) -> Result { let backend = &ncc.node_config.cluster.backend; let pgconf = &ncc.node_config.cluster.database; let mut query = query; query.backend = Some(backend.into()); if let Some(_scyconf) = ncc.node_config.cluster.scylla_st() { pgqueue .search_channel_scylla(query) .await .map_err(|e| Error::with_msg_no_trace(format!("db worker error {e}")))? // search_channel_scylla(query, backend, pgconf).await } else if let Some(conf) = ncc.node.channel_archiver.as_ref() { search_channel_archeng(query, backend.clone(), conf, pgconf).await } else if let Some(_conf) = ncc.node.archiver_appliance.as_ref() { // TODO err::todoval() } else { search_channel_databuffer(query, backend, ncc).await } }