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