Repository: ontodev/robot
Pull request #352 (2018-08-22 14:18:46)

SPARQL Update
See #227

Supports SPARQL update with 
```
robot query --input <ontology> --update <file> --output <ontology>
```


Patch:
diff --git a/docs/examples/nucleus.owl b/docs/examples/nucleus.owl
index a84138cd..36fef3ad 100644
--- a/docs/examples/nucleus.owl
+++ b/docs/examples/nucleus.owl
@@ -24,7 +24,7 @@ ObjectProperty: <http://purl.obolibrary.org/obo/BFO_0000050>
 Class: <http://purl.obolibrary.org/obo/GO_0005623>
 
     Annotations: 
-        rdfs:label "cell"^^xsd:string
+        rdfs:label "cell"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0005575>
@@ -33,7 +33,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0005623>
 Class: <http://purl.obolibrary.org/obo/GO_0043229>
 
     Annotations: 
-        rdfs:label "intracellular organelle"^^xsd:string
+        rdfs:label "intracellular organelle"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/BFO_0000050> some <http://purl.obolibrary.org/obo/GO_0005622>,
@@ -44,7 +44,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0043229>
 Class: <http://purl.obolibrary.org/obo/GO_0044464>
 
     Annotations: 
-        rdfs:label "cell part"^^xsd:string
+        rdfs:label "cell part"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/BFO_0000050> some <http://purl.obolibrary.org/obo/GO_0005623>,
@@ -54,7 +54,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0044464>
 Class: <http://purl.obolibrary.org/obo/GO_0043226>
 
     Annotations: 
-        rdfs:label "organelle"^^xsd:string
+        rdfs:label "organelle"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0005575>
@@ -63,7 +63,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0043226>
 Class: <http://purl.obolibrary.org/obo/GO_0044428>
 
     Annotations: 
-        rdfs:label "nuclear part"^^xsd:string
+        rdfs:label "nuclear part"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/BFO_0000050> some <http://purl.obolibrary.org/obo/GO_0005634>,
@@ -73,7 +73,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0044428>
 Class: <http://purl.obolibrary.org/obo/GO_0043227>
 
     Annotations: 
-        rdfs:label "membrane-bounded organelle"^^xsd:string
+        rdfs:label "membrane-bounded organelle"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0043226>
@@ -82,13 +82,13 @@ Class: <http://purl.obolibrary.org/obo/GO_0043227>
 Class: <http://purl.obolibrary.org/obo/GO_0005575>
 
     Annotations: 
-        rdfs:label "cellular_component"^^xsd:string
+        rdfs:label "cellular_component"
     
     
 Class: <http://purl.obolibrary.org/obo/GO_0044424>
 
     Annotations: 
-        rdfs:label "intracellular part"^^xsd:string
+        rdfs:label "intracellular part"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/BFO_0000050> some <http://purl.obolibrary.org/obo/GO_0005622>,
@@ -98,7 +98,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0044424>
 Class: <http://purl.obolibrary.org/obo/GO_0005622>
 
     Annotations: 
-        rdfs:label "intracellular"^^xsd:string
+        rdfs:label "intracellular"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0044464>
@@ -107,7 +107,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0005622>
 Class: <http://purl.obolibrary.org/obo/GO_0044422>
 
     Annotations: 
-        rdfs:label "organelle part"^^xsd:string
+        rdfs:label "organelle part"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/BFO_0000050> some <http://purl.obolibrary.org/obo/GO_0043226>,
@@ -117,7 +117,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0044422>
 Class: <http://purl.obolibrary.org/obo/GO_0031974>
 
     Annotations: 
-        rdfs:label "membrane-enclosed lumen"^^xsd:string
+        rdfs:label "membrane-enclosed lumen"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0005575>
@@ -126,7 +126,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0031974>
 Class: <http://purl.obolibrary.org/obo/GO_0005634>
 
     Annotations: 
-        rdfs:label "nucleus"^^xsd:string
+        rdfs:label "nucleus"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0043231>
@@ -135,7 +135,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0005634>
 Class: <http://purl.obolibrary.org/obo/GO_0044446>
 
     Annotations: 
