Skip to content

Commit

Permalink
Add processing algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
jmandel committed Nov 1, 2023
1 parent 9e95728 commit 2a7c3b8
Showing 1 changed file with 135 additions and 0 deletions.
135 changes: 135 additions & 0 deletions input/pagecontent/StructureDefinition-ViewDefinition-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,138 @@ Another use case may be for users to select database-specific numeric types.

Behavior is undefined and left to the runner if the expression returns a value
that is incompatible with the underlying database type.

### Processing Algorithm (Model)

The following description provides an algorithm for how to process a FHIR
resource as input for a `ViewDefinition`. Implementations do not need to follow
this algorithm directly, but their outputs should be consistent with what this
model produces.

#### Validate Columns

**Inputs**
* `V`: a `ViewDefinition` to validate

**Purpose**: This step will ensure that column names are unique across `V`, and
that any `unionAll` branches produce the same columns.

1. Call `ValidateColumns([], V)` according to the recursive step below.

##### `ValidateColumns(S, C)` (Recursive Step)

**Inputs**
* `S`: a single Selection Structure
* `C`: a list of Columns

**Outputs**
* `Ret`: a list of Columns

**Errors**
* Column Already Defined
* Union Branches Inconsistent

**Purpose:** This step will ensure that column names are unique across `S` and disjoint from `C`

0. Initialize `Ret` to equal `C`

1. For each Column `col` in `S.column[]`
* If a Column with name `col.name` already exists in `Ret`, throw "Column Already Defined"
* Otherwise, append `col` to `Ret`

2. For each Selection Structure `sel` in `S.select[]`
* For each Column `c` in `Validate(sel, Ret)`
* If a Column with name `c.name` already exists in `Ret`, throw "Column Already Defined"
* Otherwise, append `c` to the end of `Ret`

3. If `S.unionAll[]` is present
1. Define `u0` as `Validate(S.unionAll[0], Ret)`
2. For each Selection Structure `sel` in `S.unionAll[]`
* Define `u` as `ValidateColumns(sel, Ret)`
* If the list of names from `u0` is different from the list of names from `u`, throw "Union Branches Inconsistent"
* Otherwise, continue
3. For each Column `col` in `u0`
* Append `col` to `Ret`

5. Return `Ret`

#### Process a Resource

**Inputs**
* `V`: a `ViewDefinition`
* `R`: a FHIR Resource to process with `V`

**Emits:** one output row at a time

1. If `V.where` is defined
* Evaluate `fhirpath(V.where.path, R)` to determine whether `R` is a candidate for `V`
* If `R` is not a candidate for `V`, return immediately without emitting any rows
* Otherwise, continue
2. Emit all rows from `Process(S, V)`

##### `Process(S, N)` (Recursive Step)

**Inputs**
* `S`: a Selection Structure
* `N`: a Node (element) from a FHIR resource

**Errors**
* Multiple values found but not expected for column

**Emits:** One output row at a time

**Purpose:** This step will take sets of "partial rows" (i.e., sets of incomplete column bindings from the various clauses of `V`) and combine them to emit complete rows. For example, if there are two sets of partial rows:

* `[{"a": 1},{"a": 2}]` with bindings for the variable `a`
* `[{"b": 3},{"b": 4}]` with bindings for the variable `b`

Then the Cartesian product of these sets consists of four complete rows:

```js
[
{"a": 1, "b": 3},
{"a": 1, "b": 4},
{"a": 2, "b": 3},
{"a": 2, "b": 4}
]
```

1. Define a list of Nodes `foci` as
* If `S.forEach` is defined: `fhirpath(S.forEach, N)`
* Else if `S.forEachOrNull` is defined: `fhirpath(S.forEachOrNull, N)`
* Otherwise: `[N]` (a list with just the input node)

2. For each element `f` of `foci`
1. Initialize an empty list `parts` (each element of `parts` will be a list of partial rows)
2. Process Columns:
* For each Column `col` of `S.column`, define `val` as `fhirpath(col.path, f)`
1. Define `b` as a row whose column named `col.name` takes the value
* If `val` was the empty set: `null`
* Else if `val` has a single element `e`: `e`
* Else if `col.collection` is true: `val`
* Else: throw "Multiple values found but not expected for column"
2. Append `[b]` to `parts`
* (Note: append a list so the final element of `parts` is now a list containing the single row `b`).
3. Process Selects:
* For each selection structure `sel` of `S.select`
1. Define `rows` as the collection of all rows emitted by `Process(sel, f)`
2. Append `rows` to `parts`
* (Note: do not append the elements but the whole list, so the final element of `parts` is now the list `rows`)
4. Process UnionAlls:
1. Initialize `urows` as an empty list of rows
2. For each selection structure `u` of `S.unionAll`
* For each row `r` in `Process(u, f)`
* Append `r` to `urows`
3. Append `urows` to `parts`
* (Note: do not append the elements but the whole list, so the final element of `parts` is now the list `urows`)
5. For every list of partial rows `prows` in the cartesian product of `parts`
1. Initialize a blank row `r`
2. For each partial row `p` in `prows`
* Add `p`'s column bindings to the row `r`
3. Emit the row `r`
3. If `foci` is an empty list and `S.forEachOrNull` is defined
* (Note: when this condition is met, no rows have been emitted so far)
1. Initialize a blank row `r`
2. For each Column `c` in `ValidateColumns(V, [])`
* Bind the column `c.name` to `null` in the row `r`
3. Emit the row `r`

0 comments on commit 2a7c3b8

Please sign in to comment.