|
| 1 | +# Workflow Operations |
| 2 | + |
| 3 | +## Executing the Workflow |
| 4 | + |
| 5 | +The previous sections established the **Relational Workflow Model** and schema design principles. |
| 6 | +Your schema defines *what* entities exist, *how* they depend on each other, and *when* they are created in the workflow. |
| 7 | +**Operations** are the actions that execute this workflow—populating your pipeline with actual data. |
| 8 | + |
| 9 | +In DataJoint, operations fall into two categories: |
| 10 | + |
| 11 | +1. **Manual operations** — Actions initiated *outside* the pipeline using `insert`, `delete`, and occasionally `update` |
| 12 | +2. **Automatic operations** — Pipeline-driven population using `populate` for Imported and Computed tables |
| 13 | + |
| 14 | +The term "manual" does not imply human involvement—it means the operation originates *external to the pipeline*. |
| 15 | +A script that parses instrument files and inserts session records is performing manual operations, even though no human is involved. |
| 16 | +The key distinction is *who initiates the action*: external processes (manual) versus the pipeline's own `populate` mechanism (automatic). |
| 17 | + |
| 18 | +This distinction maps directly to the table tiers introduced in the [Relational Workflow Model](../20-concepts/05-workflows.md): |
| 19 | + |
| 20 | +| Table Tier | How Data Enters | Typical Operations | |
| 21 | +|------------|-----------------|-------------------| |
| 22 | +| **Lookup** | Schema definition (`contents` property) | None—predefined | |
| 23 | +| **Manual** | External to pipeline | `insert`, `delete` | |
| 24 | +| **Imported** | Pipeline-driven acquisition | `populate` | |
| 25 | +| **Computed** | Pipeline-driven computation | `populate` | |
| 26 | + |
| 27 | +## Lookup Tables: Part of the Schema |
| 28 | + |
| 29 | +**Lookup tables are not part of the workflow**—they are part of the schema definition itself. |
| 30 | + |
| 31 | +Lookup tables contain reference data, controlled vocabularies, parameter sets, and configuration values that define the *context* in which the workflow operates. |
| 32 | +This data is: |
| 33 | + |
| 34 | +- Defined in the table class using the `contents` property |
| 35 | +- Automatically present when the schema is activated |
| 36 | +- Shared across all workflow executions |
| 37 | + |
| 38 | +Examples include: |
| 39 | +- Species names and codes |
| 40 | +- Experimental protocols |
| 41 | +- Processing parameter sets |
| 42 | +- Instrument configurations |
| 43 | + |
| 44 | +Because lookup data defines the problem space rather than recording workflow execution, it is specified declaratively as part of the table definition: |
| 45 | + |
| 46 | +```python |
| 47 | +@schema |
| 48 | +class BlobParamSet(dj.Lookup): |
| 49 | + definition = """ |
| 50 | + blob_paramset : int |
| 51 | + --- |
| 52 | + min_sigma : float |
| 53 | + max_sigma : float |
| 54 | + threshold : float |
| 55 | + """ |
| 56 | + contents = [ |
| 57 | + (1, 1.0, 5.0, 0.1), |
| 58 | + (2, 2.0, 10.0, 0.05), |
| 59 | + ] |
| 60 | +``` |
| 61 | + |
| 62 | +When the schema is activated, an "empty" pipeline already has its lookup tables populated. |
| 63 | +This ensures that reference data is always available and consistent across all installations of the pipeline. |
| 64 | + |
| 65 | +## Manual Tables: The Workflow Entry Points |
| 66 | + |
| 67 | +**Manual tables** are where new information enters the workflow from external sources. |
| 68 | +The term "manual" refers to the data's origin—*outside the pipeline*—not to how it gets there. |
| 69 | + |
| 70 | +Manual tables capture information that originates external to the computational pipeline: |
| 71 | + |
| 72 | +- Experimental subjects and sessions |
| 73 | +- Observations and annotations |
| 74 | +- External system identifiers |
| 75 | +- Curated selections and decisions |
| 76 | + |
| 77 | +Data enters Manual tables through explicit `insert` operations from various sources: |
| 78 | + |
| 79 | +- **Human entry**: Data entry forms, lab notebooks, manual curation |
| 80 | +- **Automated scripts**: Parsing instrument files, syncing from external databases |
| 81 | +- **External systems**: Laboratory information management systems (LIMS), scheduling software |
| 82 | +- **Integration pipelines**: ETL processes that import data from other sources |
| 83 | + |
| 84 | +Each insert into a Manual table potentially triggers downstream computations—this is the "data enters the system" event that drives the pipeline forward. |
| 85 | +Whether a human clicks a button or a cron job runs a script, the effect is the same: new data enters the pipeline and becomes available for automatic processing. |
| 86 | + |
| 87 | +## Automatic Population: The Workflow Engine |
| 88 | + |
| 89 | +**Imported** and **Computed** tables are populated automatically through the `populate` mechanism. |
| 90 | +This is the core of workflow automation in DataJoint. |
| 91 | + |
| 92 | +When you call `populate()` on an auto-populated table, DataJoint: |
| 93 | + |
| 94 | +1. Identifies what work is missing by examining upstream dependencies |
| 95 | +2. Executes the table's `make()` method for each pending item |
| 96 | +3. Wraps each computation in a transaction for integrity |
| 97 | +4. Continues through all pending work, handling errors gracefully |
| 98 | + |
| 99 | +This automation embodies the Relational Workflow Model's key principle: **the schema is an executable specification**. |
| 100 | +You don't write scripts to orchestrate computations—you define dependencies, and the system figures out what to run. |
| 101 | + |
| 102 | +```python |
| 103 | +# The schema defines what should be computed |
| 104 | +# populate() executes it |
| 105 | +Detection.populate(display_progress=True) |
| 106 | +``` |
| 107 | + |
| 108 | +## The Three Core Operations |
| 109 | + |
| 110 | +### Insert: Adding Data |
| 111 | + |
| 112 | +The `insert` operation adds new entities to Manual tables, representing new information entering the workflow from external sources. |
| 113 | + |
| 114 | +```python |
| 115 | +# Single row |
| 116 | +Subject.insert1({"subject_id": "M001", "species": "mouse", "sex": "M"}) |
| 117 | + |
| 118 | +# Multiple rows |
| 119 | +Session.insert([ |
| 120 | + {"subject_id": "M001", "session_date": "2024-01-15"}, |
| 121 | + {"subject_id": "M001", "session_date": "2024-01-16"}, |
| 122 | +]) |
| 123 | +``` |
| 124 | + |
| 125 | +### Delete: Removing Data with Cascade |
| 126 | + |
| 127 | +The `delete` operation removes entities and **all their downstream dependents**. |
| 128 | +This cascading behavior is fundamental to maintaining **computational validity**—the guarantee that derived data remains consistent with its inputs. |
| 129 | + |
| 130 | +When you delete an entity: |
| 131 | +- All entities that depend on it (via foreign keys) are also deleted |
| 132 | +- This cascades through the entire dependency graph |
| 133 | +- The result is a consistent database state |
| 134 | + |
| 135 | +```python |
| 136 | +# Deleting a session removes all its downstream analysis |
| 137 | +(Session & {"subject_id": "M001", "session_date": "2024-01-15"}).delete() |
| 138 | +``` |
| 139 | + |
| 140 | +Cascading delete is the primary mechanism for: |
| 141 | +- **Correcting errors**: Delete incorrect upstream data; downstream results disappear automatically |
| 142 | +- **Reprocessing**: Delete computed results to regenerate them with updated code |
| 143 | +- **Data lifecycle**: Remove obsolete data and everything derived from it |
| 144 | + |
| 145 | +### Update: Rare and Deliberate |
| 146 | + |
| 147 | +The `update` operation modifies existing values **in place**. |
| 148 | +In DataJoint, updates are deliberately rare because they can violate computational validity. |
| 149 | + |
| 150 | +Consider: if you update an upstream value, downstream computed results become inconsistent—they were derived from the old value but now coexist with the new one. |
| 151 | +The proper approach is usually **delete and reinsert**: |
| 152 | + |
| 153 | +1. Delete the incorrect data (cascading removes dependent computations) |
| 154 | +2. Insert the corrected data |
| 155 | +3. Re-run `populate()` to regenerate downstream results |
| 156 | + |
| 157 | +The `update1` method exists for cases where in-place correction is truly needed—typically for: |
| 158 | +- Fixing typos in descriptive fields that don't affect computations |
| 159 | +- Correcting metadata that has no downstream dependencies |
| 160 | +- Administrative changes to non-scientific attributes |
| 161 | + |
| 162 | +```python |
| 163 | +# Use sparingly—only for corrections that don't affect downstream data |
| 164 | +Subject.update1({"subject_id": "M001", "notes": "Corrected housing info"}) |
| 165 | +``` |
| 166 | + |
| 167 | +## The Workflow Execution Pattern |
| 168 | + |
| 169 | +A typical DataJoint workflow follows this pattern: |
| 170 | + |
| 171 | +``` |
| 172 | +┌─────────────────────────────────────────────────────────────┐ |
| 173 | +│ 1. SCHEMA ACTIVATION │ |
| 174 | +│ - Define tables and dependencies │ |
| 175 | +│ - Lookup tables are automatically populated (contents) │ |
| 176 | +└─────────────────────────────────────────────────────────────┘ |
| 177 | + ↓ |
| 178 | +┌─────────────────────────────────────────────────────────────┐ |
| 179 | +│ 2. EXTERNAL DATA ENTRY │ |
| 180 | +│ - Insert subjects, sessions, trials into Manual tables │ |
| 181 | +│ - Each insert is a potential trigger for downstream │ |
| 182 | +└─────────────────────────────────────────────────────────────┘ |
| 183 | + ↓ |
| 184 | +┌─────────────────────────────────────────────────────────────┐ |
| 185 | +│ 3. AUTOMATIC POPULATION │ |
| 186 | +│ - Call populate() on Imported tables (data acquisition) │ |
| 187 | +│ - Call populate() on Computed tables (analysis) │ |
| 188 | +│ - System determines order from dependency graph │ |
| 189 | +└─────────────────────────────────────────────────────────────┘ |
| 190 | + ↓ |
| 191 | +┌─────────────────────────────────────────────────────────────┐ |
| 192 | +│ 4. ITERATION │ |
| 193 | +│ - New manual entries trigger new computations │ |
| 194 | +│ - Errors corrected via delete + reinsert + repopulate │ |
| 195 | +│ - Pipeline grows incrementally │ |
| 196 | +└─────────────────────────────────────────────────────────────┘ |
| 197 | +``` |
| 198 | + |
| 199 | +## Transactions and Integrity |
| 200 | + |
| 201 | +All operations in DataJoint respect **ACID transactions** and **referential integrity**: |
| 202 | + |
| 203 | +- **Inserts** verify that all referenced foreign keys exist |
| 204 | +- **Deletes** cascade to maintain referential integrity |
| 205 | +- **Populate** wraps each `make()` call in a transaction |
| 206 | + |
| 207 | +This ensures that the database always represents a consistent state—there are no orphaned records, no dangling references, and no partially-completed computations visible to other users. |
| 208 | + |
| 209 | +## Chapter Overview |
| 210 | + |
| 211 | +The following chapters detail each operation: |
| 212 | + |
| 213 | +- **[Insert](010-insert.ipynb)** — Adding data to Manual tables |
| 214 | +- **[Delete](020-delete.ipynb)** — Removing data with cascading dependencies |
| 215 | +- **[Updates](030-updates.ipynb)** — Rare in-place modifications |
| 216 | +- **[Transactions](040-transactions.ipynb)** — ACID semantics and consistency |
| 217 | +- **[Populate](050-populate.ipynb)** — Automatic workflow execution |
| 218 | +- **[The `make` Method](055-make.ipynb)** — Defining computational logic |
| 219 | +- **[Orchestration](060-orchestration.ipynb)** — Infrastructure for running at scale |
0 commit comments