1use crate::{
2 fhir::{CachePolicy, FHIRRepository, ResourceHistoryValue},
3 pg::{
4 PGConnection, StoreError,
5 pending::{self, PendingRows},
6 transaction::{commit_transaction, create_transaction},
7 },
8 types::{FHIRMethod, SupportedFHIRVersions},
9 utilities,
10};
11use haste_fhir_client::{
12 request::HistoryRequest,
13 url::{ParsedParameter, ParsedParameters},
14};
15use haste_fhir_model::r4::{
16 datetime::parse_datetime,
17 generated::{
18 resources::{Resource, ResourceType},
19 terminology::IssueType,
20 },
21 sqlx::FHIRJson,
22};
23use haste_fhir_operation_error::OperationOutcomeError;
24use haste_jwt::{ProjectId, ResourceId, TenantId, VersionId, claims::UserTokenClaims};
25use moka::future::Cache;
26use sqlx::{PgExecutor, Postgres, QueryBuilder, Row, query_builder::Separated};
27use std::{collections::HashMap, sync::Arc};
28use tokio::sync::Mutex;
29
30#[derive(Debug)]
31struct ReturnVersionedResource {
32 resource: Resource,
33 version_id: VersionId,
34}
35
36#[derive(sqlx::FromRow, Debug)]
37struct HistoryValue {
38 pub resource: FHIRJson<Resource>,
39 pub request_method: String,
40}
41
42async fn read_version_ids_from_cache<'a>(
43 cache: &Cache<VersionId, Resource>,
44 version_ids: &'a [&VersionId],
45) -> (Vec<Resource>, Vec<&'a VersionId>) {
46 let mut remaining_version_ids = vec![];
47 let mut cached_resources = vec![];
48 for version_id in version_ids {
49 if let Some(resource) = cache.get(*version_id).await {
50 cached_resources.push(resource);
51 } else {
52 remaining_version_ids.push(*version_id);
53 }
54 }
55
56 (cached_resources, remaining_version_ids)
57}
58
59impl FHIRRepository for PGConnection {
60 async fn create(
61 &self,
62 tenant: &TenantId,
63 project: &ProjectId,
64 author: &UserTokenClaims,
65 fhir_version: &SupportedFHIRVersions,
66 mut resource: Resource,
67 ) -> Result<Resource, OperationOutcomeError> {
68 utilities::set_resource_id(&mut resource, None)?;
69 utilities::set_resource_meta(&mut resource, &author.resource_type, &author.sub)?;
70 write_resource(
71 self,
72 tenant,
73 project,
74 author,
75 fhir_version,
76 resource,
77 false,
78 "POST",
79 FHIRMethod::Create,
80 )
81 .await
82 }
83
84 async fn delete(
85 &self,
86 tenant: &TenantId,
87 project: &ProjectId,
88 author: &UserTokenClaims,
89 fhir_version: &SupportedFHIRVersions,
90 mut resource: Resource,
91 id: &str,
92 ) -> Result<Resource, OperationOutcomeError> {
93 utilities::set_resource_id(&mut resource, Some(id.to_string()))?;
94 utilities::set_resource_meta(&mut resource, &author.resource_type, &author.sub)?;
95 write_resource(
96 self,
97 tenant,
98 project,
99 author,
100 fhir_version,
101 resource,
102 true,
103 "DELETE",
104 FHIRMethod::Delete,
105 )
106 .await
107 }
108
109 async fn update(
110 &self,
111 tenant: &TenantId,
112 project: &ProjectId,
113 author: &UserTokenClaims,
114 fhir_version: &SupportedFHIRVersions,
115 mut resource: Resource,
116 id: &str,
117 ) -> Result<Resource, OperationOutcomeError> {
118 utilities::set_resource_id(&mut resource, Some(id.to_string()))?;
119 utilities::set_resource_meta(&mut resource, &author.resource_type, &author.sub)?;
120 write_resource(
121 self,
122 tenant,
123 project,
124 author,
125 fhir_version,
126 resource,
127 false,
128 "PUT",
129 FHIRMethod::Update,
130 )
131 .await
132 }
133
134 async fn read_by_version_ids(
135 &self,
136 tenant_id: &TenantId,
137 project_id: &ProjectId,
138 version_ids: &[&VersionId],
139 cache_policy: CachePolicy,
140 ) -> Result<Vec<Resource>, OperationOutcomeError> {
141 if version_ids.is_empty() {
142 return Ok(Vec::new());
143 }
144
145 let (cached_result, remaining_version_ids) =
146 read_version_ids_from_cache(self.cache(), version_ids).await;
147
148 if remaining_version_ids.is_empty() {
149 return Ok(cached_result);
150 }
151
152 match self {
153 PGConnection::Pool(pool, cache) => {
154 let res = read_by_version_ids(pool, tenant_id, project_id, &remaining_version_ids)
155 .await?;
156
157 if cache_policy == CachePolicy::Cache {
158 for v in &res {
159 cache.insert(v.version_id.clone(), v.resource.clone()).await;
160 }
161 }
162
163 Ok(cached_result
164 .into_iter()
165 .chain(res.into_iter().map(|r| r.resource))
166 .collect::<Vec<_>>())
167 }
168 PGConnection::Transaction(tx, cache, pending) => {
169 pending.flush(tx).await?;
170 let mut conn = tx.lock().await;
171 let res =
173 read_by_version_ids(&mut **conn, tenant_id, project_id, &remaining_version_ids)
174 .await?;
175
176 if cache_policy == CachePolicy::Cache {
177 for v in &res {
178 cache.insert(v.version_id.clone(), v.resource.clone()).await;
179 }
180 }
181
182 Ok(cached_result
183 .into_iter()
184 .chain(res.into_iter().map(|r| r.resource))
185 .collect::<Vec<_>>())
186 }
187 }
188 }
189
190 async fn read_latest(
191 &self,
192 tenant_id: &TenantId,
193 project_id: &ProjectId,
194 resource_type: &ResourceType,
195 resource_id: &ResourceId,
196 ) -> Result<Option<Resource>, OperationOutcomeError> {
197 match self {
198 PGConnection::Pool(pool, _) => {
199 let res =
200 read_latest(pool, tenant_id, project_id, resource_type, resource_id).await?;
201 Ok(res)
202 }
203 PGConnection::Transaction(tx, _, pending) => {
204 pending.flush(tx).await?;
205 let mut conn = tx.lock().await;
206 read_latest(
208 &mut **conn,
209 tenant_id,
210 project_id,
211 resource_type,
212 resource_id,
213 )
214 .await
215 }
216 }
217 }
218
219 async fn history(
220 &self,
221 tenant_id: &TenantId,
222 project_id: &ProjectId,
223 request: &HistoryRequest,
224 ) -> Result<Vec<ResourceHistoryValue>, OperationOutcomeError> {
225 match self {
226 PGConnection::Pool(pool, _) => history(pool, tenant_id, project_id, request).await,
227 PGConnection::Transaction(tx, _, pending) => {
228 pending.flush(tx).await?;
229 let mut conn = tx.lock().await;
230 history(&mut **conn, tenant_id, project_id, request).await
232 }
233 }
234 }
235
236 fn in_transaction(&self) -> bool {
237 matches!(self, PGConnection::Transaction(_tx, _, _))
238 }
239
240 async fn transaction(&self, is_updating_sequence: bool) -> Result<Self, OperationOutcomeError> {
241 let tx = create_transaction(self, is_updating_sequence).await?;
242 let pending = match self {
243 PGConnection::Transaction(_, _, pending) => pending.clone(),
244 PGConnection::Pool(_, _) => PendingRows::new(),
245 };
246 Ok(PGConnection::Transaction(tx, self.cache().clone(), pending))
247 }
248
249 async fn commit(self) -> Result<(), OperationOutcomeError> {
250 match self {
251 PGConnection::Pool(_pool, _) => Err(StoreError::NotTransaction.into()),
252 PGConnection::Transaction(tx, _, pending) => {
253 pending.flush(&tx).await?;
254 commit_transaction(tx).await
255 }
256 }
257 }
258
259 async fn rollback(self) -> Result<(), OperationOutcomeError> {
260 match self {
261 PGConnection::Pool(_pool, _) => Err(StoreError::NotTransaction.into()),
262 PGConnection::Transaction(tx, _, _pending) => {
263 let conn = Mutex::into_inner(
264 Arc::try_unwrap(tx).map_err(|_e| StoreError::FailedCommitTransaction)?,
265 );
266
267 conn.rollback().await.map_err(StoreError::from)?;
269 Ok(())
270 }
271 }
272 }
273}
274
275#[allow(clippy::too_many_arguments)]
283async fn write_resource(
284 conn: &PGConnection,
285 tenant: &TenantId,
286 project: &ProjectId,
287 author: &UserTokenClaims,
288 fhir_version: &SupportedFHIRVersions,
289 resource: Resource,
290 deleted: bool,
291 request_method: &'static str,
292 fhir_method: FHIRMethod,
293) -> Result<Resource, OperationOutcomeError> {
294 match conn {
295 PGConnection::Pool(_pool, _) => {
296 let tx = create_transaction(conn, true).await?;
297 {
298 let mut c = tx.lock().await;
299 pending::execute(
300 &mut **c,
301 tenant,
302 project,
303 author,
304 fhir_version,
305 &resource,
306 deleted,
307 request_method,
308 fhir_method,
309 )
310 .await?;
311 }
312 commit_transaction(tx).await?;
313 }
314 PGConnection::Transaction(_tx, _, pending) => {
315 pending
316 .push(
317 tenant,
318 project,
319 author,
320 fhir_version,
321 resource.clone(),
322 deleted,
323 request_method,
324 fhir_method,
325 )
326 .await;
327 }
328 }
329
330 Ok(resource)
331}
332
333async fn read_by_version_ids<'a, 'e, E>(
334 executor: E,
335 tenant_id: &'a TenantId,
336 project_id: &'a ProjectId,
337 version_ids: &'a Vec<&'a VersionId>,
338) -> Result<Vec<ReturnVersionedResource>, OperationOutcomeError>
339where
340 E: PgExecutor<'e>,
341{
342 let bound_version_ids: Vec<&str> = version_ids
343 .iter()
344 .map(std::convert::AsRef::as_ref)
345 .collect();
346
347 let rows = sqlx::query(
350 r"
351 SELECT resource, resource_type, version_id
352 FROM resources
353 WHERE tenant = $1 AND project = $2 AND version_id = ANY($3::text[])
354 ",
355 )
356 .bind(tenant_id.as_ref())
357 .bind(project_id.as_ref())
358 .bind(&bound_version_ids)
359 .fetch_all(executor)
360 .await
361 .map_err(StoreError::from)?;
362
363 let mut requested_order: HashMap<&VersionId, usize> = HashMap::with_capacity(version_ids.len());
365 for (index, version_id) in version_ids.iter().enumerate() {
366 requested_order.insert(*version_id, index);
367 }
368
369 let mut response = rows
370 .iter()
371 .map(|row| {
372 let resource_type: ResourceType =
373 row.try_get("resource_type").map_err(StoreError::from)?;
374 let version_id: VersionId = row.try_get("version_id").map_err(StoreError::from)?;
375
376 let raw = row.try_get_raw("resource").map_err(StoreError::from)?;
381 let bytes = raw
382 .as_bytes()
383 .map_err(|e| StoreError::DeserializeError(e.to_string()))?;
384 let resource = resource_type
386 .deserialize(&bytes[1..])
387 .map_err(|e| StoreError::DeserializeError(e.to_string()))?;
388
389 Ok(ReturnVersionedResource {
390 resource,
391 version_id,
392 })
393 })
394 .collect::<Result<Vec<_>, StoreError>>()?;
395
396 response.sort_by_key(|r| {
397 requested_order
398 .get(&r.version_id)
399 .copied()
400 .unwrap_or(usize::MAX)
401 });
402
403 Ok(response)
404}
405
406async fn read_latest<'a, 'e, E>(
407 executor: E,
408 tenant_id: &'a TenantId,
409 project_id: &'a ProjectId,
410 resource_type: &'a ResourceType,
411 resource_id: &'a ResourceId,
412) -> Result<Option<Resource>, OperationOutcomeError>
413where
414 E: PgExecutor<'e>,
415{
416 let response = sqlx::query_as::<_, (FHIRJson<Resource>, bool)>(
417 r"
418 SELECT resource, deleted
419 FROM resources
420 WHERE tenant = $1 AND project = $2 AND id = $3 AND resource_type = $4
421 ORDER BY sequence DESC
422 LIMIT 1
423 ",
424 )
425 .bind(tenant_id.as_ref())
426 .bind(project_id.as_ref())
427 .bind(resource_id.as_ref())
428 .bind(resource_type.as_ref())
429 .fetch_optional(executor)
430 .await
431 .map_err(StoreError::from)?;
432
433 match response {
434 Some((_, true)) | None => Ok(None),
435 Some((json, _)) => Ok(Some(json.0)),
436 }
437}
438
439fn process_history_parameters<'a>(
440 parameters: &'a ParsedParameters,
441 clauses: &mut Separated<'_, 'a, Postgres, &str>,
442) -> Result<(), OperationOutcomeError> {
443 for parameter in parameters.parameters() {
444 match parameter {
445 ParsedParameter::Result(result_param) => {
446 if result_param.name.as_str() == "_since" {
447 if let Some(value) = result_param.value.first() {
448 let date_time = parse_datetime(value.as_str()).map_err(|e| {
449 OperationOutcomeError::fatal(
450 IssueType::invalid(),
451 format!("Invalid _since parameter datetime: {e:?}"),
452 )
453 })?;
454
455 clauses.push(" created_at >= ").push_bind_unseparated(
456 chrono::DateTime::try_from(date_time).map_err(|e| {
457 OperationOutcomeError::fatal(
458 IssueType::invalid(),
459 format!("Invalid _since parameter datetime: {e:?}"),
460 )
461 })?,
462 );
463 }
464 } else {
465 }
467 }
468 ParsedParameter::Resource(_) => {
469 return Err(OperationOutcomeError::fatal(
470 IssueType::not_supported(),
471 format!(
472 "Parameter '{}' is not supported for history requests.",
473 parameter.name()
474 ),
475 ));
476 }
477 }
478 }
479
480 Ok(())
481}
482
483async fn history<'a, 'e, E>(
484 executor: E,
485 tenant: &'a TenantId,
486 project: &'a ProjectId,
487 history_request: &'a HistoryRequest,
488) -> Result<Vec<ResourceHistoryValue>, OperationOutcomeError>
489where
490 E: PgExecutor<'e>,
491{
492 let mut query_builder: QueryBuilder<sqlx::Postgres> =
493 QueryBuilder::new(r"SELECT resource, request_method FROM resources WHERE ");
494
495 let mut clauses = query_builder.separated(" AND ");
496 clauses
497 .push(" tenant = ")
498 .push_bind_unseparated(tenant.as_ref())
499 .push(" project = ")
500 .push_bind_unseparated(project.as_ref());
501
502 let history_parameters = match history_request {
503 HistoryRequest::Instance(history_instance_request) => &history_instance_request.parameters,
504 HistoryRequest::Type(history_type_request) => &history_type_request.parameters,
505 HistoryRequest::System(system_request) => &system_request.parameters,
506 };
507
508 process_history_parameters(history_parameters, &mut clauses)?;
509
510 match history_request {
511 HistoryRequest::Instance(history_instance_request) => {
512 clauses
513 .push(" resource_type = ")
514 .push_bind_unseparated(history_instance_request.resource_type.as_ref())
515 .push(" id = ")
516 .push_bind_unseparated(&history_instance_request.id);
517 }
518 HistoryRequest::Type(history_type_request) => {
519 clauses
520 .push(" resource_type = ")
521 .push_bind_unseparated(history_type_request.resource_type.as_ref());
522 }
523 HistoryRequest::System(_request) => {}
524 }
525
526 let limit = if let Some(ParsedParameter::Result(count_param)) = history_parameters.get("_count")
527 {
528 std::cmp::min(
529 1000,
530 count_param
531 .value
532 .first()
533 .and_then(|v| v.parse::<i64>().ok())
534 .unwrap_or(100),
535 )
536 } else {
537 1000
538 };
539
540 if limit < 0 {
541 return Err(OperationOutcomeError::fatal(
542 IssueType::invalid(),
543 "Invalid _count parameter value. Must be greater than or equal to 0.".to_string(),
544 ));
545 }
546
547 query_builder
548 .push(" ORDER BY sequence DESC LIMIT ")
549 .push_bind(limit);
550
551 if let Some(ParsedParameter::Result(offset_param)) = history_parameters.get("_offset") {
552 let offset = offset_param
553 .value
554 .first()
555 .and_then(|v| v.parse::<i64>().ok())
556 .unwrap_or(0);
557
558 if offset < 0 {
559 return Err(OperationOutcomeError::fatal(
560 IssueType::invalid(),
561 "Invalid _offset parameter value. Must be greater than or equal to 0.".to_string(),
562 ));
563 }
564
565 query_builder.push(" OFFSET ").push_bind(offset);
566 }
567
568 let query = query_builder.build_query_as::<HistoryValue>();
569
570 let result: Vec<HistoryValue> = query.fetch_all(executor).await.map_err(StoreError::from)?;
571
572 Ok(result
573 .into_iter()
574 .map(|r| ResourceHistoryValue {
575 resource: r.resource.0,
576 request_method: r.request_method,
577 })
578 .collect::<Vec<_>>())
579}