-        rdfs:label "intracellular organelle part"^^xsd:string
+        rdfs:label "intracellular organelle part"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/BFO_0000050> some <http://purl.obolibrary.org/obo/GO_0005622>,
@@ -148,7 +148,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0044446>
 Class: <http://purl.obolibrary.org/obo/GO_0070013>
 
     Annotations: 
-        rdfs:label "intracellular organelle lumen"^^xsd:string
+        rdfs:label "intracellular organelle lumen"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0043233>,
@@ -159,7 +159,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0070013>
 Class: <http://purl.obolibrary.org/obo/GO_0043231>
 
     Annotations: 
-        rdfs:label "intracellular membrane-bounded organelle"^^xsd:string
+        rdfs:label "intracellular membrane-bounded organelle"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0043227>,
@@ -169,7 +169,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0043231>
 Class: <http://purl.obolibrary.org/obo/GO_0031981>
 
     Annotations: 
-        rdfs:label "nuclear lumen"^^xsd:string
+        rdfs:label "nuclear lumen"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/BFO_0000050> some <http://purl.obolibrary.org/obo/GO_0005634>,
@@ -180,7 +180,7 @@ Class: <http://purl.obolibrary.org/obo/GO_0031981>
 Class: <http://purl.obolibrary.org/obo/GO_0043233>
 
     Annotations: 
-        rdfs:label "organelle lumen"^^xsd:string
+        rdfs:label "organelle lumen"
     
     SubClassOf: 
         <http://purl.obolibrary.org/obo/GO_0031974>,
