use core::fmt::Write as _;
use cosmian_kmip::{
kmip_0::kmip_types::State,
kmip_2_1::{
kmip_attributes::Attributes,
kmip_types::{LinkedObjectIdentifier::TextString, NameType, UniqueIdentifier},
},
};
pub(super) trait PlaceholderTrait {
const NEEDS_INTEGER_CAST: bool = true;
const JSON_FN_EACH_ELEMENT: &'static str = "json_each";
const JSON_FN_EXTRACT_PATH: &'static str = "json_extract";
const JSON_FN_EXTRACT_TEXT: &'static str = "json_extract";
#[allow(dead_code)]
const JSON_ARRAY_LENGTH: &'static str = "json_array_length";
const JSON_NODE_LINK: &'static str = "'$.Link'";
const JSON_TEXT_LINK_OBJ_ID: &'static str = "'$.LinkedObjectIdentifier'";
const JSON_TEXT_LINK_TYPE: &'static str = "'$.LinkType'";
const JSON_NODE_NAME: &'static str = "'$.Name'";
const JSON_TEXT_NAME_VALUE: &'static str = "'$.NameValue'";
const JSON_TEXT_NAME_TYPE: &'static str = "'$.NameType'";
const TYPE_INTEGER: &'static str = "INTEGER";
#[must_use]
fn binder(param_number: usize) -> String {
format!("${param_number}")
}
#[must_use]
fn links_additional_rq_from() -> Option<String> {
Some(format!(
"{}({}(objects.attributes, {})) as links",
Self::JSON_FN_EACH_ELEMENT,
Self::JSON_FN_EXTRACT_PATH,
Self::JSON_NODE_LINK
))
}
#[must_use]
fn names_additional_rq_from() -> Option<String> {
Some(format!(
"{}({}(objects.attributes, {})) as names",
Self::JSON_FN_EACH_ELEMENT,
Self::JSON_FN_EXTRACT_PATH,
Self::JSON_NODE_NAME
))
}
#[must_use]
fn link_evaluation(node_name: &str, node_value: &str) -> String {
format!(
"{}(links.value, {}) = {}",
Self::JSON_FN_EXTRACT_TEXT,
node_name, node_value )
}
#[must_use]
fn name_evaluation(node_name: &str, node_value: &str) -> String {
format!(
"{}(names.value, {}) = {}",
Self::JSON_FN_EXTRACT_TEXT,
node_name, node_value )
}
#[must_use]
fn format_json_path(attribute_names: &[&str]) -> String {
"$.".to_owned() + &*attribute_names.join(".")
}
#[must_use]
fn extract_attribute_path(attribute_names: &[&str]) -> String {
format!(
"{}(objects.attributes, '{}')",
Self::JSON_FN_EXTRACT_TEXT,
Self::format_json_path(attribute_names)
)
}
#[must_use]
fn extract_object_type() -> String {
Self::extract_attribute_path(&["ObjectType"])
}
}
pub(super) enum MySqlPlaceholder {}
impl PlaceholderTrait for MySqlPlaceholder {
const JSON_ARRAY_LENGTH: &'static str = "JSON_LENGTH";
const JSON_FN_EACH_ELEMENT: &'static str = "json_search";
const JSON_TEXT_LINK_OBJ_ID: &'static str = "'$[*].LinkedObjectIdentifier'";
const JSON_TEXT_LINK_TYPE: &'static str = "'$[*].LinkType'";
const JSON_TEXT_NAME_TYPE: &'static str = "'$[*].NameType'";
const JSON_TEXT_NAME_VALUE: &'static str = "'$[*].NameValue'";
const NEEDS_INTEGER_CAST: bool = false;
const TYPE_INTEGER: &'static str = "UNSIGNED INTEGER";
fn binder(_param_number: usize) -> String {
"?".to_owned()
}
fn links_additional_rq_from() -> Option<String> {
None
}
fn names_additional_rq_from() -> Option<String> {
None
}
fn extract_attribute_path(attribute_names: &[&str]) -> String {
format!(
"JSON_UNQUOTE(JSON_EXTRACT(objects.attributes, '{}'))",
Self::format_json_path(attribute_names)
)
}
fn link_evaluation(node_name: &str, node_value: &str) -> String {
format!(
"{}({}(objects.attributes, {}), 'one', {}, NULL, {}) IS NOT NULL",
Self::JSON_FN_EACH_ELEMENT,
Self::JSON_FN_EXTRACT_PATH,
Self::JSON_NODE_LINK,
node_value,
node_name,
)
}
fn name_evaluation(node_name: &str, node_value: &str) -> String {
format!(
"{}({}(objects.attributes, {}), 'one', {}, NULL, {}) IS NOT NULL",
Self::JSON_FN_EACH_ELEMENT,
Self::JSON_FN_EXTRACT_PATH,
Self::JSON_NODE_NAME,
node_value,
node_name,
)
}
}
pub(super) enum PgSqlPlaceholder {}
impl PlaceholderTrait for PgSqlPlaceholder {
const JSON_ARRAY_LENGTH: &'static str = "jsonb_array_length";
const JSON_FN_EACH_ELEMENT: &'static str = "jsonb_array_elements";
const JSON_FN_EXTRACT_PATH: &'static str = "jsonb_extract_path";
const JSON_FN_EXTRACT_TEXT: &'static str = "jsonb_extract_path_text";
const JSON_NODE_LINK: &'static str = "'Link'";
const JSON_NODE_NAME: &'static str = "'Name'";
const JSON_TEXT_LINK_OBJ_ID: &'static str = "'LinkedObjectIdentifier'";
const JSON_TEXT_LINK_TYPE: &'static str = "'LinkType'";
const JSON_TEXT_NAME_TYPE: &'static str = "'NameType'";
const JSON_TEXT_NAME_VALUE: &'static str = "'NameValue'";
const TYPE_INTEGER: &'static str = "BIGINT";
fn extract_attribute_path(attribute_names: &[&str]) -> String {
if attribute_names.is_empty() {
return "(objects.attributes)::jsonb".to_owned();
}
let mut path = String::from("(objects.attributes)::jsonb");
if let Some((last, heads)) = attribute_names.split_last() {
for key in heads {
let _ = write!(path, " -> '{key}'");
}
let _ = write!(path, " ->> '{last}'");
}
path
}
fn extract_object_type() -> String {
"(objects.attributes)::jsonb ->> 'ObjectType'".to_owned()
}
}
pub(super) enum SqlitePlaceholder {}
impl PlaceholderTrait for SqlitePlaceholder {}
#[derive(Debug, Clone, PartialEq)]
pub(super) enum LocateParam {
Text(String),
I64(i64),
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct LocateQuery {
pub(super) sql: String,
pub(super) params: Vec<LocateParam>,
}
struct LocateQueryBuilder<P: PlaceholderTrait> {
params: Vec<LocateParam>,
_phantom: core::marker::PhantomData<P>,
}
impl<P: PlaceholderTrait> LocateQueryBuilder<P> {
const fn new() -> Self {
Self {
params: Vec::new(),
_phantom: core::marker::PhantomData,
}
}
fn bind_text(&mut self, value: impl Into<String>) -> String {
self.params.push(LocateParam::Text(value.into()));
P::binder(self.params.len())
}
fn bind_i64(&mut self, value: i64) -> String {
self.params.push(LocateParam::I64(value));
P::binder(self.params.len())
}
fn finish(self, sql: String) -> LocateQuery {
LocateQuery {
sql,
params: self.params,
}
}
}
pub(super) fn query_from_attributes<P: PlaceholderTrait>(
attributes: Option<&Attributes>,
state: Option<State>,
user: &str,
user_must_be_owner: bool,
vendor_id: &str,
) -> LocateQuery {
let mut qb = LocateQueryBuilder::<P>::new();
let mut query =
"SELECT DISTINCT objects.id as id, objects.state as state, objects.attributes as attrs \
FROM objects"
.to_owned();
if let Some(attributes) = attributes {
let tags = attributes.get_tags(vendor_id);
let tags_len = tags.len();
if tags_len > 0 {
let tag_placeholders = tags
.iter()
.map(|t| qb.bind_text(t.clone()))
.collect::<Vec<String>>()
.join(", ");
let tags_len_i64 = i64::try_from(tags_len).unwrap_or(0);
let tags_len_placeholder = qb.bind_i64(tags_len_i64);
query = format!(
"{query} INNER JOIN (
SELECT id
FROM tags
WHERE tag IN ({tag_placeholders})
GROUP BY id
HAVING COUNT(DISTINCT tag) = {tags_len_placeholder}
) AS matched_tags
ON objects.id = matched_tags.id"
);
}
}
if !user_must_be_owner {
query = format!(
"{query}\n LEFT JOIN read_access ON objects.id = read_access.id AND \
read_access.userid = {}",
qb.bind_text(user)
);
}
if let Some(attributes) = attributes {
if let Some(links) = &attributes.link {
if !links.is_empty() {
if let Some(additional_rq_from) = P::links_additional_rq_from() {
query = format!("{query}, {additional_rq_from}");
}
}
}
if let Some(names) = &attributes.name {
if !names.is_empty() {
if let Some(additional_rq_from) = P::names_additional_rq_from() {
query = format!("{query}, {additional_rq_from}");
}
}
}
}
if user_must_be_owner {
query = format!("{query} WHERE objects.owner = {}", qb.bind_text(user));
} else {
query = format!(
"{query} WHERE (objects.owner = {} OR read_access.userid = {})",
qb.bind_text(user),
qb.bind_text(user)
);
}
if let Some(state) = state {
let state_s: &'static str = state.into();
query = format!("{query} AND state = {}", qb.bind_text(state_s));
}
#[allow(clippy::collapsible_match)]
if let Some(attributes) = attributes {
if let Some(uid) = &attributes.unique_identifier {
if let UniqueIdentifier::TextString(id) = uid {
query = format!("{query} AND objects.id = {}", qb.bind_text(id.clone()));
}
}
if let Some(object_group) = &attributes.object_group {
query = format!(
"{query} AND {} = {}",
P::extract_attribute_path(&["ObjectGroup"]),
qb.bind_text(object_group.clone())
);
}
if let Some(object_group_member) = attributes.object_group_member {
query = format!(
"{query} AND {} = {}",
P::extract_attribute_path(&["ObjectGroupMember"]),
qb.bind_text(object_group_member.to_string())
);
}
if let Some(cryptographic_algorithm) = attributes.cryptographic_algorithm {
query = format!(
"{query} AND {} = {}",
P::extract_attribute_path(&["CryptographicAlgorithm"]),
qb.bind_text(cryptographic_algorithm.to_string())
);
}
if let Some(cryptographic_length) = attributes.cryptographic_length {
let len_i64 = i64::from(cryptographic_length);
if P::NEEDS_INTEGER_CAST {
query = format!(
"{query} AND CAST ({} AS {}) = {}",
P::extract_attribute_path(&["CryptographicLength"]),
P::TYPE_INTEGER,
qb.bind_i64(len_i64)
);
} else {
query = format!(
"{query} AND {} = {}",
P::extract_attribute_path(&["CryptographicLength"]),
qb.bind_i64(len_i64)
);
}
}
if let Some(key_format_type) = attributes.key_format_type {
query = format!(
"{query} AND {} = {}",
P::extract_attribute_path(&["KeyFormatType"]),
qb.bind_text(key_format_type.to_string())
);
}
if let Some(object_type) = attributes.object_type {
query = format!(
"{query} AND {} = {}",
P::extract_object_type(),
qb.bind_text(object_type.to_string())
);
}
if let Some(app) = &attributes.application_specific_information {
query = format!(
"{query} AND {} = {}",
P::extract_attribute_path(&[
"ApplicationSpecificInformation",
"ApplicationNamespace"
]),
qb.bind_text(app.application_namespace.clone())
);
if let Some(data) = &app.application_data {
query = format!(
"{query} AND {} = {}",
P::extract_attribute_path(&[
"ApplicationSpecificInformation",
"ApplicationData"
]),
qb.bind_text(data.clone())
);
}
}
if let Some(links) = &attributes.link {
for link in links {
query = format!(
"{query} AND {}",
P::link_evaluation(
P::JSON_TEXT_LINK_TYPE,
&qb.bind_text(link.link_type.to_string())
)
);
if let TextString(uid) = &link.linked_object_identifier {
query = format!(
"{query} AND {}",
P::link_evaluation(P::JSON_TEXT_LINK_OBJ_ID, &qb.bind_text(uid.clone()))
);
}
}
}
if let Some(names) = &attributes.name {
for name in names {
query = format!(
"{query} AND {}",
P::name_evaluation(
P::JSON_TEXT_NAME_TYPE,
&qb.bind_text(match &name.name_type {
NameType::UninterpretedTextString => "UninterpretedTextString",
NameType::URI => "URI",
})
)
);
query = format!(
"{query} AND {}",
P::name_evaluation(
P::JSON_TEXT_NAME_VALUE,
&qb.bind_text(name.name_value.clone())
)
);
}
}
}
qb.finish(query)
}