use sbol_db_app::{DateField, FacetedSearch};
const PREFIXES: &str = "\
PREFIX sbol2: <http://sbols.org/v2#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX ncbi: <http://www.ncbi.nlm.nih.gov#>
PREFIX synbiohub: <http://synbiohub.org#>
PREFIX sbh: <http://wiki.synbiohub.org/wiki/Terms/synbiohub#>
PREFIX igem: <http://wiki.synbiohub.org/wiki/Terms/igem#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX prov: <http://www.w3.org/ns/prov#>
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX cello: <http://cellocad.org/Terms/cello#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX purl: <http://purl.obolibrary.org/obo/>
PREFIX biopax: <http://www.biopax.org/release/biopax-level3.owl#>
PREFIX so: <http://identifiers.org/so/>
PREFIX bench: <http://wiki.synbiohub.org/wiki/Terms/benchling#>
PREFIX genbank: <http://www.ncbi.nlm.nih.gov/genbank#>
";
pub fn count(type_short_name: &str) -> String {
let ty = sanitize_local_name(type_short_name);
format!(
"{PREFIXES}\nSELECT (COUNT(DISTINCT ?subject) AS ?count) WHERE {{\n ?subject a sbol2:{ty} .\n}}"
)
}
pub fn root_collections() -> String {
format!(
"{PREFIXES}
SELECT ?Collection ?name ?description ?displayId ?version WHERE {{
?Collection a sbol2:Collection .
FILTER NOT EXISTS {{ ?otherCollection sbol2:member ?Collection }}
OPTIONAL {{ ?Collection dcterms:title ?name . }}
OPTIONAL {{ ?Collection sbol2:displayId ?displayId . }}
OPTIONAL {{ ?Collection dcterms:description ?description . }}
OPTIONAL {{ ?Collection sbol2:version ?version }}
}}"
)
}
pub fn sub_collections(parent_uri: &str) -> String {
let parent = iri(parent_uri);
format!(
"{PREFIXES}
SELECT ?Collection ?name ?description ?displayId ?version WHERE {{
?Collection a sbol2:Collection .
{parent} sbol2:member ?Collection .
OPTIONAL {{ ?Collection dcterms:title ?name . }}
OPTIONAL {{ ?Collection sbol2:displayId ?displayId . }}
OPTIONAL {{ ?Collection dcterms:description ?description . }}
OPTIONAL {{ ?Collection sbol2:version ?version }}
FILTER NOT EXISTS {{ {parent} sbol2:member ?otherCollection . ?otherCollection sbol2:member ?Collection }}
}}"
)
}
pub fn metadata(uri: &str) -> String {
let subject = iri(uri);
format!(
"{PREFIXES}
SELECT DISTINCT ?persistentIdentity ?displayId ?version ?name ?description ?type WHERE {{
{subject} a ?type .
OPTIONAL {{ {subject} sbol2:persistentIdentity ?persistentIdentity . }}
OPTIONAL {{ {subject} sbol2:displayId ?displayId . }}
OPTIONAL {{ {subject} sbol2:version ?version . }}
OPTIONAL {{ {subject} dcterms:title ?name . }}
OPTIONAL {{ {subject} dcterms:description ?description . }}
}}"
)
}
pub fn metadata_of(subjects: &[String]) -> String {
let values = subjects
.iter()
.map(|s| iri(s))
.collect::<Vec<_>>()
.join(" ");
format!(
"{PREFIXES}
SELECT DISTINCT ?subject ?displayId ?version ?name ?description ?type WHERE {{
VALUES ?subject {{ {values} }}
?subject a ?type .
OPTIONAL {{ ?subject sbol2:displayId ?displayId . }}
OPTIONAL {{ ?subject sbol2:version ?version . }}
OPTIONAL {{ ?subject dcterms:title ?name . }}
OPTIONAL {{ ?subject dcterms:description ?description . }}
}}"
)
}
pub fn owned_by(user_uri: &str) -> String {
let owner = iri(user_uri);
format!(
"{PREFIXES}
SELECT DISTINCT ?subject ?displayId ?version ?name ?description ?type WHERE {{
?subject a sbol2:Collection .
?subject a ?type .
?subject sbh:ownedBy {owner} .
FILTER NOT EXISTS {{ ?otherCollection sbol2:member ?subject }}
OPTIONAL {{ ?subject sbol2:displayId ?displayId . }}
OPTIONAL {{ ?subject sbol2:version ?version . }}
OPTIONAL {{ ?subject dcterms:title ?name . }}
OPTIONAL {{ ?subject dcterms:description ?description . }}
}}"
)
}
pub fn uses(uri: &str, count_only: bool) -> String {
let target = iri(uri);
let criteria = format!(
"{{ ?subject ?p {target} }} UNION {{ ?subject ?p ?use . ?use ?useP {target} }} .\n \
FILTER(?useP != <http://wiki.synbiohub.org/wiki/Terms/synbiohub#topLevel>)"
);
search_template(&criteria, count_only, None, None)
}
pub fn twins(uri: &str, count_only: bool) -> String {
let target = iri(uri);
let criteria = format!(
"?subject sbol2:sequence ?seq .\n \
?seq sbol2:elements ?elements .\n \
{target} a sbol2:ComponentDefinition .\n \
{target} sbol2:sequence ?seq2 .\n \
?seq2 sbol2:elements ?elements2 .\n \
FILTER(?subject != {target} && ?elements = ?elements2)"
);
search_template(&criteria, count_only, None, None)
}
pub fn faceted(query: &FacetedSearch, count_only: bool) -> String {
let criteria = faceted_criteria(query);
let (limit, offset) = if count_only {
(None, None)
} else {
(Some(query.effective_limit()), Some(query.offset))
};
search_template(&criteria, count_only, limit, offset)
}
fn faceted_criteria(query: &FacetedSearch) -> String {
let mut criteria = String::new();
if let Some(class) = &query.class {
criteria.push_str(&format!(" {} a {} .\n", "?subject", iri(class)));
}
if let Some(collection) = &query.collection_member {
criteria.push_str(&format!(
" {} sbol2:member ?subject .\n",
iri(collection)
));
}
for (predicate, object) in &query.predicate_eq {
criteria.push_str(&format!(
" ?subject {} {} .\n",
term(predicate),
term(object)
));
}
if let Some(field) = query.date_field {
let (var, predicate) = match field {
DateField::Created => ("?cdate", "dcterms:created"),
DateField::Modified => ("?mdate", "dcterms:modified"),
};
criteria.push_str(&format!(" ?subject {predicate} {var} .\n"));
if let Some(before) = &query.date_before {
criteria.push_str(&format!(
" FILTER (xsd:dateTime({var}) <= \"{before}T23:59:59Z\"^^xsd:dateTime)\n"
));
}
if let Some(after) = &query.date_after {
criteria.push_str(&format!(
" FILTER (xsd:dateTime({var}) >= \"{after}T00:00:00Z\"^^xsd:dateTime)\n"
));
}
}
criteria
}
fn search_template(
criteria: &str,
count_only: bool,
limit: Option<usize>,
offset: Option<usize>,
) -> String {
if count_only {
return format!(
"{PREFIXES}
SELECT (COUNT(DISTINCT ?subject) AS ?count) WHERE {{
{criteria}
?subject a ?type .
?subject sbh:topLevel ?subject .
OPTIONAL {{ ?subject sbol2:displayId ?displayId . }}
OPTIONAL {{ ?subject sbol2:version ?version . }}
OPTIONAL {{ ?subject dcterms:title ?name . }}
OPTIONAL {{ ?subject dcterms:description ?description . }}
}}"
);
}
let limit = limit.map(|l| format!("\nLIMIT {l}")).unwrap_or_default();
let offset = offset
.filter(|o| *o > 0)
.map(|o| format!("\nOFFSET {o}"))
.unwrap_or_default();
format!(
"{PREFIXES}
SELECT DISTINCT ?subject ?displayId ?version ?name ?description ?type ?sbolType ?role WHERE {{
{criteria}
?subject a ?type .
?subject sbh:topLevel ?subject .
OPTIONAL {{ ?subject sbol2:displayId ?displayId . }}
OPTIONAL {{ ?subject sbol2:version ?version . }}
OPTIONAL {{ ?subject dcterms:title ?name . }}
OPTIONAL {{ ?subject dcterms:description ?description . }}
OPTIONAL {{ ?subject sbol2:type ?sbolType . FILTER(STRSTARTS(str(?sbolType),'http://www.biopax.org/release/biopax-level3.owl')) }}
OPTIONAL {{ ?subject sbol2:role ?role . FILTER(STRSTARTS(str(?role),'http://identifiers.org/so/')) }}
}}{limit}{offset}"
)
}
fn iri(value: &str) -> String {
format!("<{value}>")
}
fn term(value: &str) -> String {
if value.starts_with("http://") || value.starts_with("https://") {
iri(value)
} else {
value.to_owned()
}
}
fn sanitize_local_name(value: &str) -> String {
value
.chars()
.filter(|c| c.is_ascii_alphanumeric() || *c == '_')
.collect()
}