diff --git a/docs/examples/nucleus_update.owl b/docs/examples/nucleus_update.owl
new file mode 100644
index 00000000..afa6adfc
--- /dev/null
+++ b/docs/examples/nucleus_update.owl
@@ -0,0 +1,296 @@
+<?xml version="1.0"?>
+<rdf:RDF xmlns="http://purl.obolibrary.org/obo/go.owl#"
+     xml:base="http://purl.obolibrary.org/obo/go.owl"
+     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:owl="http://www.w3.org/2002/07/owl#"
+     xmlns:xml="http://www.w3.org/XML/1998/namespace"
+     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
+     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
+    <owl:Ontology rdf:about="http://purl.obolibrary.org/obo/go.owl"/>
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Annotation properties
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://www.w3.org/2000/01/rdf-schema#label -->
+
+    <owl:AnnotationProperty rdf:about="http://www.w3.org/2000/01/rdf-schema#label"/>
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Datatypes
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://www.w3.org/2001/XMLSchema#string -->
+
+    <rdfs:Datatype rdf:about="http://www.w3.org/2001/XMLSchema#string"/>
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Object Properties
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/BFO_0000050 -->
+
+    <owl:ObjectProperty rdf:about="http://purl.obolibrary.org/obo/BFO_0000050">
+        <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#TransitiveProperty"/>
+    </owl:ObjectProperty>
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Classes
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005575 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005575">
+        <rdfs:label>cellular_component</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005622 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005622">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044464"/>
+        <rdfs:label>intracellular</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005623 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005623">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+        <rdfs:label>cell</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005634 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005634">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043231"/>
+        <rdfs:label>nucleus</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0031974 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0031974">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+        <rdfs:label>membrane-enclosed lumen</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0031981 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0031981">
+        <rdfs:label>obsolete nuclear lumen</rdfs:label>
+        <owl:deprecated rdf:datatype="http://www.w3.org/2001/XMLSchema#boolean">true</owl:deprecated>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043226 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043226">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+        <rdfs:label>organelle</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043227 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043227">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043226"/>
+        <rdfs:label>membrane-bounded organelle</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043229 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043229">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043226"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044424"/>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0005622"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:label>intracellular organelle</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043231 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043231">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043227"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043229"/>
+        <rdfs:label>intracellular membrane-bounded organelle</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043233 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043233">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0031974"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044422"/>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0043226"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:label>organelle lumen</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044422 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044422">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0043226"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:label>organelle part</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044424 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044424">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044464"/>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0005622"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:label>intracellular part</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044428 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044428">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044446"/>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0005634"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:label>nuclear part</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044446 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044446">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044422"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044424"/>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0005622"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0043226"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0043229"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:label>intracellular organelle part</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044464 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044464">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0005623"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:label>cell part</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0070013 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0070013">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043233"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044446"/>
+        <rdfs:subClassOf>
+            <owl:Restriction>
+                <owl:onProperty rdf:resource="http://purl.obolibrary.org/obo/BFO_0000050"/>
+                <owl:someValuesFrom rdf:resource="http://purl.obolibrary.org/obo/GO_0043229"/>
+            </owl:Restriction>
+        </rdfs:subClassOf>
+        <rdfs:label>intracellular organelle lumen</rdfs:label>
+    </owl:Class>
+</rdf:RDF>
+
+
+
+<!-- Generated by the OWL API (version 4.2.6) https://github.com/owlcs/owlapi -->
+
diff --git a/docs/examples/revert.ru b/docs/examples/revert.ru
new file mode 100644
index 00000000..35156823
--- /dev/null
+++ b/docs/examples/revert.ru
@@ -0,0 +1,13 @@
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
+
+DELETE { ?s rdfs:label 'obsolete nuclear lumen' ;
+			owl:deprecated 'true'^^xsd:boolean }
+INSERT { ?s rdfs:label 'nuclear lumen' ;
+            rdfs:subClassOf <http://purl.obolibrary.org/obo/GO_0070013> ;
+        	rdfs:subClassOf <http://purl.obolibrary.org/obo/GO_0044428> ;
+        	rdfs:subClassOf [ a owl:Restriction ; 
+        					  owl:onProperty <http://purl.obolibrary.org/obo/BFO_0000050> ;
+        					  owl:someValuesFrom <http://purl.obolibrary.org/obo/GO_0005634> ]}
+WHERE { ?s rdfs:label 'obsolete nuclear lumen' }
\ No newline at end of file
diff --git a/docs/examples/update.ru b/docs/examples/update.ru
new file mode 100644
index 00000000..64cf743e
--- /dev/null
+++ b/docs/examples/update.ru
@@ -0,0 +1,10 @@
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
+
+DELETE { ?s rdfs:label 'nuclear lumen' ;
+			rdfs:subClassOf ?super }
+INSERT { ?s rdfs:label 'obsolete nuclear lumen' ;
+            owl:deprecated 'true'^^xsd:boolean }
+WHERE { ?s rdfs:label 'nuclear lumen';
+		   rdfs:subClassOf ?super }
\ No newline at end of file
diff --git a/docs/query.md b/docs/query.md
index 740689f8..b2c812aa 100644
--- a/docs/query.md
+++ b/docs/query.md
@@ -40,10 +40,35 @@ The example above also uses the [global](/global)  `--catalog` option to specify
 
 The names of the graphs correspond to the ontology IRIs of the imports. If the import does not have an ontology IRI, one will be automatically generated. Running `query` with the `-vv` flag will print the names of all graphs as they are added.
 
+## Update
+
+The `query` command also supports [SPARQL Update](https://www.w3.org/TR/sparql11-update/) to insert and delete triples.
+
+    robot query --input nucleus.owl \
+      --update update.ru \
+      --output results/nucleus_update.owl
+
+When using SPARQL update, you can either provide an `--output` for the updated ontology, or [chain](/chaining) it into another command.
+
+You can perform multiple updates in one command to reduce time spent loading and saving the ontology. Updates are processed in the order that they are input.
+
+    robot query --input nucleus.owl \
+     --update update.ru \
+     --update revert.ru \
+     --output results/nucleus.owl
+
+The `--update` option only updates the ontology itself, not any of the imports.
+
+**Warning:** The output of SPARQL updates will not include `xsd:string` datatypes, because `xsd:string` is considered implicit in RDF version 1.1. This behaviour differs from other ROBOT commands, where `xsd:string` datatypes from the input are maintained in the output.
+
 ---
 
 ## Error Messages
 
+### Missing File Error
+
+The file provided for `--update` does not exist. Check the path and try again.
+
 ### Missing Query Error
 
 You must specify a query to execute with `--query` or `--queries`.
diff --git a/robot-command/src/main/java/org/obolibrary/robot/QueryCommand.java b/robot-command/src/main/java/org/obolibrary/robot/QueryCommand.java
index cfe334d0..8634d438 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/QueryCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/QueryCommand.java
@@ -3,14 +3,18 @@ package org.obolibrary.robot;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
 import org.apache.jena.query.Dataset;
+import org.apache.jena.rdf.model.Model;
+import org.semanticweb.owlapi.model.AddImport;
+import org.semanticweb.owlapi.model.OWLImportsDeclaration;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -26,6 +30,9 @@ public class QueryCommand implements Command {
   /** Namespace for error messages. */
   private static final String NS = "query#";
 
+  /** Error message when update file provided does not exist. */
+  private static final String missingFileError = NS + "MISSING FILE ERROR file '%s' does not exist";
+
   /** Error message when a query is not provided */
   private static final String missingQueryError =
       NS + "MISSING QUERY ERROR at least one query must be provided";
@@ -43,8 +50,10 @@ public class QueryCommand implements Command {
     o.addOption("i", "input", true, "load ontology from a file");
     o.addOption("I", "input-iri", true, "load ontology from an IRI");
     o.addOption("f", "format", true, "the query result format: CSV, TSV," + " TTL, JSONLD, etc.");
+    o.addOption("o", "output", true, "save updated ontology to a file");
     o.addOption("O", "output-dir", true, "Directory for output");
     o.addOption("g", "use-graphs", true, "if true, load imports as named graphs");
+    o.addOption("u", "update", true, "run a SPARQL UPDATE");
 
     Option opt;
 
@@ -135,14 +144,51 @@ public class QueryCommand implements Command {
     String outputDir = CommandLineHelper.getDefaultValue(line, "output-dir", "");
     IOHelper ioHelper = CommandLineHelper.getIOHelper(line);
     state = CommandLineHelper.updateInputOntology(ioHelper, state, line);
+    OWLOntology inputOntology = state.getOntology();
+
+    // If an update(s) are provided, run then return the OWLOntology
+    List<String> updatePaths = CommandLineHelper.getOptionalValues(line, "update");
+    if (!updatePaths.isEmpty()) {
+      Map<String, String> updates = new LinkedHashMap<>();
+      for (String updatePath : updatePaths) {
+        File f = new File(updatePath);
+        if (!f.exists()) {
+          throw new Exception(String.format(missingFileError, updatePath));
+        }
+        updates.put(f.getPath(), FileUtils.readFileToString(f));
+      }
+
+      // Load the ontology as a model, ignoring imports
+      Model model = QueryOperation.loadOntologyAsModel(inputOntology);
+
+      // Execute the updates
+      for (Map.Entry<String, String> update : updates.entrySet()) {
+        logger.debug(String.format("Running update '%s'", update.getKey()));
+        QueryOperation.execUpdate(model, update.getValue());
+      }
+
+      OWLOntology outputOntology = QueryOperation.convertModel(model);
+
+      // If the input ontology had imports, maintain them
+      if (inputOntology.getImports().size() > 0) {
+        OWLOntologyManager manager = inputOntology.getOWLOntologyManager();
+        for (OWLImportsDeclaration importsDeclaration : inputOntology.getImportsDeclarations()) {
+          manager.applyChange(new AddImport(outputOntology, importsDeclaration));
+        }
+      }
+
+      CommandLineHelper.maybeSaveOutput(line, outputOntology);
+      state.setOntology(outputOntology);
+      return state;
+    }
 
     // Determine what to do with the imports and create a new dataset
     boolean useGraphs = CommandLineHelper.getBooleanValue(line, "use-graphs", false);
     Dataset dataset;
     if (useGraphs) {
-      dataset = QueryOperation.loadOntologyAsDataset(state.getOntology(), true);
+      dataset = QueryOperation.loadOntologyAsDataset(inputOntology, true);
     } else {
-      dataset = QueryOperation.loadOntologyAsDataset(state.getOntology(), false);
+      dataset = QueryOperation.loadOntologyAsDataset(inputOntology, false);
     }
 
     // Collect all queries as (queryPath, outputPath) pairs.
diff --git a/robot-core/src/main/java/org/obolibrary/robot/QueryOperation.java b/robot-core/src/main/java/org/obolibrary/robot/QueryOperation.java
index 2e295b8a..0cc231cb 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/QueryOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/QueryOperation.java
@@ -2,10 +2,7 @@ package org.obolibrary.robot;
 
 import java.io.*;
 import java.nio.file.Path;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
 import org.apache.jena.query.*;
 import org.apache.jena.rdf.model.Model;
 import org.apache.jena.rdf.model.ModelFactory;
@@ -15,11 +12,10 @@ import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.DatasetGraphFactory;
 import org.apache.jena.tdb.TDB;
 import org.apache.jena.tdb.TDBFactory;
+import org.apache.jena.update.UpdateAction;
+import org.semanticweb.owlapi.apibinding.OWLManager;
 import org.semanticweb.owlapi.formats.TurtleDocumentFormat;
-import org.semanticweb.owlapi.model.IRI;
-import org.semanticweb.owlapi.model.OWLOntology;
-import org.semanticweb.owlapi.model.OWLOntologyManager;
-import org.semanticweb.owlapi.model.OWLOntologyStorageException;
+import org.semanticweb.owlapi.model.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -95,11 +91,7 @@ public class QueryOperation {
     TDB.getContext().set(TDB.symUnionDefaultGraph, true);
     // Load each ontology in the set as a named model
     for (OWLOntology ont : ontologies) {
-      Model m = ModelFactory.createDefaultModel();
-      ByteArrayOutputStream os = new ByteArrayOutputStream();
-      manager.saveOntology(ont, new TurtleDocumentFormat(), os);
-      String content = new String(os.toByteArray(), "UTF-8");
-      RDFParser.fromString(content).lang(RDFLanguages.TTL).parse(m);
+      Model m = loadOntologyAsModel(ont);
       // Get the name of the graph as the ontology IRI
       IRI iri = ont.getOntologyID().getOntologyIRI().orNull();
       String name;
@@ -109,12 +101,44 @@ public class QueryOperation {
         // If there is no IRI, generate a random ID
         name = UUID.randomUUID().toString();
       }
-      dataset.addNamedModel(name, m);
       logger.info("Named graph added: " + name);
+      dataset.addNamedModel(name, m);
     }
     return dataset;
   }
 
+  /**
+   * Given an OWLOntology, return the Model representation of the axioms.
+   *
+   * @param ontology OWLOntology to convert to Model
+   * @return Model of axioms (imports ignored)
+   * @throws UnsupportedEncodingException on issue parsing byte array to string
+   * @throws OWLOntologyStorageException on issue writing ontology to TTL format
+   */
+  public static Model loadOntologyAsModel(OWLOntology ontology)
+      throws UnsupportedEncodingException, OWLOntologyStorageException {
+    Model model = ModelFactory.createDefaultModel();
+    ByteArrayOutputStream os = new ByteArrayOutputStream();
+    ontology.getOWLOntologyManager().saveOntology(ontology, new TurtleDocumentFormat(), os);
+    String content = new String(os.toByteArray(), "UTF-8");
+    RDFParser.fromString(content).lang(RDFLanguages.TTL).parse(model);
+    return model;
+  }
+
+  /**
+   * Given a Model, return the OWLOntology representation of the axioms.
+   *
+   * @param model Model to convert to OWLOntology
+   * @return OWLOntology of axioms
+   * @throws OWLOntologyCreationException on issue loading ontology from byte array
+   */
+  public static OWLOntology convertModel(Model model) throws OWLOntologyCreationException {
+    ByteArrayOutputStream os = new ByteArrayOutputStream();
+    RDFDataMgr.write(os, model, Lang.TTL);
+    OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
+    return manager.loadOntologyFromOntologyDocument(new ByteArrayInputStream(os.toByteArray()));
+  }
+
   /**
    * Count results.
    *
@@ -191,6 +215,16 @@ public class QueryOperation {
     return qExec.execSelect();
   }
 
+  /**
+   * Given a Model and a SPARQL update (as string), update the model.
+   *
+   * @param model Model to update
+   * @param updateString SPARQL update
+   */
+  public static void execUpdate(Model model, String updateString) {
+    UpdateAction.parseExecute(updateString, model);
+  }
+
   /**
    * Execute a verification. Writes to STDERR.
    *
@@ -544,6 +578,24 @@ public class QueryOperation {
     }
   }
 
+  /**
+   * Given a Model, a SPARQL update string, an output stream, and the output format, update the
+   * model and write it to the output.
+   *
+   * @param model Model to update
+   * @param updateString SPARQL update
+   * @param output output stream to write to
+   * @param outputFormat the file format
+   */
+  public static void runUpdate(Model model, String updateString, File output, Lang outputFormat)
+      throws FileNotFoundException {
+    if (outputFormat == null) {
+      outputFormat = Lang.TTL;
+    }
+    execUpdate(model, updateString);
+    writeResult(model, outputFormat, new FileOutputStream(output));
+  }
+
   /**
    * Run a SELECT query over the graph and write the result to a file. Prints violations to STDERR.
    *


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/QueryOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/QueryOperationTest.java
index 3b6e8775..c12ad7b7 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/QueryOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/QueryOperationTest.java
@@ -14,6 +14,7 @@ import org.apache.jena.rdf.model.*;
 import org.apache.jena.riot.Lang;
 import org.junit.Test;
 import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyCreationException;
 import org.semanticweb.owlapi.model.OWLOntologyStorageException;
 
 /**
@@ -105,6 +106,29 @@ public class QueryOperationTest extends CoreTest {
     assertTrue(model.contains(s, p, o));
   }
 
+  /**
+   * Tests an update statement that adds a label.
+   *
+   * @throws IOException on IO error
+   * @throws OWLOntologyStorageException on ontology error
+   * @throws OWLOntologyCreationException on ontology error
+   */
+  @Test
+  public void testExecUpdate()
+      throws IOException, OWLOntologyCreationException, OWLOntologyStorageException {
+    OWLOntology inputOntology = loadOntology("/simple.owl");
+    Model model = QueryOperation.loadOntologyAsModel(inputOntology);
+    String updateString =
+        "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>"
+            + "PREFIX s: <https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#>"
+            + "INSERT { "
+            + "s:test2 rdfs:label \"test 2\" ."
+            + " } WHERE {}";
+    QueryOperation.execUpdate(model, updateString);
+    OWLOntology outputOntology = QueryOperation.convertModel(model);
+    assertIdentical("/simple_update.owl", outputOntology);
+  }
+
   /**
    * Tests a verify with violations.
    *
diff --git a/robot-core/src/test/resources/simple_update.owl b/robot-core/src/test/resources/simple_update.owl
new file mode 100644
index 00000000..81579e5a
--- /dev/null
+++ b/robot-core/src/test/resources/simple_update.owl
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<rdf:RDF xmlns="https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#"
+     xml:base="https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl"
+     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:owl="http://www.w3.org/2002/07/owl#"
+     xmlns:xml="http://www.w3.org/XML/1998/namespace"
+     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
+     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
+    <owl:Ontology rdf:about="https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl"/>
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Classes
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#test1 -->
+
+    <owl:Class rdf:about="https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#test1">
+        <rdfs:label>Test 1</rdfs:label>
+        <rdfs:label>test one</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#test2 -->
+
+    <owl:Class rdf:about="https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#test2">
+        <rdfs:subClassOf rdf:resource="https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#test1"/>
+        <rdfs:label>test 2</rdfs:label>
+    </owl:Class>
+</rdf:RDF>
+
+
+
+<!-- Generated by the OWL API (version 4.2.6) https://github.com/owlcs/owlapi -->
+