├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── net │ └── fortytwo │ └── tpop │ └── sail │ ├── DataStore.java │ ├── GraphIndex.java │ ├── GraphSail.java │ ├── GraphSailConnection.java │ ├── GraphSailDataset.java │ ├── GraphSailSink.java │ ├── GraphSailStatement.java │ ├── GraphSailStore.java │ ├── IterUtils.java │ ├── NamespaceStore.java │ ├── Schema.java │ └── tg │ └── TinkerGraphIndex.java └── test ├── java └── net │ └── fortytwo │ └── tpop │ └── sail │ ├── GetAndRetrieveStatementsTest.java │ ├── GraphSailTest.java │ ├── GraphSailTestBase.java │ ├── NamespaceStoreTest.java │ ├── SailAdder.java │ └── SailTest.java └── resources └── net └── fortytwo └── tpop └── sail └── graph-example-sail-test.trig /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | # Eclipse 4 | .classpath 5 | .project 6 | .settings 7 | 8 | # Intellij 9 | .idea 10 | *.iml 11 | 12 | # Maven 13 | target/ 14 | pom.xml.tag 15 | pom.xml.releaseBackup 16 | pom.xml.versionsBackup 17 | pom.xml.next 18 | release.properties 19 | dependency-reduced-pom.xml 20 | buildNumber.properties 21 | .mvn/timing.properties 22 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 23 | !/.mvn/wrapper/maven-wrapper.jar 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphSail 2 | 3 | RDF storage and inference layer (SAIL) for Apache TinkerPop 3 4 | 5 | ## Namespaces 6 | 7 | Namespace definitions (prefix - IRI pairs) are a convenience provided in GraphSail for compatibility with RDF4j. 8 | However, they are not stored "in the graph" like RDF statements (which are represented as vertices and edges). 9 | Whereas Blueprints GraphSail attached namespaces to a special reference vertex, GraphSail v3 stores them in-memory. 10 | Read-write access to namespaces is very fast, but the namespaces persist only for the liftime of a session. 11 | In addition, changes to namespaces are not bound to TinkerPop transactions, so they are not rolled back if a write operation fails. 12 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | net.fortytwo.tpop 8 | graphsail 9 | 1.0-SNAPSHOT 10 | jar 11 | GraphSail for TinkerPop3 12 | RDF storage and inference layer (SAIL) for Apache TinkerPop 3 13 | 14 | 15 | 2.2.2 16 | 3.2.5 17 | 4.12 18 | 19 | 20 | 21 | 22 | 23 | org.apache.tinkerpop 24 | gremlin-core 25 | ${tinkerpop.version} 26 | 27 | 28 | org.apache.tinkerpop 29 | tinkergraph-gremlin 30 | ${tinkerpop.version} 31 | 32 | 33 | org.eclipse.rdf4j 34 | rdf4j-sail-base 35 | ${rdf4j.version} 36 | 37 | 38 | org.eclipse.rdf4j 39 | rdf4j-sail-memory 40 | ${rdf4j.version} 41 | 42 | 43 | 44 | 45 | org.eclipse.rdf4j 46 | rdf4j-rio-trig 47 | ${rdf4j.version} 48 | test 49 | 50 | 51 | junit 52 | junit 53 | ${junit.version} 54 | test 55 | 56 | 57 | 58 | 59 | 60 | src/main/java 61 | src/test/java 62 | target 63 | ${project.artifactId}-${project.version} 64 | 65 | 66 | src/main/resources 67 | 68 | 69 | 70 | 71 | src/test/resources 72 | 73 | 74 | 75 | 76 | maven-compiler-plugin 77 | 2.3.2 78 | 79 | 1.8 80 | 1.8 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/DataStore.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 4 | import org.apache.tinkerpop.gremlin.structure.Direction; 5 | import org.apache.tinkerpop.gremlin.structure.Edge; 6 | import org.apache.tinkerpop.gremlin.structure.Graph; 7 | import org.apache.tinkerpop.gremlin.structure.Property; 8 | import org.apache.tinkerpop.gremlin.structure.T; 9 | import org.apache.tinkerpop.gremlin.structure.Vertex; 10 | import org.eclipse.rdf4j.common.iteration.CloseableIteration; 11 | import org.eclipse.rdf4j.model.BNode; 12 | import org.eclipse.rdf4j.model.IRI; 13 | import org.eclipse.rdf4j.model.Literal; 14 | import org.eclipse.rdf4j.model.Resource; 15 | import org.eclipse.rdf4j.model.Statement; 16 | import org.eclipse.rdf4j.model.Value; 17 | import org.eclipse.rdf4j.model.ValueFactory; 18 | import org.eclipse.rdf4j.model.impl.SimpleValueFactory; 19 | import org.eclipse.rdf4j.model.vocabulary.RDF; 20 | import org.eclipse.rdf4j.sail.SailException; 21 | 22 | import java.util.Collections; 23 | import java.util.HashSet; 24 | import java.util.Iterator; 25 | import java.util.Set; 26 | import java.util.function.Function; 27 | 28 | /** 29 | * A context object which is shared between the Blueprints Sail and its connections. 30 | * 31 | * @author Joshua Shinavier (http://fortytwo.net) 32 | */ 33 | class DataStore { 34 | 35 | private final boolean readOnly; 36 | private final Graph graph; 37 | private final GraphTraversalSource traversal; 38 | private final NamespaceStore namespaces = new NamespaceStore(); 39 | private final ValueFactory valueFactory = SimpleValueFactory.getInstance(); 40 | 41 | private final GraphIndex valueIndex; 42 | 43 | private boolean uniqueStatements; 44 | 45 | private final SailChangedHelper sailChangedHelper; 46 | 47 | DataStore(final Graph graph, 48 | final boolean readOnly, 49 | final Function indexFactory, 50 | final SailChangedHelper sailChangedHelper) { 51 | this.graph = graph; 52 | this.traversal = graph.traversal(); 53 | this.readOnly = readOnly; 54 | this.sailChangedHelper = sailChangedHelper; 55 | 56 | valueIndex = indexFactory.apply(Schema.VertexProperties.VALUE); 57 | valueIndex.initialize(); 58 | } 59 | 60 | public Graph getGraph() { 61 | return graph; 62 | } 63 | 64 | boolean isReadOnly() { 65 | return readOnly; 66 | } 67 | 68 | NamespaceStore getNamespaces() { 69 | return namespaces; 70 | } 71 | 72 | private CloseableIteration getSubjectStatements(final Resource subject) { 73 | // assuming all edges are statements 74 | Vertex vertex = getVertexByValue(subject); 75 | Iterator edges = null == vertex 76 | ? Collections.emptyIterator() 77 | : vertex.edges(Direction.OUT); 78 | return toStatements(edges); 79 | } 80 | 81 | private CloseableIteration getObjectStatements(final Value object) { 82 | // assuming all edges are statements 83 | Vertex vertex = getVertexByValue(object); 84 | Iterator edges = null == vertex 85 | ? Collections.emptyIterator() 86 | : vertex.edges(Direction.IN); 87 | return toStatements(edges); 88 | } 89 | 90 | CloseableIteration getAllStatements() { 91 | return toStatements(getAllStatementEdges()); 92 | } 93 | 94 | private Vertex getVertexByValue(final Value value) { 95 | return getVertexByValue(value, findLabel(value)); 96 | } 97 | 98 | private Vertex getVertexByValue(final Value value, final Schema.VertexLabel vertexLabel) { 99 | Iterator hits = traversal.V() 100 | .has(T.label, vertexLabel.name()).has(Schema.VertexProperties.VALUE, value.stringValue()); 101 | while (hits.hasNext()) { 102 | Vertex next = hits.next(); 103 | 104 | // literals, additionally, may differ in datatype or language 105 | if (vertexLabel.equals(Schema.VertexLabel.Literal) 106 | && !datatypeAndLanguageEquals((Literal) value, next)) { 107 | continue; 108 | } 109 | 110 | return next; 111 | } 112 | 113 | return null; 114 | } 115 | 116 | boolean edgeExists(final Vertex outV, final Vertex inV, final String label, final String context) { 117 | Iterator edges = outV.edges(Direction.OUT, label); 118 | while (edges.hasNext()) { 119 | Edge next = edges.next(); 120 | if (!next.inVertex().equals(inV)) continue; 121 | if (contextEquals(context, next)) { 122 | return true; 123 | } 124 | } 125 | return false; 126 | } 127 | 128 | Statement addStatementInternal(final Vertex outV, final Vertex inV, final String label, final String context) { 129 | Edge edge = outV.addEdge(label, inV); 130 | registerStatementAdded(); 131 | if (null != context) { 132 | edge.property(Schema.EdgeProperties.CONTEXT, context); 133 | } 134 | return toStatement(edge); 135 | } 136 | 137 | private void registerStatementAdded() { 138 | sailChangedHelper.statementsAdded = true; 139 | } 140 | 141 | private void registerStatementRemoved() { 142 | sailChangedHelper.statementsRemoved = true; 143 | } 144 | 145 | private boolean contextEquals(final String expected, final Edge edge) { 146 | Property actual = edge.property(Schema.EdgeProperties.CONTEXT); 147 | if (actual.isPresent()) { 148 | return null != expected && actual.value().equals(expected); 149 | } else { 150 | return null == expected; 151 | } 152 | } 153 | 154 | private CloseableIteration toStatements(final Iterator edges) { 155 | return IterUtils.toCloseableIteration(edges, this::toStatement); 156 | } 157 | 158 | private boolean datatypeAndLanguageEquals(final Literal expected, final Vertex vertex) { 159 | String datatype = getDatatype(vertex); 160 | if (null == datatype || !datatype.equals(expected.getDatatype().stringValue())) { 161 | return false; 162 | } 163 | if (datatype.equals(RDF.LANGSTRING.stringValue())) { 164 | String language = getLanguage(vertex); 165 | if (null == language || !language.equals(expected.getLanguage().get())) { 166 | return false; 167 | } 168 | } 169 | 170 | return true; 171 | } 172 | 173 | Vertex getOrCreateVertexByValue(final Value value) { 174 | return getOrCreateVertexByValue(value, findLabel(value)); 175 | } 176 | 177 | private Vertex getOrCreateVertexByValue(final Value value, final Schema.VertexLabel vertexLabel) { 178 | Vertex vertex = getVertexByValue(value, vertexLabel); 179 | if (null == vertex) { 180 | vertex = createNewVertex(value, vertexLabel); 181 | } 182 | return vertex; 183 | } 184 | 185 | private Vertex createNewVertex(final Value value, final Schema.VertexLabel vertexLabel) { 186 | switch (vertexLabel) { 187 | case IRI: 188 | return createNewIRIVertex(value); 189 | case BNode: 190 | return createNewBNodeVertex(value); 191 | case Literal: 192 | return createNewLiteralVertex(value); 193 | default: 194 | throw new IllegalStateException(); 195 | } 196 | } 197 | 198 | private Vertex createVertex(final String label, final Value value) { 199 | Vertex vertex = graph.addVertex(label); 200 | vertex.property(Schema.VertexProperties.VALUE, value.stringValue()); 201 | valueIndex.add(vertex); 202 | return vertex; 203 | } 204 | 205 | private Vertex createNewIRIVertex(final Value value) { 206 | return createVertex(Schema.VertexLabel.IRI.name(), value); 207 | } 208 | 209 | private Vertex createNewBNodeVertex(final Value value) { 210 | return createVertex(Schema.VertexLabel.BNode.name(), value); 211 | } 212 | 213 | private Vertex createNewLiteralVertex(final Value value) { 214 | Vertex vertex = createVertex(Schema.VertexLabel.Literal.name(), value); 215 | IRI datatype = ((Literal) value).getDatatype(); 216 | vertex.property(Schema.VertexProperties.DATATYPE, datatype.stringValue()); 217 | if (datatype.equals(RDF.LANGSTRING)) { 218 | vertex.property(Schema.VertexProperties.LANGUAGE, ((Literal) value).getLanguage().get()); 219 | } 220 | return vertex; 221 | } 222 | 223 | private Schema.VertexLabel findLabel(final Value value) { 224 | if (value instanceof IRI) { 225 | return Schema.VertexLabel.IRI; 226 | } else if (value instanceof BNode) { 227 | return Schema.VertexLabel.BNode; 228 | } else if (value instanceof Literal) { 229 | return Schema.VertexLabel.Literal; 230 | } else { 231 | throw new IllegalArgumentException(); 232 | } 233 | } 234 | 235 | // note: for now, every edge in the graph is assumed to be a statement edge 236 | private Iterator getAllStatementEdges() { 237 | return graph.edges(); 238 | } 239 | 240 | private void removeEdgeCleanly(final Edge edge) { 241 | Vertex outV = edge.outVertex(); 242 | Vertex inV = edge.inVertex(); 243 | edge.remove(); 244 | removeIfIsolated(outV); 245 | removeIfIsolated(inV); 246 | } 247 | 248 | void removeIteratorStatements(final CloseableIteration statements) { 249 | while (statements.hasNext()) { 250 | Statement next = statements.next(); 251 | 252 | // beware of ConcurrentModificationExceptions 253 | removeEdgeCleanly(((GraphSailStatement) next).getEdge()); 254 | registerStatementRemoved(); 255 | } 256 | } 257 | 258 | private void removeIfIsolated(final Vertex toTest) { 259 | if (isIsolated(toTest)) { 260 | deleteVertex(toTest); 261 | } 262 | } 263 | 264 | private void deleteVertex(final Vertex toDelete) { 265 | valueIndex.remove(toDelete); 266 | toDelete.remove(); 267 | } 268 | 269 | private boolean isIsolated(final Vertex toTest) { 270 | return isEmpty(toTest.edges(Direction.BOTH)); 271 | } 272 | 273 | private boolean isEmpty(final Iterator iter) { 274 | return !iter.hasNext(); 275 | } 276 | 277 | ValueFactory getValueFactory() { 278 | return valueFactory; 279 | } 280 | 281 | boolean getUniqueStatements() { 282 | return uniqueStatements; 283 | } 284 | 285 | void setUniqueStatements(boolean uniqueStatements) { 286 | this.uniqueStatements = uniqueStatements; 287 | } 288 | 289 | private Statement toStatement(final Edge edge) { 290 | return new GraphSailStatement(edge, 291 | getSubject(edge), getPredicate(edge), getObject(edge), getContext(edge)); 292 | } 293 | 294 | private Resource getSubject(final Edge edge) { 295 | return toResource(edge.outVertex()); 296 | } 297 | 298 | private IRI getPredicate(final Edge edge) { 299 | return toIRI(edge.label()); 300 | } 301 | 302 | private Value getObject(final Edge edge) { 303 | return toValue(edge.inVertex()); 304 | } 305 | 306 | private Resource getContext(final Edge edge) { 307 | Property prop = edge.property(Schema.EdgeProperties.CONTEXT); 308 | return prop.isPresent() ? toResource(prop.value()) : null; 309 | } 310 | 311 | private Resource toResource(final Vertex vertex) { 312 | return (Resource) toValue(vertex); 313 | } 314 | 315 | private IRI toIRI(final String iriValue) { 316 | return valueFactory.createIRI(iriValue); 317 | } 318 | 319 | private BNode toBNode(final String bNodeValue) { 320 | return valueFactory.createBNode(bNodeValue.substring(2)); 321 | } 322 | 323 | private Resource toResource(final String iriOrBNodeValue) { 324 | // note: an IRI beginning with "_:" is not allowed 325 | if (iriOrBNodeValue.startsWith("_:")) { 326 | return toBNode(iriOrBNodeValue); 327 | } else { 328 | return toIRI(iriOrBNodeValue); 329 | } 330 | } 331 | 332 | private Value toValue(final Vertex vertex) { 333 | Schema.VertexLabel kind = Schema.VertexLabel.valueOf(vertex.label()); 334 | switch (kind) { 335 | case IRI: 336 | return toIRI(vertex); 337 | case BNode: 338 | return toBNode(vertex); 339 | case Literal: 340 | return toLiteral(vertex); 341 | default: 342 | throw new IllegalStateException(); 343 | } 344 | } 345 | 346 | private IRI toIRI(final Vertex vertex) { 347 | return toIRI(getValue(vertex)); 348 | } 349 | 350 | private BNode toBNode(final Vertex vertex) { 351 | return toBNode(getValue(vertex)); 352 | } 353 | 354 | private Literal toLiteral(final Vertex vertex) { 355 | String value = getValue(vertex); 356 | String datatype = getDatatype(vertex); 357 | if (datatype.equals(RDF.LANGSTRING.stringValue())) { 358 | String language = getLanguage(vertex); 359 | return valueFactory.createLiteral(value, language); 360 | } else { 361 | IRI dt = valueFactory.createIRI(datatype); 362 | return valueFactory.createLiteral(value, dt); 363 | } 364 | } 365 | 366 | private String getValue(final Vertex vertex) { 367 | return vertex.value(Schema.VertexProperties.VALUE); 368 | } 369 | 370 | private String getDatatype(final Vertex vertex) { 371 | return vertex.value(Schema.VertexProperties.DATATYPE); 372 | } 373 | 374 | private String getLanguage(final Vertex vertex) { 375 | Property prop = vertex.property(Schema.VertexProperties.LANGUAGE); 376 | return prop.isPresent() ? prop.value() : null; 377 | } 378 | 379 | CloseableIteration buildIterator(final Resource subject, 380 | final IRI predicate, 381 | final Value object, 382 | final Resource... contexts) { 383 | if (null != subject) { 384 | return getStatementsBySubject(subject, predicate, object, contexts); 385 | } else if (null != object) { 386 | return getStatementsByObject(subject, predicate, object, contexts); 387 | } else { 388 | return getStatementsWithFullScan(subject, predicate, object, contexts); 389 | } 390 | } 391 | 392 | private CloseableIteration getStatementsBySubject( 393 | final Resource subject, 394 | final IRI predicate, 395 | final Value object, 396 | final Resource... contexts) { 397 | 398 | CloseableIteration iter = getSubjectStatements(subject); 399 | if (null != object) { 400 | iter = addObjectFilter(iter, object); 401 | } 402 | if (null != predicate) { 403 | iter = addPredicateFilter(iter, predicate); 404 | } 405 | if (contexts.length > 0) { 406 | iter = addContextFilter(iter, contexts); 407 | } 408 | return iter; 409 | } 410 | 411 | private CloseableIteration getStatementsByObject( 412 | final Resource subject, 413 | final IRI predicate, 414 | final Value object, 415 | final Resource... contexts) { 416 | CloseableIteration iter = getObjectStatements(object); 417 | if (null != subject) { 418 | iter = addSubjectFilter(iter, subject); 419 | } 420 | if (null != predicate) { 421 | iter = addPredicateFilter(iter, predicate); 422 | } 423 | if (contexts.length > 0) { 424 | iter = addContextFilter(iter, contexts); 425 | } 426 | return iter; 427 | } 428 | 429 | private CloseableIteration getStatementsWithFullScan( 430 | final Resource subject, 431 | final IRI predicate, 432 | final Value object, 433 | final Resource... contexts) { 434 | CloseableIteration iter = getAllStatements(); 435 | if (null != subject) { 436 | iter = addSubjectFilter(iter, subject); 437 | } 438 | if (null != object) { 439 | iter = addObjectFilter(iter, object); 440 | } 441 | if (null != predicate) { 442 | iter = addPredicateFilter(iter, predicate); 443 | } 444 | if (contexts.length > 0) { 445 | iter = addContextFilter(iter, contexts); 446 | } 447 | return iter; 448 | } 449 | 450 | private CloseableIteration addSubjectFilter( 451 | final CloseableIteration base, final Resource equalSubject) { 452 | return IterUtils.filter(base, statement -> statement.getSubject().equals(equalSubject)); 453 | } 454 | 455 | private CloseableIteration addObjectFilter( 456 | final CloseableIteration base, final Value equalObject) { 457 | return IterUtils.filter(base, statement -> statement.getObject().equals(equalObject)); 458 | } 459 | 460 | private CloseableIteration addPredicateFilter( 461 | final CloseableIteration base, final IRI equalPredicate) { 462 | return IterUtils.filter(base, statement -> statement.getPredicate().equals(equalPredicate)); 463 | } 464 | 465 | private CloseableIteration addContextFilter( 466 | final CloseableIteration base, final Resource[] contexts) { 467 | Set contextSet = createNullSafeSetOfContexts(contexts); 468 | return IterUtils.filter(base, 469 | statement -> contextSet.contains(statement.getContext())); 470 | } 471 | 472 | static Set findContextsIn(final CloseableIteration iter) { 473 | Set contexts = new HashSet<>(); // may contain null 474 | while (iter.hasNext()) { 475 | contexts.add(iter.next().getContext()); 476 | } 477 | return contexts; 478 | } 479 | 480 | Set createNullSafeSetOfContexts(final Resource[] contexts) { 481 | // HashSet explicitly allows null as an element 482 | Set set = new HashSet<>(); 483 | Collections.addAll(set, contexts); 484 | return set; 485 | } 486 | 487 | SailChangedHelper getSailChangedHelper() { 488 | return sailChangedHelper; 489 | } 490 | 491 | abstract static class SailChangedHelper { 492 | private boolean statementsAdded; 493 | private boolean statementsRemoved; 494 | 495 | public abstract void notifyOfChanges(boolean statementsAdded, boolean statementsRemoved); 496 | 497 | synchronized void flush() { 498 | boolean addedTmp = statementsAdded; 499 | boolean removedTmp = statementsRemoved; 500 | statementsAdded = false; 501 | statementsRemoved = false; 502 | notifyOfChanges(addedTmp, removedTmp); 503 | } 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/GraphIndex.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Vertex; 4 | 5 | public abstract class GraphIndex { 6 | protected final String key; 7 | 8 | protected GraphIndex(final String key) { 9 | this.key = key; 10 | } 11 | 12 | protected abstract boolean isAutomatic(); 13 | 14 | public abstract void initialize(); 15 | 16 | protected abstract void addInternal(Vertex vertex); 17 | 18 | public abstract void removeInternal(Vertex vertex); 19 | 20 | void add(Vertex vertex) { 21 | if (!isAutomatic()) { 22 | addInternal(vertex); 23 | } 24 | } 25 | 26 | void remove(Vertex vertex) { 27 | if (!isAutomatic()) { 28 | removeInternal(vertex); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/GraphSail.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Graph; 4 | import org.eclipse.rdf4j.model.ValueFactory; 5 | import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; 6 | import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; 7 | import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; 8 | import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverImpl; 9 | import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; 10 | import org.eclipse.rdf4j.sail.NotifyingSailConnection; 11 | import org.eclipse.rdf4j.sail.Sail; 12 | import org.eclipse.rdf4j.sail.SailChangedEvent; 13 | import org.eclipse.rdf4j.sail.SailException; 14 | import org.eclipse.rdf4j.sail.base.SailStore; 15 | import org.eclipse.rdf4j.sail.helpers.AbstractNotifyingSail; 16 | 17 | import java.util.function.Function; 18 | 19 | /** 20 | * An RDF storage and inference layer (SAIL) for Apache TinkerPop 3 21 | * 22 | * @author Joshua Shinavier (http://fortytwo.net) 23 | */ 24 | public class GraphSail extends AbstractNotifyingSail { 25 | private final DataStore dataStore; 26 | private final SailStore sailStore; 27 | 28 | private EvaluationStrategyFactory evalStratFactory; 29 | private FederatedServiceResolver serviceResolver; 30 | private FederatedServiceResolverImpl dependentServiceResolver; 31 | 32 | public GraphSail(final Graph graph, final Function indexFactory) { 33 | this(graph, indexFactory, false); 34 | } 35 | 36 | public GraphSail(final Graph graph, final Function indexFactory, final boolean readOnly) { 37 | this.dataStore = new DataStore(graph, readOnly, indexFactory, new DataStore.SailChangedHelper() { 38 | @Override 39 | public void notifyOfChanges(boolean statementsAdded, boolean statementsRemoved) { 40 | if (statementsAdded || statementsRemoved) { 41 | SailChangedEvent event = new SailChangedEvent() { 42 | @Override 43 | public Sail getSail() { 44 | return GraphSail.this; 45 | } 46 | 47 | @Override 48 | public boolean statementsAdded() { 49 | return statementsAdded; 50 | } 51 | 52 | @Override 53 | public boolean statementsRemoved() { 54 | return statementsRemoved; 55 | } 56 | }; 57 | notifySailChanged(event); 58 | } 59 | } 60 | }); 61 | this.sailStore = new GraphSailStore(dataStore); 62 | } 63 | 64 | DataStore getDataStore() { 65 | return dataStore; 66 | } 67 | 68 | SailStore getSailStore() { 69 | return sailStore; 70 | } 71 | 72 | synchronized EvaluationStrategyFactory getEvaluationStrategyFactory() { 73 | if (evalStratFactory == null) { 74 | evalStratFactory = new StrictEvaluationStrategyFactory(getFederatedServiceResolver()); 75 | } 76 | evalStratFactory.setQuerySolutionCacheThreshold(getIterationCacheSyncThreshold()); 77 | return evalStratFactory; 78 | } 79 | 80 | private synchronized FederatedServiceResolver getFederatedServiceResolver() { 81 | if (serviceResolver == null) { 82 | if (dependentServiceResolver == null) { 83 | dependentServiceResolver = new FederatedServiceResolverImpl(); 84 | } 85 | setFederatedServiceResolver(dependentServiceResolver); 86 | } 87 | return serviceResolver; 88 | } 89 | 90 | private synchronized void setFederatedServiceResolver(FederatedServiceResolver resolver) { 91 | this.serviceResolver = resolver; 92 | if (resolver != null && evalStratFactory instanceof FederatedServiceResolverClient) { 93 | ((FederatedServiceResolverClient) evalStratFactory).setFederatedServiceResolver(resolver); 94 | } 95 | } 96 | 97 | /** 98 | * Enables or disables enforcement of a unique statements policy (disabled by default), 99 | * which ensures that no new statement will be added which is identical 100 | * (in all of its subject, predicate, object and context) to an existing statement. 101 | * If enabled, this policy will first remove any existing statements identical to the to-be-added statement, 102 | * before adding the latter statement. 103 | * This comes at the cost of higher write latency. 104 | * 105 | * @param flag whether this policy should be enforced 106 | */ 107 | public void enforceUniqueStatements(final boolean flag) { 108 | dataStore.setUniqueStatements(flag); 109 | } 110 | 111 | @Override 112 | protected void shutDownInternal() throws SailException { 113 | wrapForSail(sailStore::close); 114 | } 115 | 116 | @Override 117 | protected NotifyingSailConnection getConnectionInternal() throws SailException { 118 | return new GraphSailConnection(this); 119 | } 120 | 121 | @Override 122 | public boolean isWritable() throws SailException { 123 | return !dataStore.isReadOnly(); 124 | } 125 | 126 | @Override 127 | public ValueFactory getValueFactory() { 128 | return dataStore.getValueFactory(); 129 | } 130 | 131 | private void wrapForSail(final NoargMethod method) { 132 | try { 133 | method.call(); 134 | } catch (Exception e) { 135 | throw new SailException(e); 136 | } 137 | } 138 | 139 | @FunctionalInterface 140 | private interface NoargMethod { 141 | void call() throws Exception; 142 | } 143 | } -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/GraphSailConnection.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Vertex; 4 | import org.eclipse.rdf4j.model.IRI; 5 | import org.eclipse.rdf4j.model.Resource; 6 | import org.eclipse.rdf4j.model.Value; 7 | import org.eclipse.rdf4j.sail.SailException; 8 | import org.eclipse.rdf4j.sail.base.SailSourceConnection; 9 | 10 | /** 11 | * A transactional connection to a GraphSail RDF store 12 | * 13 | * @author Joshua Shinavier (http://fortytwo.net) 14 | */ 15 | class GraphSailConnection extends SailSourceConnection { 16 | 17 | private static final Resource[] DEFAULT_CONTEXT = {null}; 18 | 19 | private final DataStore dataStore; 20 | 21 | GraphSailConnection(GraphSail sail) { 22 | super(sail, sail.getSailStore(), sail.getEvaluationStrategyFactory()); 23 | this.dataStore = sail.getDataStore(); 24 | } 25 | 26 | @Override 27 | public void addStatementInternal( 28 | final Resource subject, 29 | final IRI predicate, 30 | final Value object, 31 | final Resource... contexts) throws SailException { 32 | Vertex subjectVertex = dataStore.getOrCreateVertexByValue(subject); 33 | Vertex objectVertex = dataStore.getOrCreateVertexByValue(object); 34 | Resource[] addContexts = 0 == contexts.length 35 | ? DEFAULT_CONTEXT 36 | : contexts; 37 | String label = predicate.stringValue(); 38 | for (Resource context : addContexts) { 39 | String contextValue = null == context ? null : context.stringValue(); 40 | if (dataStore.getUniqueStatements() 41 | && dataStore.edgeExists(subjectVertex, objectVertex, label, contextValue)) { 42 | continue; 43 | } 44 | 45 | dataStore.addStatementInternal(subjectVertex, objectVertex, label, contextValue); 46 | } 47 | } 48 | 49 | @Override 50 | public void removeStatementsInternal(final Resource subject, 51 | final IRI predicate, 52 | final Value object, 53 | final Resource... contexts) throws SailException { 54 | dataStore.removeIteratorStatements(dataStore.buildIterator(subject, predicate, object, contexts)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/GraphSailDataset.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.eclipse.rdf4j.common.iteration.CloseableIteration; 4 | import org.eclipse.rdf4j.model.IRI; 5 | import org.eclipse.rdf4j.model.Namespace; 6 | import org.eclipse.rdf4j.model.Resource; 7 | import org.eclipse.rdf4j.model.Statement; 8 | import org.eclipse.rdf4j.model.Value; 9 | import org.eclipse.rdf4j.sail.SailException; 10 | import org.eclipse.rdf4j.sail.base.SailDataset; 11 | 12 | import java.util.Set; 13 | 14 | class GraphSailDataset implements SailDataset { 15 | private final DataStore dataStore; 16 | 17 | GraphSailDataset(DataStore dataStore) { 18 | this.dataStore = dataStore; 19 | } 20 | 21 | @Override 22 | public void close() throws SailException { 23 | // TODO: graph transaction handling 24 | } 25 | 26 | @Override 27 | public CloseableIteration getNamespaces() throws SailException { 28 | return dataStore.getNamespaces().getAll(); 29 | } 30 | 31 | @Override 32 | public String getNamespace(final String prefix) throws SailException { 33 | return dataStore.getNamespaces().get(prefix); 34 | } 35 | 36 | @Override 37 | public CloseableIteration getContextIDs() throws SailException { 38 | Set contexts = DataStore.findContextsIn(dataStore.getAllStatements()); 39 | return IterUtils.toCloseableIteration(contexts.iterator(), s -> s); 40 | } 41 | 42 | @Override 43 | public CloseableIteration getStatements( 44 | Resource subject, IRI predicate, Value object, Resource... contexts) throws SailException { 45 | return dataStore.buildIterator(subject, predicate, object, contexts); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/GraphSailSink.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.eclipse.rdf4j.common.iteration.CloseableIteration; 4 | import org.eclipse.rdf4j.model.IRI; 5 | import org.eclipse.rdf4j.model.Resource; 6 | import org.eclipse.rdf4j.model.Statement; 7 | import org.eclipse.rdf4j.model.Value; 8 | import org.eclipse.rdf4j.sail.SailException; 9 | import org.eclipse.rdf4j.sail.base.SailSink; 10 | 11 | import java.util.Set; 12 | 13 | class GraphSailSink implements SailSink { 14 | private final DataStore dataStore; 15 | 16 | GraphSailSink(final DataStore dataStore) { 17 | this.dataStore = dataStore; 18 | } 19 | 20 | @Override 21 | public void prepare() throws SailException { 22 | // TODO: graph transaction handling 23 | } 24 | 25 | @Override 26 | public void flush() throws SailException { 27 | dataStore.getSailChangedHelper().flush(); 28 | // TODO: graph transaction handling 29 | } 30 | 31 | @Override 32 | public void setNamespace(final String prefix, final String name) throws SailException { 33 | dataStore.getNamespaces().set(prefix, name); 34 | } 35 | 36 | @Override 37 | public void removeNamespace(final String prefix) throws SailException { 38 | dataStore.getNamespaces().remove(prefix); 39 | } 40 | 41 | @Override 42 | public void clearNamespaces() throws SailException { 43 | dataStore.getNamespaces().clear(); 44 | } 45 | 46 | @Override 47 | public void clear(Resource... contexts) throws SailException { 48 | Set contextSet = contexts.length == 0 ? null : dataStore.createNullSafeSetOfContexts(contexts); 49 | 50 | CloseableIteration edges = dataStore.getAllStatements(); 51 | if (0 < contexts.length) { 52 | edges = IterUtils.filter(edges, 53 | st -> contextSet.contains(st.getContext())); 54 | } 55 | 56 | dataStore.removeIteratorStatements(edges); 57 | } 58 | 59 | @Override 60 | public void observe(Resource subject, IRI predicate, Value object, Resource... contexts) throws SailException { 61 | // TODO: no-op? 62 | } 63 | 64 | @Override 65 | public void approve(Resource subject, IRI predicate, Value object, Resource context) throws SailException { 66 | // TODO: no-op? 67 | } 68 | 69 | @Override 70 | public void deprecate(Resource subject, IRI predicate, Value object, Resource context) throws SailException { 71 | // TODO: no-op? 72 | } 73 | 74 | @Override 75 | public void close() throws SailException { 76 | // TODO: graph transaction handling 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/GraphSailStatement.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Edge; 4 | import org.eclipse.rdf4j.model.IRI; 5 | import org.eclipse.rdf4j.model.Resource; 6 | import org.eclipse.rdf4j.model.Value; 7 | import org.eclipse.rdf4j.model.impl.ContextStatement; 8 | 9 | class GraphSailStatement extends ContextStatement { 10 | private final Edge edge; 11 | 12 | GraphSailStatement( 13 | final Edge edge, final Resource subject, final IRI predicate, final Value object, final Resource context) { 14 | super(subject, predicate, object, context); 15 | this.edge = edge; 16 | } 17 | 18 | Edge getEdge() { 19 | return edge; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/GraphSailStore.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.eclipse.rdf4j.IsolationLevel; 4 | import org.eclipse.rdf4j.model.ValueFactory; 5 | import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; 6 | import org.eclipse.rdf4j.sail.SailException; 7 | import org.eclipse.rdf4j.sail.base.BackingSailSource; 8 | import org.eclipse.rdf4j.sail.base.SailDataset; 9 | import org.eclipse.rdf4j.sail.base.SailSink; 10 | import org.eclipse.rdf4j.sail.base.SailSource; 11 | import org.eclipse.rdf4j.sail.base.SailStore; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | class GraphSailStore implements SailStore { 16 | private final Logger logger = LoggerFactory.getLogger(GraphSailStore.class); 17 | 18 | private final DataStore dataStore; 19 | private final SailSource sailSource; 20 | 21 | GraphSailStore(DataStore dataStore) { 22 | this.dataStore = dataStore; 23 | sailSource = new BackingSailSource() { 24 | @Override 25 | public SailSink sink(IsolationLevel isolationLevel) throws SailException { 26 | return new GraphSailSink(dataStore); 27 | } 28 | 29 | @Override 30 | public SailDataset dataset(IsolationLevel isolationLevel) throws SailException { 31 | return new GraphSailDataset(dataStore); 32 | } 33 | }; 34 | } 35 | 36 | @Override 37 | public ValueFactory getValueFactory() { 38 | return null; 39 | } 40 | 41 | @Override 42 | public EvaluationStatistics getEvaluationStatistics() { 43 | // TODO 44 | return new EvaluationStatistics(); 45 | } 46 | 47 | @Override 48 | public SailSource getExplicitSailSource() { 49 | return sailSource; 50 | } 51 | 52 | @Override 53 | public SailSource getInferredSailSource() { 54 | return sailSource; 55 | } 56 | 57 | @Override 58 | public void close() { 59 | try { 60 | dataStore.getGraph().close(); 61 | } catch (Exception e) { 62 | logger.error("failed to close graph", e); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/IterUtils.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.eclipse.rdf4j.common.iteration.CloseableIteration; 4 | 5 | import java.util.Collection; 6 | import java.util.Iterator; 7 | import java.util.LinkedList; 8 | import java.util.NoSuchElementException; 9 | import java.util.function.Function; 10 | import java.util.function.Predicate; 11 | 12 | class IterUtils { 13 | static CloseableIteration toCloseableIteration( 14 | final Iterator iterator, 15 | final Function mapping) { 16 | return new CloseableIteration() { 17 | @Override 18 | public void close() throws X { 19 | // do nothing 20 | } 21 | 22 | @Override 23 | public boolean hasNext() throws X { 24 | return iterator.hasNext(); 25 | } 26 | 27 | @Override 28 | public R next() throws X { 29 | return mapping.apply(iterator.next()); 30 | } 31 | 32 | @Override 33 | public void remove() throws X { 34 | throw new UnsupportedOperationException(); 35 | } 36 | }; 37 | } 38 | 39 | static CloseableIteration filter( 40 | final CloseableIteration base, 41 | final Predicate criterion) { 42 | return new CloseableIteration() { 43 | private T nextElement; 44 | 45 | @Override 46 | public void close() throws X { 47 | base.close(); 48 | } 49 | 50 | @Override 51 | public boolean hasNext() throws X { 52 | lookAhead(); 53 | return null != nextElement; 54 | } 55 | 56 | @Override 57 | public T next() throws X { 58 | lookAhead(); 59 | if (null == nextElement) { 60 | throw new NoSuchElementException(); 61 | } 62 | 63 | T tmp = nextElement; 64 | nextElement = null; 65 | return tmp; 66 | } 67 | 68 | @Override 69 | public void remove() throws X { 70 | throw new UnsupportedOperationException(); 71 | } 72 | 73 | private void lookAhead() throws X { 74 | while (null == nextElement) { 75 | if (!base.hasNext()) break; 76 | 77 | T tmp = base.next(); 78 | if (criterion.test(tmp)) { 79 | nextElement = tmp; 80 | } 81 | } 82 | } 83 | }; 84 | } 85 | 86 | static Collection collect(final CloseableIteration iter) throws X { 87 | try { 88 | Collection result = new LinkedList<>(); 89 | while (iter.hasNext()) { 90 | result.add(iter.next()); 91 | } 92 | return result; 93 | } finally { 94 | iter.close(); 95 | } 96 | } 97 | 98 | static long count(final CloseableIteration iter) throws X { 99 | try { 100 | long count = 0; 101 | while (iter.hasNext()) { 102 | iter.next(); 103 | count++; 104 | } 105 | return count; 106 | } finally { 107 | iter.close(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/NamespaceStore.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import com.google.common.base.Preconditions; 4 | import org.eclipse.rdf4j.common.iteration.CloseableIteration; 5 | import org.eclipse.rdf4j.model.Namespace; 6 | import org.eclipse.rdf4j.model.impl.SimpleNamespace; 7 | import org.eclipse.rdf4j.sail.SailException; 8 | 9 | import java.util.Map; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | /** 13 | * An in-memory store for IRI namespace definitions. 14 | * This store is not persisted between sessions. 15 | * 16 | * @author Joshua Shinavier (http://fortytwo.net) 17 | */ 18 | class NamespaceStore { 19 | private final Map namesByPrefix = new ConcurrentHashMap<>(); 20 | 21 | String get(final String prefix) { 22 | Preconditions.checkNotNull(prefix); 23 | 24 | return namesByPrefix.get(trim(prefix)); 25 | } 26 | 27 | void set(final String prefix, final String name) { 28 | Preconditions.checkNotNull(prefix); 29 | Preconditions.checkNotNull(name); 30 | 31 | namesByPrefix.put(trim(prefix), trim(name)); 32 | } 33 | 34 | void remove(final String prefix) { 35 | Preconditions.checkNotNull(trim(prefix)); 36 | 37 | namesByPrefix.remove(trim(prefix)); 38 | } 39 | 40 | void clear() { 41 | namesByPrefix.clear(); 42 | } 43 | 44 | CloseableIteration getAll() { 45 | return IterUtils.toCloseableIteration(namesByPrefix.entrySet().iterator(), 46 | e -> new SimpleNamespace(e.getKey(), e.getValue())); 47 | } 48 | 49 | private String trim(final String untrimmed) { 50 | return untrimmed.trim(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/Schema.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | interface Schema { 4 | enum VertexLabel {IRI, BNode, Literal} 5 | 6 | interface VertexProperties { 7 | String DATATYPE = "datatype"; 8 | String LANGUAGE = "language"; 9 | String VALUE = "value"; 10 | } 11 | 12 | interface EdgeProperties { 13 | String CONTEXT = "context"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/fortytwo/tpop/sail/tg/TinkerGraphIndex.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail.tg; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.fortytwo.tpop.sail.GraphIndex; 5 | import org.apache.tinkerpop.gremlin.structure.Vertex; 6 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; 7 | 8 | public class TinkerGraphIndex extends GraphIndex { 9 | private final TinkerGraph graph; 10 | 11 | public TinkerGraphIndex(final String key, final TinkerGraph graph) { 12 | super(key); 13 | this.graph = graph; 14 | } 15 | 16 | @Override 17 | protected boolean isAutomatic() { 18 | return true; 19 | } 20 | 21 | @Override 22 | public void initialize() { 23 | checkIsProperIndexKey(key); 24 | 25 | if (!keyExists(key)) { 26 | graph.createIndex(key, Vertex.class); 27 | } 28 | } 29 | 30 | @Override 31 | public void addInternal(Vertex vertex) { 32 | throw new UnsupportedOperationException(); 33 | } 34 | 35 | @Override 36 | public void removeInternal(final Vertex vertex) { 37 | throw new UnsupportedOperationException(); 38 | } 39 | 40 | private boolean keyExists(final String key) { 41 | return graph.getIndexedKeys(Vertex.class).contains(key); 42 | } 43 | 44 | private void checkIsProperIndexKey(final String key) { 45 | Preconditions.checkNotNull(key); 46 | Preconditions.checkArgument(key.length() > 0); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/net/fortytwo/tpop/sail/GetAndRetrieveStatementsTest.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.eclipse.rdf4j.model.BNode; 4 | import org.eclipse.rdf4j.model.vocabulary.RDF; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | public class GetAndRetrieveStatementsTest extends GraphSailTestBase { 10 | 11 | @Test 12 | public void firstTest() { 13 | createConnection(); 14 | 15 | assertEquals(0, countStatements()); 16 | connection.addStatement(RDF.TYPE, RDF.TYPE, RDF.TYPE); 17 | assertEquals(1, countStatements()); 18 | } 19 | 20 | @Test 21 | public void testTmp() { 22 | for (int i = 0; i < 10; i++) { 23 | BNode node = graphSail.getValueFactory().createBNode(); 24 | System.out.println("node: " + node.stringValue() + " " + node.getID() + " " + node.toString()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/net/fortytwo/tpop/sail/GraphSailTest.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import net.fortytwo.tpop.sail.tg.TinkerGraphIndex; 4 | import org.apache.tinkerpop.gremlin.structure.Graph; 5 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; 6 | import org.eclipse.rdf4j.common.iteration.CloseableIteration; 7 | import org.eclipse.rdf4j.sail.Sail; 8 | 9 | import java.util.function.Function; 10 | 11 | public class GraphSailTest extends SailTest { 12 | 13 | @Override 14 | protected void before() throws Exception { 15 | } 16 | 17 | @Override 18 | protected void after() throws Exception { 19 | } 20 | 21 | @Override 22 | protected Sail createSail() throws Exception { 23 | GraphWrapper wrapper = createGraphWrapper(); 24 | GraphSail sail = new GraphSail(wrapper.getGraph(), wrapper.getIndexFactory()); 25 | sail.enforceUniqueStatements(uniqueStatements); 26 | return sail; 27 | } 28 | 29 | // override me for alternative graph back-ends 30 | protected GraphWrapper createGraphWrapper() { 31 | return createTinkerGraphWrapper(); 32 | } 33 | 34 | private GraphWrapper createTinkerGraphWrapper() { 35 | TinkerGraph graph = TinkerGraph.open(); 36 | return new GraphWrapper(graph, key -> new TinkerGraphIndex(key, graph)); 37 | } 38 | 39 | protected static int countIterator(final CloseableIteration iter) throws X { 40 | int count = 0; 41 | while (iter.hasNext()) { 42 | iter.next(); 43 | count++; 44 | } 45 | iter.close(); 46 | return count; 47 | } 48 | 49 | private static class GraphWrapper { 50 | private final Graph graph; 51 | private final Function indexFactory; 52 | 53 | private GraphWrapper(final Graph graph, final Function indexFactory) { 54 | this.graph = graph; 55 | this.indexFactory = indexFactory; 56 | } 57 | 58 | public Graph getGraph() { 59 | return graph; 60 | } 61 | 62 | public Function getIndexFactory() { 63 | return indexFactory; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/net/fortytwo/tpop/sail/GraphSailTestBase.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import net.fortytwo.tpop.sail.tg.TinkerGraphIndex; 4 | import org.apache.tinkerpop.gremlin.structure.Graph; 5 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; 6 | import org.eclipse.rdf4j.common.iteration.CloseableIteration; 7 | import org.eclipse.rdf4j.sail.SailConnection; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | 11 | import java.util.function.Function; 12 | 13 | public class GraphSailTestBase { 14 | 15 | protected GraphSail graphSail; 16 | protected SailConnection connection; 17 | 18 | @Before 19 | public void setUp() { 20 | graphSail = createSail(); 21 | graphSail.initialize(); 22 | } 23 | 24 | @After 25 | public void tearDown() { 26 | graphSail.shutDown(); 27 | } 28 | 29 | protected GraphSail createSail() { 30 | GraphWrapper wrapper = createGraphWrapper(); 31 | return new GraphSail(wrapper.getGraph(), wrapper.getIndexFactory()); 32 | } 33 | 34 | // override me for alternative graph back-ends 35 | protected GraphWrapper createGraphWrapper() { 36 | return createTinkerGraphWrapper(); 37 | } 38 | 39 | protected void createConnection() { 40 | connection = graphSail.getConnection(); 41 | } 42 | 43 | protected int countStatements() { 44 | return countIterator(connection.getStatements(null, null, null, false)); 45 | } 46 | 47 | private GraphWrapper createTinkerGraphWrapper() { 48 | TinkerGraph graph = TinkerGraph.open(); 49 | return new GraphWrapper(graph, key -> new TinkerGraphIndex(key, graph)); 50 | } 51 | 52 | protected static int countIterator(final CloseableIteration iter) throws X { 53 | int count = 0; 54 | while (iter.hasNext()) { 55 | iter.next(); 56 | count++; 57 | } 58 | iter.close(); 59 | return count; 60 | } 61 | 62 | private static class GraphWrapper { 63 | private final Graph graph; 64 | private final Function indexFactory; 65 | 66 | private GraphWrapper(final Graph graph, final Function indexFactory) { 67 | this.graph = graph; 68 | this.indexFactory = indexFactory; 69 | } 70 | 71 | public Graph getGraph() { 72 | return graph; 73 | } 74 | 75 | public Function getIndexFactory() { 76 | return indexFactory; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/net/fortytwo/tpop/sail/NamespaceStoreTest.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | import static org.junit.Assert.assertNull; 8 | 9 | public class NamespaceStoreTest extends GraphSailTestBase { 10 | private static final String EX_PREFIX = "ex"; 11 | private static final String EX_NAME = "http://example.org/"; 12 | 13 | private NamespaceStore namespaces; 14 | 15 | @Before 16 | public void setUp() { 17 | namespaces = new NamespaceStore(); 18 | } 19 | 20 | @Test 21 | public void addAndGetNameSpaceIsSuccessful() { 22 | assertEquals(0, countNamespaces()); 23 | namespaces.set(EX_PREFIX, EX_NAME); 24 | assertEquals(1, countNamespaces()); 25 | assertEquals(EX_NAME, namespaces.get(EX_PREFIX)); 26 | assertEquals(EX_PREFIX, namespaces.getAll().next().getPrefix()); 27 | assertEquals(EX_NAME, namespaces.getAll().next().getName()); 28 | namespaces.set(EX_PREFIX + "2", EX_NAME + "2"); 29 | assertEquals(2, countNamespaces()); 30 | } 31 | 32 | @Test(expected = NullPointerException.class) 33 | public void addNullPrefixFails() { 34 | namespaces.set(null, EX_NAME); 35 | } 36 | 37 | @Test(expected = NullPointerException.class) 38 | public void addNullNameFails() { 39 | namespaces.set(EX_PREFIX, null); 40 | } 41 | 42 | @Test 43 | public void nameForNonexistentPrefixIsNull() { 44 | assertNull(namespaces.get("foo")); 45 | } 46 | @Test 47 | public void getAllFromEmptyStoreIsTrivial() { 48 | assertEquals(0, countNamespaces()); 49 | } 50 | 51 | @Test(expected = NullPointerException.class) 52 | public void getNullPrefixFails() { 53 | namespaces.get(null); 54 | } 55 | 56 | @Test 57 | public void emptyPrefixIsAllowed() { 58 | assertNull(namespaces.get("")); 59 | namespaces.set("", EX_NAME); 60 | assertEquals(EX_NAME, namespaces.get("")); 61 | namespaces.set("", EX_NAME + "2"); 62 | assertEquals(EX_NAME + "2", namespaces.get("")); 63 | } 64 | 65 | @Test 66 | public void emptyNameIsAllowed() { 67 | namespaces.set(EX_PREFIX, ""); 68 | assertEquals("", namespaces.get(EX_PREFIX)); 69 | } 70 | 71 | @Test 72 | public void duplicateNamesAreAllowed() { 73 | namespaces.set(EX_PREFIX, EX_NAME); 74 | namespaces.set(EX_PREFIX + "2", EX_NAME); 75 | assertEquals(2, countNamespaces()); 76 | assertEquals(EX_NAME, namespaces.get(EX_PREFIX)); 77 | assertEquals(EX_NAME, namespaces.get(EX_PREFIX + "2")); 78 | } 79 | 80 | @Test 81 | public void duplicatePrefixOverridesPreviousName() { 82 | namespaces.set(EX_PREFIX, EX_NAME); 83 | assertEquals(EX_NAME, namespaces.get(EX_PREFIX)); 84 | namespaces.set(EX_PREFIX, EX_NAME + "2"); 85 | assertEquals(EX_NAME + "2", namespaces.get(EX_PREFIX)); 86 | assertEquals(1, countNamespaces()); 87 | } 88 | 89 | @Test 90 | public void prefixesAndNamesAreTrimmed() { 91 | namespaces.set(" " + EX_PREFIX, EX_NAME + " \t"); 92 | assertEquals(EX_NAME, namespaces.get(EX_PREFIX)); 93 | } 94 | 95 | private int countNamespaces() { 96 | return countIterator(namespaces.getAll()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/net/fortytwo/tpop/sail/SailAdder.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | import org.eclipse.rdf4j.model.Resource; 4 | import org.eclipse.rdf4j.model.Statement; 5 | import org.eclipse.rdf4j.rio.RDFHandler; 6 | import org.eclipse.rdf4j.rio.RDFHandlerException; 7 | import org.eclipse.rdf4j.sail.SailConnection; 8 | import org.eclipse.rdf4j.sail.SailException; 9 | 10 | class SailAdder implements RDFHandler { 11 | private final SailConnection c; 12 | private final Resource[] contexts; 13 | 14 | public SailAdder(final SailConnection c, 15 | final Resource... contexts) { 16 | this.c = c; 17 | this.contexts = contexts; 18 | } 19 | 20 | public void startRDF() throws RDFHandlerException { 21 | } 22 | 23 | public void endRDF() throws RDFHandlerException { 24 | } 25 | 26 | public void handleNamespace(final String prefix, 27 | final String uri) throws RDFHandlerException { 28 | try { 29 | c.setNamespace(prefix, uri); 30 | } catch (SailException e) { 31 | throw new RDFHandlerException(e); 32 | } 33 | } 34 | 35 | public void handleStatement(final Statement s) throws RDFHandlerException { 36 | try { 37 | if (1 <= contexts.length) { 38 | for (Resource x : contexts) { 39 | c.addStatement(s.getSubject(), s.getPredicate(), s.getObject(), x); 40 | } 41 | } else { 42 | c.addStatement(s.getSubject(), s.getPredicate(), s.getObject(), s.getContext()); 43 | } 44 | } catch (SailException e) { 45 | throw new RDFHandlerException(e); 46 | } 47 | } 48 | 49 | public void handleComment(String s) throws RDFHandlerException { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/net/fortytwo/tpop/sail/SailTest.java: -------------------------------------------------------------------------------- 1 | package net.fortytwo.tpop.sail; 2 | 3 | 4 | import com.google.common.base.Preconditions; 5 | import org.eclipse.rdf4j.common.iteration.CloseableIteration; 6 | import org.eclipse.rdf4j.model.BNode; 7 | import org.eclipse.rdf4j.model.Literal; 8 | import org.eclipse.rdf4j.model.Namespace; 9 | import org.eclipse.rdf4j.model.Resource; 10 | import org.eclipse.rdf4j.model.Statement; 11 | import org.eclipse.rdf4j.model.IRI; 12 | import org.eclipse.rdf4j.model.Value; 13 | import org.eclipse.rdf4j.model.ValueFactory; 14 | import org.eclipse.rdf4j.model.datatypes.XMLDatatypeUtil; 15 | import org.eclipse.rdf4j.model.vocabulary.RDF; 16 | import org.eclipse.rdf4j.model.vocabulary.RDFS; 17 | import org.eclipse.rdf4j.model.vocabulary.XMLSchema; 18 | import org.eclipse.rdf4j.query.BindingSet; 19 | import org.eclipse.rdf4j.query.QueryEvaluationException; 20 | import org.eclipse.rdf4j.query.impl.EmptyBindingSet; 21 | import org.eclipse.rdf4j.query.parser.ParsedQuery; 22 | import org.eclipse.rdf4j.query.parser.sparql.SPARQLParser; 23 | import org.eclipse.rdf4j.rio.RDFFormat; 24 | import org.eclipse.rdf4j.rio.RDFHandler; 25 | import org.eclipse.rdf4j.rio.RDFParser; 26 | import org.eclipse.rdf4j.rio.Rio; 27 | import org.eclipse.rdf4j.sail.NotifyingSail; 28 | import org.eclipse.rdf4j.sail.NotifyingSailConnection; 29 | import org.eclipse.rdf4j.sail.Sail; 30 | import org.eclipse.rdf4j.sail.SailChangedEvent; 31 | import org.eclipse.rdf4j.sail.SailChangedListener; 32 | import org.eclipse.rdf4j.sail.SailConnection; 33 | import org.eclipse.rdf4j.sail.SailConnectionListener; 34 | import org.eclipse.rdf4j.sail.SailException; 35 | import org.eclipse.rdf4j.sail.inferencer.InferencerConnection; 36 | import org.eclipse.rdf4j.sail.inferencer.fc.ForwardChainingRDFSInferencer; 37 | import org.junit.After; 38 | import org.junit.Before; 39 | import org.junit.Ignore; 40 | import org.junit.Test; 41 | 42 | import javax.xml.datatype.XMLGregorianCalendar; 43 | import java.io.InputStream; 44 | import java.util.Collection; 45 | import java.util.HashSet; 46 | import java.util.Iterator; 47 | import java.util.LinkedList; 48 | import java.util.Set; 49 | 50 | import static org.junit.Assert.assertEquals; 51 | import static org.junit.Assert.assertFalse; 52 | import static org.junit.Assert.assertNotEquals; 53 | import static org.junit.Assert.assertNotNull; 54 | import static org.junit.Assert.assertNull; 55 | import static org.junit.Assert.assertTrue; 56 | 57 | /** 58 | * @author Joshua Shinavier (http://fortytwo.net) 59 | */ 60 | public abstract class SailTest { 61 | protected Sail sail; 62 | protected ForwardChainingRDFSInferencer inferencer; 63 | 64 | protected boolean uniqueStatements = false; 65 | 66 | @Before 67 | public final void setUp() throws Exception { 68 | before(); 69 | this.sail = createSail(); 70 | sail.initialize(); 71 | 72 | if (sail instanceof NotifyingSail) { 73 | try (SailConnection sc = getConnection()) { 74 | if (sc instanceof InferencerConnection) { 75 | inferencer = new ForwardChainingRDFSInferencer((NotifyingSail) sail); 76 | } 77 | } 78 | } 79 | } 80 | 81 | @After 82 | public final void tearDown() throws Exception { 83 | sail.shutDown(); 84 | after(); 85 | } 86 | 87 | protected abstract void before() throws Exception; 88 | 89 | protected abstract void after() throws Exception; 90 | 91 | protected abstract Sail createSail() throws Exception; 92 | 93 | // statement manipulation ////////////////////////////////////////////////// 94 | 95 | @Test 96 | public void testGetStatementsS_POG() throws Exception { 97 | try (SailConnection sc = getConnection()) { 98 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/S_POG#a"); 99 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/S_POG#b"); 100 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/test/S_POG#c"); 101 | IRI uriD = sail.getValueFactory().createIRI("http://example.org/test/S_POG#d"); 102 | long before, after; 103 | 104 | // default context, different S,P,O 105 | sc.removeStatements(uriA, null, null); 106 | commit(sc); 107 | before = countStatements(sc, uriA, null, null, false); 108 | sc.addStatement(uriA, uriB, uriC); 109 | commit(sc); 110 | after = countStatements(sc, uriA, null, null, false); 111 | assertEquals(0, before); 112 | System.out.flush(); 113 | assertEquals(1, after); 114 | 115 | // one specific context, different S,P,O 116 | sc.removeStatements(uriA, null, null, uriD); 117 | commit(sc); 118 | before = countStatements(sc, uriA, null, null, false, uriD); 119 | sc.addStatement(uriA, uriB, uriC, uriD); 120 | commit(sc); 121 | after = countStatements(sc, uriA, null, null, false, uriD); 122 | assertEquals(0, before); 123 | assertEquals(1, after); 124 | 125 | // one specific context, same S,P,O,G 126 | sc.removeStatements(uriA, null, null, uriA); 127 | commit(sc); 128 | before = countStatements(sc, uriA, null, null, false, uriA); 129 | sc.addStatement(uriA, uriB, uriC, uriA); 130 | commit(sc); 131 | after = countStatements(sc, uriA, null, null, false, uriA); 132 | assertEquals(0, before); 133 | assertEquals(1, after); 134 | 135 | // default context, same S,P,O 136 | sc.removeStatements(uriA, null, null); 137 | commit(sc); 138 | before = countStatements(sc, uriA, null, null, false); 139 | sc.addStatement(uriA, uriB, uriC); 140 | commit(sc); 141 | after = countStatements(sc, uriA, null, null, false); 142 | assertEquals(0, before); 143 | assertEquals(1, after); 144 | } 145 | } 146 | 147 | @Test 148 | public void testGetStatementsSP_OG() throws Exception { 149 | try (SailConnection sc = getConnection()) { 150 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/SP_OG#a"); 151 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/SP_OG#b"); 152 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/test/SP_OG#c"); 153 | long before, after; 154 | 155 | // Add statement to the implicit null context. 156 | sc.removeStatements(null, null, null); 157 | before = countStatements(sc, uriA, uriB, null, false); 158 | sc.addStatement(uriA, uriB, uriC); 159 | commit(sc); 160 | after = countStatements(sc, uriA, uriB, null, false); 161 | assertEquals(0, before); 162 | assertEquals(1, after); 163 | } 164 | } 165 | 166 | @Test 167 | public void testGetStatementsO_SPG() throws Exception { 168 | try (SailConnection sc = getConnection()) { 169 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/O_SPG#a"); 170 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/O_SPG#b"); 171 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/test/O_SPG#c"); 172 | Literal plainLitA = sail.getValueFactory().createLiteral("arbitrary plain literal 9548734867"); 173 | Literal stringLitA = sail.getValueFactory().createLiteral("arbitrary string literal 8765", XMLSchema.STRING); 174 | long before, after; 175 | 176 | // Add statement to a specific context. 177 | sc.removeStatements(null, null, uriA, uriA); 178 | commit(sc); 179 | before = countStatements(sc, null, null, uriA, false); 180 | sc.addStatement(uriB, uriC, uriA); 181 | commit(sc); 182 | after = countStatements(sc, null, null, uriA, false); 183 | assertEquals(0, before); 184 | assertEquals(1, after); 185 | 186 | // Add plain literal statement to the default context. 187 | sc.removeStatements(null, null, plainLitA); 188 | commit(sc); 189 | before = countStatements(sc, null, null, plainLitA, false); 190 | sc.addStatement(uriA, uriA, plainLitA); 191 | commit(sc); 192 | after = countStatements(sc, null, null, plainLitA, false); 193 | assertEquals(0, before); 194 | assertEquals(1, after); 195 | 196 | // Add string-typed literal statement to the default context. 197 | sc.removeStatements(null, null, plainLitA); 198 | commit(sc); 199 | before = countStatements(sc, null, null, stringLitA, false); 200 | sc.addStatement(uriA, uriA, stringLitA); 201 | commit(sc); 202 | after = countStatements(sc, null, null, stringLitA, false); 203 | assertEquals(0, before); 204 | assertEquals(1, after); 205 | } 206 | } 207 | 208 | @Test 209 | public void testGetStatementsPO_SG() throws Exception { 210 | try (SailConnection sc = getConnection()) { 211 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/PO_SG#a"); 212 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/PO_SG#b"); 213 | IRI foo = sail.getValueFactory().createIRI("http://example.org/ns#foo"); 214 | IRI firstName = sail.getValueFactory().createIRI("http://example.org/ns#firstName"); 215 | Literal plainLitA = sail.getValueFactory().createLiteral("arbitrary plain literal 8765675"); 216 | Literal fooLabel = sail.getValueFactory().createLiteral("foo", XMLSchema.STRING); 217 | long before, after; 218 | 219 | // Add statement to the implicit null context. 220 | sc.removeStatements(null, null, null, uriA); 221 | commit(sc); 222 | before = countStatements(sc, null, uriA, uriB, false); 223 | sc.addStatement(uriA, uriA, uriB); 224 | commit(sc); 225 | after = countStatements(sc, null, uriA, uriB, false); 226 | assertEquals(0, before); 227 | assertEquals(1, after); 228 | 229 | // Add plain literal statement to the default context. 230 | sc.removeStatements(null, null, plainLitA); 231 | commit(sc); 232 | before = countStatements(sc, null, uriA, plainLitA, false); 233 | sc.addStatement(uriA, uriA, plainLitA); 234 | sc.addStatement(uriA, uriB, plainLitA); 235 | sc.addStatement(uriB, uriB, plainLitA); 236 | commit(sc); 237 | after = countStatements(sc, null, uriA, plainLitA, false); 238 | assertEquals(0, before); 239 | assertEquals(1, after); 240 | 241 | // Add string-typed literal statement to the default context. 242 | sc.removeStatements(null, null, fooLabel); 243 | commit(sc); 244 | before = countStatements(sc, null, firstName, fooLabel, false); 245 | sc.addStatement(foo, firstName, fooLabel); 246 | commit(sc); 247 | after = countStatements(sc, null, firstName, fooLabel, false); 248 | assertEquals(0, before); 249 | assertEquals(1, after); 250 | assertEquals(foo, toSet(sc.getStatements(null, firstName, fooLabel, false)).iterator().next().getSubject()); 251 | 252 | } 253 | } 254 | 255 | @Test 256 | public void testGetStatementsSPO_G() throws Exception { 257 | try (SailConnection sc = getConnection()) { 258 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/S_POG#a"); 259 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/S_POG#b"); 260 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/test/S_POG#c"); 261 | IRI uriD = sail.getValueFactory().createIRI("http://example.org/test/S_POG#d"); 262 | long before, after; 263 | 264 | // default context, different S,P,O 265 | sc.removeStatements(uriA, null, null); 266 | commit(sc); 267 | before = countStatements(sc, uriA, uriB, uriC, false); 268 | sc.addStatement(uriA, uriB, uriC); 269 | commit(sc); 270 | after = countStatements(sc, uriA, uriB, uriC, false); 271 | assertEquals(0, before); 272 | assertEquals(1, after); 273 | 274 | // default context, same S,P,O 275 | sc.removeStatements(uriA, null, null); 276 | commit(sc); 277 | before = countStatements(sc, uriA, uriB, uriC, false); 278 | sc.addStatement(uriA, uriB, uriC); 279 | commit(sc); 280 | after = countStatements(sc, uriA, uriB, uriC, false); 281 | assertEquals(0, before); 282 | assertEquals(1, after); 283 | 284 | // one specific context, different S,P,O 285 | sc.removeStatements(uriA, null, null, uriD); 286 | commit(sc); 287 | before = countStatements(sc, uriA, uriB, uriC, false, uriD); 288 | sc.addStatement(uriA, uriB, uriC, uriD); 289 | commit(sc); 290 | after = countStatements(sc, uriA, uriB, uriC, false, uriD); 291 | assertEquals(0, before); 292 | assertEquals(1, after); 293 | 294 | // one specific context, same S,P,O,G 295 | sc.removeStatements(uriA, null, null, uriA); 296 | commit(sc); 297 | before = countStatements(sc, uriA, uriB, uriC, false, uriA); 298 | sc.addStatement(uriA, uriB, uriC, uriA); 299 | commit(sc); 300 | after = countStatements(sc, uriA, uriB, uriC, false, uriA); 301 | assertEquals(0, before); 302 | assertEquals(1, after); 303 | } 304 | } 305 | 306 | @Test 307 | public void testGetStatementsP_SOG() throws Exception { 308 | try (SailConnection sc = getConnection()) { 309 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/P_SOG#a"); 310 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/P_SOG#b"); 311 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/test/P_SOG#c"); 312 | IRI foo = sail.getValueFactory().createIRI("http://example.org/ns#foo"); 313 | IRI firstName = sail.getValueFactory().createIRI("http://example.org/ns#firstName"); 314 | Literal plainLitA = sail.getValueFactory().createLiteral("arbitrary plain literal 238445"); 315 | Literal fooLabel = sail.getValueFactory().createLiteral("foo", XMLSchema.STRING); 316 | long before, after; 317 | 318 | // Add statement to the implicit null context. 319 | sc.removeStatements(null, uriA, null); 320 | commit(sc); 321 | before = countStatements(sc, null, uriA, null, false); 322 | sc.addStatement(uriB, uriA, uriC); 323 | commit(sc); 324 | after = countStatements(sc, null, uriA, null, false); 325 | assertEquals(0, before); 326 | assertEquals(1, after); 327 | 328 | // Add plain literal statement to the default context. 329 | sc.removeStatements(null, uriA, null); 330 | commit(sc); 331 | before = countStatements(sc, null, uriA, null, false); 332 | sc.addStatement(uriA, uriA, plainLitA); 333 | sc.addStatement(uriA, uriB, plainLitA); 334 | sc.addStatement(uriB, uriB, plainLitA); 335 | commit(sc); 336 | after = countStatements(sc, null, uriA, null, false); 337 | assertEquals(0, before); 338 | assertEquals(1, after); 339 | 340 | // Add string-typed literal statement to the default context. 341 | sc.removeStatements(null, firstName, null); 342 | commit(sc); 343 | before = countStatements(sc, null, firstName, null, false); 344 | sc.addStatement(foo, firstName, fooLabel); 345 | commit(sc); 346 | after = countStatements(sc, null, firstName, null, false); 347 | assertEquals(0, before); 348 | assertEquals(1, after); 349 | assertEquals(foo, toSet(sc.getStatements(null, firstName, null, false)).iterator().next().getSubject()); 350 | 351 | // Add statement to a non-null context. 352 | sc.removeStatements(null, uriA, null); 353 | commit(sc); 354 | before = countStatements(sc, null, uriA, null, false); 355 | sc.addStatement(uriB, uriA, uriC, uriA); 356 | commit(sc); 357 | after = countStatements(sc, null, uriA, null, false); 358 | assertEquals(0, before); 359 | assertEquals(1, after); 360 | 361 | sc.removeStatements(null, uriA, null); 362 | commit(sc); 363 | before = countStatements(sc, null, uriA, null, false); 364 | sc.addStatement(uriB, uriA, uriC, uriC); 365 | sc.addStatement(uriC, uriA, uriA, uriA); 366 | commit(sc); 367 | sc.addStatement(uriA, uriA, uriB, uriB); 368 | commit(sc); 369 | after = countStatements(sc, null, uriA, null, false); 370 | assertEquals(0, before); 371 | assertEquals(3, after); 372 | } 373 | } 374 | 375 | @Test 376 | public void testGetStatementsWithVariableContexts() throws Exception { 377 | try (SailConnection sc = getConnection()) { 378 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 379 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 380 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/uriC"); 381 | long count; 382 | sc.clear(); 383 | //sc.removeStatements(uriA, uriA, uriA); 384 | commit(sc); 385 | Resource[] contexts = {uriA, null}; 386 | sc.addStatement(uriA, uriB, uriC, contexts); 387 | commit(sc); 388 | 389 | // Get statements from all contexts. 390 | count = countStatements(sc, uriA, null, null, false); 391 | assertEquals(2, count); 392 | 393 | // Get statements from a specific partition context. 394 | count = countStatements(sc, null, null, null, false, uriA); 395 | assertEquals(1, count); 396 | 397 | // Get statements from the null context. 398 | Resource[] c = {null}; 399 | count = countStatements(sc, null, null, null, false, c); 400 | //assertTrue(count > 0); 401 | assertEquals(1, count); 402 | long countLast = count; 403 | 404 | // Get statements from more than one context. 405 | count = countStatements(sc, null, null, null, false, contexts); 406 | assertEquals(1 + countLast, count); 407 | 408 | } 409 | } 410 | 411 | @Test 412 | public void testRemoveStatements() throws Exception { 413 | try (SailConnection sc = getConnection()) { 414 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 415 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 416 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/uriC"); 417 | Resource[] contexts = {uriA, null}; 418 | long count; 419 | 420 | // Remove from all contexts. 421 | sc.removeStatements(uriA, null, null); 422 | commit(sc); 423 | count = countStatements(sc, uriA, null, null, false); 424 | assertEquals(0, count); 425 | sc.addStatement(uriA, uriB, uriC, contexts); 426 | commit(sc); 427 | count = countStatements(sc, uriA, null, null, false); 428 | // two, because the statement was added to two contexts 429 | assertEquals(2, count); 430 | sc.removeStatements(uriA, null, null); 431 | commit(sc); 432 | count = countStatements(sc, uriA, null, null, false); 433 | assertEquals(0, count); 434 | 435 | // Remove from one partition context. 436 | sc.removeStatements(uriA, null, null); 437 | commit(sc); 438 | count = countStatements(sc, uriA, null, null, false); 439 | assertEquals(0, count); 440 | sc.addStatement(uriA, uriB, uriC, contexts); 441 | commit(sc); 442 | count = countStatements(sc, uriA, null, null, false); 443 | assertEquals(2, count); 444 | Resource[] oneContext = {uriA}; 445 | sc.removeStatements(uriA, null, null, oneContext); 446 | commit(sc); 447 | count = countStatements(sc, uriA, null, null, false); 448 | assertEquals(1, count); 449 | 450 | // Remove from the null context. 451 | sc.removeStatements(uriA, null, null); 452 | commit(sc); 453 | count = countStatements(sc, uriA, null, null, false); 454 | assertEquals(0, count); 455 | sc.addStatement(uriA, uriB, uriC, contexts); 456 | commit(sc); 457 | count = countStatements(sc, uriA, null, null, false); 458 | assertEquals(2, count); 459 | Resource[] nullContext = {null}; 460 | sc.removeStatements(uriA, null, null, nullContext); 461 | commit(sc); 462 | count = countStatements(sc, uriA, null, null, false); 463 | assertEquals(1, count); 464 | 465 | // Remove from more than one context. 466 | sc.removeStatements(uriA, null, null); 467 | commit(sc); 468 | count = countStatements(sc, uriA, null, null, false); 469 | assertEquals(0, count); 470 | sc.addStatement(uriA, uriB, uriC, contexts); 471 | commit(sc); 472 | count = countStatements(sc, uriA, null, null, false); 473 | assertEquals(2, count); 474 | sc.removeStatements(uriA, null, null); 475 | commit(sc); 476 | count = countStatements(sc, uriA, null, null, false, contexts); 477 | assertEquals(0, count); 478 | } 479 | } 480 | 481 | @Test 482 | public void testClear() throws Exception { 483 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 484 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 485 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/uriC"); 486 | 487 | try (SailConnection sc = getConnection()) { 488 | sc.clear(); 489 | assertEquals(0L, sc.size()); 490 | sc.addStatement(uriA, uriB, uriC, uriA); 491 | sc.addStatement(uriC, uriA, uriB, uriA); 492 | sc.addStatement(uriB, uriC, uriA, uriA); 493 | assertEquals(3L, sc.size(uriA)); 494 | sc.addStatement(uriA, uriB, uriC, uriB); 495 | sc.addStatement(uriB, uriC, uriA, uriB); 496 | assertEquals(2L, sc.size(uriB)); 497 | sc.addStatement(uriA, uriB, uriC); 498 | assertEquals(1L, sc.size((Resource) null)); 499 | sc.addStatement(uriA, uriB, uriC, uriC); 500 | sc.addStatement(uriB, uriC, uriA, uriC); 501 | sc.addStatement(uriC, uriA, uriB, uriC); 502 | sc.addStatement(uriA, uriB, uriB, uriC); 503 | assertEquals(4L, sc.size(uriC)); 504 | assertEquals(10L, sc.size()); 505 | sc.clear(uriA, uriC); 506 | assertEquals(1L, sc.size((Resource) null)); 507 | assertEquals(0L, sc.size(uriA)); 508 | assertEquals(2L, sc.size(uriB)); 509 | assertEquals(0L, sc.size(uriC)); 510 | assertEquals(3L, sc.size()); 511 | sc.clear(); 512 | assertEquals(0L, sc.size()); 513 | commit(sc); 514 | } 515 | } 516 | 517 | @Test 518 | public void testGetContextIDs() throws Exception { 519 | // TODO 520 | } 521 | 522 | @Test 523 | public void testSize() throws Exception { 524 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 525 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 526 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/uriC"); 527 | 528 | try (SailConnection sc = getConnection()) { 529 | sc.removeStatements(null, null, null); 530 | 531 | assertEquals(0L, sc.size()); 532 | sc.addStatement(uriA, uriB, uriC, uriA); 533 | // commit(sc); 534 | for (Statement st : IterUtils.collect(sc.getStatements(null, null, null, false))) { 535 | System.out.println("st: " + st); 536 | } 537 | assertEquals(1L, sc.size()); 538 | sc.addStatement(uriA, uriB, uriC, uriB); 539 | // commit(sc); 540 | assertEquals(2L, sc.size()); 541 | sc.addStatement(uriB, uriB, uriC, uriB); 542 | // commit(sc); 543 | assertEquals(3L, sc.size()); 544 | sc.addStatement(uriC, uriB, uriA); 545 | // commit(sc); 546 | assertEquals(4L, sc.size()); 547 | assertEquals(1L, sc.size(uriA)); 548 | assertEquals(2L, sc.size(uriB)); 549 | assertEquals(0L, sc.size(uriC)); 550 | assertEquals(1L, sc.size((IRI) null)); 551 | assertEquals(3L, sc.size(uriB, null)); 552 | assertEquals(3L, sc.size(uriB, uriC, null)); 553 | assertEquals(4L, sc.size(uriA, uriB, null)); 554 | assertEquals(4L, sc.size(uriA, uriB, uriC, null)); 555 | assertEquals(3L, sc.size(uriA, uriB)); 556 | commit(sc); 557 | } 558 | } 559 | 560 | @Test 561 | public void testDuplicateStatements() throws Exception { 562 | if (uniqueStatements) { 563 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 564 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 565 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/uriC"); 566 | try (SailConnection sc = getConnection()) { 567 | sc.clear(); 568 | assertEquals(0, countStatements(sc, uriA, uriB, uriC, false)); 569 | sc.addStatement(uriA, uriB, uriC); 570 | assertEquals(1, countStatements(sc, uriA, uriB, uriC, false)); 571 | sc.addStatement(uriA, uriB, uriC); 572 | assertEquals(1, countStatements(sc, uriA, uriB, uriC, false)); 573 | 574 | sc.addStatement(uriA, uriB, uriC, uriC); 575 | assertEquals(2, countStatements(sc, uriA, uriB, uriC, false)); 576 | assertEquals(1, countStatements(sc, uriA, uriB, uriC, false, uriC)); 577 | commit(sc); 578 | } 579 | } 580 | } 581 | 582 | // IRIs //////////////////////////////////////////////////////////////////// 583 | 584 | // literals //////////////////////////////////////////////////////////////// 585 | 586 | // Note: this test will always pass as long as we're using ValueFactoryImpl 587 | 588 | @Test 589 | public void testCreateLiteralsThroughValueFactory() throws Exception { 590 | Literal l; 591 | ValueFactory vf = sail.getValueFactory(); 592 | 593 | l = vf.createLiteral("a plain literal"); 594 | assertNotNull(l); 595 | assertFalse(l.getLanguage().isPresent()); 596 | assertEquals("a plain literal", l.getLabel()); 597 | assertEquals(XMLSchema.STRING, l.getDatatype()); 598 | l = vf.createLiteral("auf Deutsch, bitte", "de"); 599 | assertNotNull(l); 600 | assertEquals("de", l.getLanguage().get()); 601 | assertEquals("auf Deutsch, bitte", l.getLabel()); 602 | assertEquals(RDF.LANGSTRING, l.getDatatype()); 603 | 604 | // Test data-typed createLiteral methods 605 | l = vf.createLiteral("foo", XMLSchema.STRING); 606 | assertNotNull(l); 607 | assertFalse(l.getLanguage().isPresent()); 608 | assertEquals("foo", l.getLabel()); 609 | assertEquals(XMLSchema.STRING, l.getDatatype()); 610 | l = vf.createLiteral(42); 611 | assertNotNull(l); 612 | assertFalse(l.getLanguage().isPresent()); 613 | assertEquals("42", l.getLabel()); 614 | assertEquals(42, l.intValue()); 615 | assertEquals(XMLSchema.INT, l.getDatatype()); 616 | l = vf.createLiteral(42L); 617 | assertNotNull(l); 618 | assertFalse(l.getLanguage().isPresent()); 619 | assertEquals("42", l.getLabel()); 620 | assertEquals(42L, l.longValue()); 621 | assertEquals(XMLSchema.LONG, l.getDatatype()); 622 | l = vf.createLiteral((short) 42); 623 | assertNotNull(l); 624 | assertFalse(l.getLanguage().isPresent()); 625 | assertEquals("42", l.getLabel()); 626 | assertEquals((short) 42, l.shortValue()); 627 | assertEquals(XMLSchema.SHORT, l.getDatatype()); 628 | l = vf.createLiteral(true); 629 | assertNotNull(l); 630 | assertFalse(l.getLanguage().isPresent()); 631 | assertEquals("true", l.getLabel()); 632 | assertEquals(true, l.booleanValue()); 633 | assertEquals(XMLSchema.BOOLEAN, l.getDatatype()); 634 | l = vf.createLiteral((byte) 'c'); 635 | assertNotNull(l); 636 | assertFalse(l.getLanguage().isPresent()); 637 | assertEquals("99", l.getLabel()); 638 | assertEquals((byte) 'c', l.byteValue()); 639 | assertEquals(XMLSchema.BYTE, l.getDatatype()); 640 | XMLGregorianCalendar calendar = XMLDatatypeUtil.parseCalendar("2002-10-10T12:00:00-05:00"); 641 | l = vf.createLiteral(calendar); 642 | assertNotNull(l); 643 | assertFalse(l.getLanguage().isPresent()); 644 | assertEquals("2002-10-10T12:00:00-05:00", l.getLabel()); 645 | assertEquals(calendar, l.calendarValue()); 646 | assertEquals(XMLSchema.DATETIME, l.getDatatype()); 647 | } 648 | 649 | @Test 650 | public void testGetLiteralsFromTripleStore() throws Exception { 651 | addTestFile(); 652 | 653 | Literal l; 654 | String prefix = "urn:com.tinkerpop.blueprints.pgm.oupls.sail.test/"; 655 | XMLGregorianCalendar calendar; 656 | ValueFactory vf = sail.getValueFactory(); 657 | SailConnection sc = getConnection(); 658 | 659 | // Get an actual plain literal from the triple store. 660 | IRI ford = vf.createIRI(prefix + "ford"); 661 | l = (Literal) toSet(sc.getStatements(ford, RDFS.COMMENT, null, false)).iterator().next().getObject(); 662 | assertNotNull(l); 663 | assertEquals("he really knows where his towel is", l.getLabel()); 664 | assertEquals(XMLSchema.STRING, l.getDatatype()); 665 | assertFalse(l.getLanguage().isPresent()); 666 | IRI thor = vf.createIRI(prefix + "thor"); 667 | 668 | // Get an actual language-tagged literal from the triple store. 669 | IRI foafName = vf.createIRI("http://xmlns.com/foaf/0.1/name"); 670 | Iterator iter = toSet(sc.getStatements(thor, foafName, null, false)).iterator(); 671 | boolean found = false; 672 | while (iter.hasNext()) { 673 | l = (Literal) iter.next().getObject(); 674 | if (l.getLanguage().get().equals("en")) { 675 | found = true; 676 | assertEquals("Thor", l.getLabel()); 677 | assertEquals(RDF.LANGSTRING, l.getDatatype()); 678 | } 679 | // if (l.getLanguage().equals("is")) { 680 | // found = true; 681 | // assertEquals("?�r", l.getLabel()); 682 | // } 683 | } 684 | assertTrue(found); 685 | 686 | // Get an actual data-typed literal from the triple-store. 687 | IRI msnChatID = vf.createIRI("http://xmlns.com/foaf/0.1/msnChatID"); 688 | l = (Literal) toSet(sc.getStatements(thor, msnChatID, null, false)).iterator().next().getObject(); 689 | assertNotNull(l); 690 | assertFalse(l.getLanguage().isPresent()); 691 | assertEquals("Thorster123", l.getLabel()); 692 | assertEquals(XMLSchema.STRING, l.getDatatype()); 693 | 694 | // Test Literal.xxxValue() methods for Literals read from the triple 695 | // store 696 | IRI valueUri, hasValueUri; 697 | hasValueUri = vf.createIRI(prefix + "hasValue"); 698 | valueUri = vf.createIRI(prefix + "stringValue"); 699 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 700 | assertNotNull(l); 701 | assertFalse(l.getLanguage().isPresent()); 702 | assertEquals("foo", l.getLabel()); 703 | assertEquals(XMLSchema.STRING, l.getDatatype()); 704 | valueUri = vf.createIRI(prefix + "byteValue"); 705 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 706 | assertNotNull(l); 707 | assertFalse(l.getLanguage().isPresent()); 708 | assertEquals("99", l.getLabel()); 709 | assertEquals(XMLSchema.BYTE, l.getDatatype()); 710 | assertEquals((byte) 'c', l.byteValue()); 711 | valueUri = vf.createIRI(prefix + "booleanValue"); 712 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 713 | assertNotNull(l); 714 | assertFalse(l.getLanguage().isPresent()); 715 | assertEquals("false", l.getLabel()); 716 | assertEquals(XMLSchema.BOOLEAN, l.getDatatype()); 717 | assertEquals(false, l.booleanValue()); 718 | valueUri = vf.createIRI(prefix + "intValue"); 719 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 720 | assertNotNull(l); 721 | assertFalse(l.getLanguage().isPresent()); 722 | assertEquals("42", l.getLabel()); 723 | assertEquals(XMLSchema.INT, l.getDatatype()); 724 | assertEquals(42, l.intValue()); 725 | valueUri = vf.createIRI(prefix + "shortValue"); 726 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 727 | assertNotNull(l); 728 | assertFalse(l.getLanguage().isPresent()); 729 | assertEquals("42", l.getLabel()); 730 | assertEquals(XMLSchema.SHORT, l.getDatatype()); 731 | assertEquals((short) 42, l.shortValue()); 732 | valueUri = vf.createIRI(prefix + "longValue"); 733 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 734 | assertNotNull(l); 735 | assertFalse(l.getLanguage().isPresent()); 736 | assertEquals("42", l.getLabel()); 737 | assertEquals(XMLSchema.LONG, l.getDatatype()); 738 | assertEquals(42L, l.longValue()); 739 | valueUri = vf.createIRI(prefix + "floatValue"); 740 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 741 | assertNotNull(l); 742 | assertFalse(l.getLanguage().isPresent()); 743 | assertEquals("3.1415926", l.getLabel()); 744 | assertEquals(XMLSchema.FLOAT, l.getDatatype()); 745 | assertEquals((float) 3.1415, l.floatValue(), 0.0001); 746 | valueUri = vf.createIRI(prefix + "doubleValue"); 747 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 748 | assertNotNull(l); 749 | assertFalse(l.getLanguage().isPresent()); 750 | assertEquals("3.1415926", l.getLabel()); 751 | assertEquals(XMLSchema.DOUBLE, l.getDatatype()); 752 | assertEquals(3.1415, l.doubleValue(), 0.0001); 753 | valueUri = vf.createIRI(prefix + "dateTimeValue"); 754 | calendar = XMLDatatypeUtil.parseCalendar("2002-10-10T12:00:00-05:00"); 755 | l = (Literal) toSet(sc.getStatements(valueUri, hasValueUri, null, false)).iterator().next().getObject(); 756 | assertNotNull(l); 757 | assertFalse(l.getLanguage().isPresent()); 758 | assertEquals("2002-10-10T12:00:00-05:00", l.getLabel()); 759 | assertEquals(XMLSchema.DATETIME, l.getDatatype()); 760 | assertEquals(calendar, l.calendarValue()); 761 | sc.close(); 762 | } 763 | 764 | // blank nodes ///////////////////////////////////////////////////////////// 765 | 766 | @Test 767 | public void testBlankNodes() throws Throwable { 768 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/S_POG#a"); 769 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/S_POG#b"); 770 | try (SailConnection sc = getConnection()) { 771 | ValueFactory factory = sail.getValueFactory(); 772 | BNode bNode = factory.createBNode(); 773 | try { 774 | sc.addStatement(uriA, uriA, bNode); 775 | } catch (SailException se) { 776 | // FIXME: not supporting blank nodes ATM 777 | assertTrue(se.getCause() instanceof UnsupportedOperationException); 778 | } 779 | commit(sc); 780 | } 781 | } 782 | 783 | // tuple queries /////////////////////////////////////////////////////////// 784 | 785 | @Test 786 | public void testEvaluate() throws Exception { 787 | addTestFile(); 788 | 789 | Set languages; 790 | String prefix = "urn:com.tinkerpop.blueprints.pgm.oupls.sail.test/"; 791 | IRI thorUri = sail.getValueFactory().createIRI(prefix + "thor"); 792 | 793 | try (SailConnection sc = getConnection()) { 794 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 795 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 796 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/uriC"); 797 | sc.addStatement(uriA, uriB, uriC); 798 | commit(sc); 799 | 800 | SPARQLParser parser = new SPARQLParser(); 801 | BindingSet bindings = new EmptyBindingSet(); 802 | String baseIRI = "http://example.org/bogus/"; 803 | String queryStr; 804 | ParsedQuery query; 805 | CloseableIteration results; 806 | int count; 807 | // s ?p ?o SELECT 808 | queryStr = "SELECT ?y ?z WHERE { ?y ?z }"; 809 | query = parser.parseQuery(queryStr, baseIRI); 810 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 811 | count = 0; 812 | while (results.hasNext()) { 813 | count++; 814 | BindingSet set = results.next(); 815 | IRI y = (IRI) set.getValue("y"); 816 | Value z = set.getValue("z"); 817 | assertNotNull(y); 818 | assertNotNull(z); 819 | // System.out.println("y = " + y + ", z = " + z); 820 | } 821 | results.close(); 822 | assertTrue(count > 0); 823 | 824 | // s p ?o SELECT using a namespace prefix 825 | queryStr = "PREFIX foaf: \n" + "SELECT ?z WHERE { <" + prefix + "thor> foaf:name ?z }"; 826 | query = parser.parseQuery(queryStr, baseIRI); 827 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 828 | count = 0; 829 | languages = new HashSet<>(); 830 | while (results.hasNext()) { 831 | count++; 832 | BindingSet set = results.next(); 833 | Literal z = (Literal) set.getValue("z"); 834 | assertNotNull(z); 835 | languages.add(z.getLanguage().get()); 836 | } 837 | results.close(); 838 | assertTrue(count > 0); 839 | assertEquals(2, languages.size()); 840 | assertTrue(languages.contains("en")); 841 | assertTrue(languages.contains("is")); 842 | 843 | // ?s p o SELECT using a plain literal value with no language tag 844 | queryStr = "PREFIX rdfs: \n" 845 | + "SELECT ?s WHERE { ?s rdfs:comment \"he really knows where his towel is\" }"; 846 | IRI fordUri = sail.getValueFactory().createIRI(prefix + "ford"); 847 | query = parser.parseQuery(queryStr, baseIRI); 848 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 849 | count = 0; 850 | while (results.hasNext()) { 851 | count++; 852 | BindingSet set = results.next(); 853 | IRI s = (IRI) set.getValue("s"); 854 | assertNotNull(s); 855 | assertEquals(s, fordUri); 856 | } 857 | results.close(); 858 | assertTrue(count > 0); 859 | 860 | // ?s p o SELECT using a language-specific literal value 861 | queryStr = "PREFIX foaf: \n" + "SELECT ?s WHERE { ?s foaf:name \"Thor\"@en }"; 862 | query = parser.parseQuery(queryStr, baseIRI); 863 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 864 | count = 0; 865 | while (results.hasNext()) { 866 | count++; 867 | BindingSet set = results.next(); 868 | IRI s = (IRI) set.getValue("s"); 869 | assertNotNull(s); 870 | assertEquals(s, thorUri); 871 | } 872 | results.close(); 873 | assertTrue(count > 0); 874 | 875 | // The language tag is necessary 876 | queryStr = "PREFIX foaf: \n" + "SELECT ?s WHERE { ?s foaf:name \"Thor\" }"; 877 | query = parser.parseQuery(queryStr, baseIRI); 878 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 879 | count = 0; 880 | while (results.hasNext()) { 881 | count++; 882 | results.next(); 883 | } 884 | results.close(); 885 | assertEquals(0, count); 886 | 887 | // ?s p o SELECT using a typed literal value 888 | queryStr = "PREFIX foaf: \n" + "PREFIX xsd: \n" + "SELECT ?s WHERE { ?s foaf:msnChatID \"Thorster123\"^^xsd:string }"; 889 | query = parser.parseQuery(queryStr, baseIRI); 890 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 891 | count = 0; 892 | while (results.hasNext()) { 893 | count++; 894 | BindingSet set = results.next(); 895 | IRI s = (IRI) set.getValue("s"); 896 | assertNotNull(s); 897 | assertEquals(s, thorUri); 898 | } 899 | results.close(); 900 | assertTrue(count > 0); 901 | 902 | // In RDF 1.1, the xsd:string datatype is the default, and does not need to be given in the query 903 | queryStr = "PREFIX foaf: \n" 904 | + "PREFIX xsd: \n" 905 | + "SELECT ?s WHERE { ?s foaf:msnChatID \"Thorster123\" }"; 906 | query = parser.parseQuery(queryStr, baseIRI); 907 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 908 | assertEquals(1, IterUtils.count(results)); 909 | 910 | // s ?p o SELECT 911 | // TODO: commented out languages for now 912 | queryStr = "PREFIX foaf: \n" + "PREFIX xsd: \n" + "SELECT ?p WHERE { <" + prefix + "thor> ?p \"Thor\"@en }"; 913 | query = parser.parseQuery(queryStr, baseIRI); 914 | IRI foafNameUri = sail.getValueFactory().createIRI("http://xmlns.com/foaf/0.1/name"); 915 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 916 | count = 0; 917 | while (results.hasNext()) { 918 | count++; 919 | BindingSet set = results.next(); 920 | IRI p = (IRI) set.getValue("p"); 921 | assertNotNull(p); 922 | assertEquals(p, foafNameUri); 923 | } 924 | results.close(); 925 | assertTrue(count > 0); 926 | 927 | // context-specific SELECT 928 | queryStr = "PREFIX foaf: \n" + "SELECT ?z\n" + "FROM <" + prefix + "ctx1>\n" + "WHERE { <" + prefix + "thor> foaf:name ?z }"; 929 | query = parser.parseQuery(queryStr, baseIRI); 930 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 931 | count = 0; 932 | languages = new HashSet<>(); 933 | while (results.hasNext()) { 934 | count++; 935 | BindingSet set = results.next(); 936 | Literal z = (Literal) set.getValue("z"); 937 | assertNotNull(z); 938 | languages.add(z.getLanguage().get()); 939 | } 940 | results.close(); 941 | assertTrue(count > 0); 942 | assertEquals(2, languages.size()); 943 | assertTrue(languages.contains("en")); 944 | assertTrue(languages.contains("is")); 945 | queryStr = "PREFIX foaf: \n" + "SELECT ?z\n" + "FROM \n" + "WHERE { <" + prefix + "thor> foaf:name ?z }"; 946 | query = parser.parseQuery(queryStr, baseIRI); 947 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 948 | count = 0; 949 | while (results.hasNext()) { 950 | count++; 951 | results.next(); 952 | } 953 | results.close(); 954 | assertEquals(0, count); 955 | 956 | // s p o? select without and with inferencing 957 | // TODO commented out waiting for inferencing 958 | // queryStr = 959 | // "PREFIX rdf: \n" 960 | // + "SELECT ?o\n" 961 | // + "WHERE { <" + prefix + "instance1> rdf:type ?o }"; 962 | // query = parser.parseQuery(queryStr, baseIRI); 963 | // results = sc.evaluate(query.getTupleExpr(), query.getDataset(), 964 | // bindings, false); 965 | // count = 0; 966 | // while (results.hasNext()) { 967 | // count++; 968 | // BindingSet set = results.next(); 969 | // IRI o = (IRI) set.getValue("o"); 970 | // assertEquals(prefix + "classB", o.toString()); 971 | // } 972 | // results.close(); 973 | // assertEquals(1, count); 974 | // results = sc.evaluate(query.getTupleExpr(), query.getDataset(), 975 | // bindings, true); 976 | // count = 0; 977 | // boolean foundA = false, foundB = false; 978 | // while (results.hasNext()) { 979 | // count++; 980 | // BindingSet set = results.next(); 981 | // IRI o = (IRI) set.getValue("o"); 982 | // String s = o.toString(); 983 | // if (s.equals(prefix + "classA")) { 984 | // foundA = true; 985 | // } else if (s.equals(prefix + "classB")) { 986 | // foundB = true; 987 | // } 988 | // } 989 | // results.close(); 990 | // assertEquals(2, count); 991 | // assertTrue(foundA); 992 | // assertTrue(foundB); 993 | 994 | } 995 | } 996 | 997 | @Test 998 | public void testJoins() throws Exception { 999 | addTestFile(); 1000 | 1001 | SPARQLParser parser = new SPARQLParser(); 1002 | BindingSet bindings = new EmptyBindingSet(); 1003 | String baseIRI = "http://example.org/bogus/"; 1004 | 1005 | try (SailConnection sc = getConnection()) { 1006 | CloseableIteration results; 1007 | int count; 1008 | String queryStr = "PREFIX : \n" + 1009 | "PREFIX foaf: \n" + 1010 | "SELECT ?foaf WHERE {\n" + 1011 | " :ford foaf:knows ?friend .\n" + 1012 | " ?friend foaf:knows ?foaf .\n" + 1013 | "}"; 1014 | ParsedQuery query = parser.parseQuery(queryStr, baseIRI); 1015 | results = sc.evaluate(query.getTupleExpr(), query.getDataset(), bindings, false); 1016 | count = 0; 1017 | while (results.hasNext()) { 1018 | count++; 1019 | BindingSet set = results.next(); 1020 | IRI foaf = (IRI) set.getValue("foaf"); 1021 | assertTrue(foaf.stringValue().startsWith("urn:com.tinkerpop.blueprints.pgm.oupls.sail.test/")); 1022 | } 1023 | results.close(); 1024 | assertEquals(4, count); 1025 | } 1026 | } 1027 | 1028 | // listeners /////////////////////////////////////////////////////////////// 1029 | // (disabled for Sails which do not implement NotifyingSail) 1030 | 1031 | @Test 1032 | public void testSailConnectionListeners() throws Exception { 1033 | if (sail instanceof NotifyingSail) { 1034 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 1035 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 1036 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/uriC"); 1037 | 1038 | TestListener listener1 = new TestListener(), listener2 = new TestListener(); 1039 | try (NotifyingSailConnection sc = (NotifyingSailConnection) getConnection()) { 1040 | sc.clear(); 1041 | commit(sc); 1042 | 1043 | // Add a listener and add statements 1044 | sc.addConnectionListener(listener1); 1045 | sc.addStatement(uriA, uriB, uriC, uriA); 1046 | sc.addStatement(uriB, uriC, uriA, uriA); 1047 | commit(sc); 1048 | 1049 | // Add another listener and remove a statement 1050 | sc.addConnectionListener(listener2); 1051 | sc.removeStatements(uriA, null, null); 1052 | commit(sc); 1053 | 1054 | assertEquals(2, listener1.getAdded()); 1055 | assertEquals(0, listener2.getAdded()); 1056 | assertEquals(1, listener1.getRemoved()); 1057 | assertEquals(1, listener2.getRemoved()); 1058 | 1059 | // Remove a listener and clear 1060 | sc.removeConnectionListener(listener1); 1061 | sc.clear(); 1062 | commit(sc); 1063 | 1064 | assertEquals(1, listener1.getRemoved()); 1065 | assertEquals(2, listener2.getRemoved()); 1066 | } 1067 | } 1068 | } 1069 | 1070 | @Test 1071 | public void testSailChangedListeners() throws Exception { 1072 | if (sail instanceof NotifyingSail) { 1073 | final Collection events = new LinkedList<>(); 1074 | SailChangedListener listener = events::add; 1075 | ((NotifyingSail) sail).addSailChangedListener(listener); 1076 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 1077 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 1078 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/uriC"); 1079 | try (SailConnection sc = getConnection()) { 1080 | sc.clear(); 1081 | commit(sc); 1082 | events.clear(); 1083 | assertEquals(0, events.size()); 1084 | sc.addStatement(uriA, uriB, uriC, uriA); 1085 | sc.addStatement(uriB, uriC, uriA, uriA); 1086 | // Events are buffered until the commit 1087 | assertEquals(0, events.size()); 1088 | commit(sc); 1089 | // Only one SailChangedEvent per commit 1090 | assertEquals(1, events.size()); 1091 | SailChangedEvent event = events.iterator().next(); 1092 | assertTrue(event.statementsAdded()); 1093 | assertFalse(event.statementsRemoved()); 1094 | events.clear(); 1095 | assertEquals(0, events.size()); 1096 | sc.removeStatements(uriA, uriB, uriC, uriA); 1097 | commit(sc); 1098 | assertEquals(1, events.size()); 1099 | event = events.iterator().next(); 1100 | assertFalse(event.statementsAdded()); 1101 | assertTrue(event.statementsRemoved()); 1102 | events.clear(); 1103 | assertEquals(0, events.size()); 1104 | sc.clear(); 1105 | commit(sc); 1106 | assertEquals(1, events.size()); 1107 | event = events.iterator().next(); 1108 | assertFalse(event.statementsAdded()); 1109 | assertTrue(event.statementsRemoved()); 1110 | } 1111 | } 1112 | } 1113 | 1114 | // namespaces ////////////////////////////////////////////////////////////// 1115 | 1116 | @Test 1117 | public void testClearNamespaces() throws Exception { 1118 | addTestFile(); 1119 | 1120 | try (SailConnection sc = getConnection()) { 1121 | CloseableIteration namespaces; 1122 | int count; 1123 | count = 0; 1124 | namespaces = sc.getNamespaces(); 1125 | while (namespaces.hasNext()) { 1126 | namespaces.next(); 1127 | count++; 1128 | } 1129 | namespaces.close(); 1130 | assertTrue(count > 0); 1131 | // TODO: actually clear namespaces (but this wipes them out for 1132 | // subsequent tests) 1133 | } 1134 | } 1135 | 1136 | @Test 1137 | public void testGetNamespace() throws Exception { 1138 | addTestFile(); 1139 | 1140 | try (SailConnection sc = getConnection()) { 1141 | String name; 1142 | name = sc.getNamespace("bogus"); 1143 | assertNull(name); 1144 | name = sc.getNamespace("rdfs"); 1145 | assertEquals(name, "http://www.w3.org/2000/01/rdf-schema#"); 1146 | } 1147 | } 1148 | 1149 | private void showNamespaces(final SailConnection c) throws SailException { 1150 | System.out.println("namespaces:"); 1151 | try (CloseableIteration iter = c.getNamespaces()) { 1152 | while (iter.hasNext()) { 1153 | Namespace n = iter.next(); 1154 | System.out.println("\t" + n.getPrefix() + ":\t" + n.getName()); 1155 | } 1156 | } 1157 | } 1158 | 1159 | @Test 1160 | public void testGetNamespaces() throws Exception { 1161 | try (SailConnection sc = getConnection()) { 1162 | CloseableIteration namespaces; 1163 | int before = 0, during = 0, after = 0; 1164 | // just iterate through all namespaces 1165 | namespaces = sc.getNamespaces(); 1166 | while (namespaces.hasNext()) { 1167 | Namespace ns = namespaces.next(); 1168 | before++; 1169 | // System.out.println("namespace: " + ns); 1170 | } 1171 | namespaces.close(); 1172 | // Note: assumes that these namespace prefixes are unused. 1173 | int nTests = 10; 1174 | String prefixPrefix = "testns"; 1175 | String namePrefix = "http://example.org/test"; 1176 | for (int i = 0; i < nTests; i++) { 1177 | sc.setNamespace(prefixPrefix + i, namePrefix + i); 1178 | } 1179 | commit(sc); 1180 | namespaces = sc.getNamespaces(); 1181 | while (namespaces.hasNext()) { 1182 | Namespace ns = namespaces.next(); 1183 | during++; 1184 | String prefix = ns.getPrefix(); 1185 | String name = ns.getName(); 1186 | if (prefix.startsWith(prefixPrefix)) { 1187 | assertEquals(name, namePrefix + prefix.substring(prefixPrefix.length())); 1188 | } 1189 | } 1190 | namespaces.close(); 1191 | for (int i = 0; i < nTests; i++) { 1192 | sc.removeNamespace(prefixPrefix + i); 1193 | } 1194 | commit(sc); 1195 | namespaces = sc.getNamespaces(); 1196 | while (namespaces.hasNext()) { 1197 | namespaces.next(); 1198 | after++; 1199 | } 1200 | namespaces.close(); 1201 | assertEquals(during, before + nTests); 1202 | assertEquals(after, before); 1203 | } 1204 | } 1205 | 1206 | @Test 1207 | public void testSetNamespace() throws Exception { 1208 | try (SailConnection sc = getConnection()) { 1209 | String prefix = "foo"; 1210 | String emptyPrefix = ""; 1211 | String name = "http://example.org/foo"; 1212 | String otherName = "http://example.org/bar"; 1213 | 1214 | sc.removeNamespace(prefix); 1215 | sc.removeNamespace(emptyPrefix); 1216 | commit(sc); 1217 | 1218 | // Namespace initially absent? 1219 | assertNull(sc.getNamespace(prefix)); 1220 | assertNull(sc.getNamespace(emptyPrefix)); 1221 | 1222 | // Can we set the namespace? 1223 | sc.setNamespace(prefix, name); 1224 | commit(sc); 1225 | assertEquals(sc.getNamespace(prefix), name); 1226 | 1227 | // Can we reset the namespace? 1228 | sc.setNamespace(prefix, otherName); 1229 | commit(sc); 1230 | assertEquals(sc.getNamespace(prefix), otherName); 1231 | 1232 | // Can we use an empty namespace prefix? 1233 | sc.setNamespace(emptyPrefix, name); 1234 | commit(sc); 1235 | assertEquals(sc.getNamespace(emptyPrefix), name); 1236 | } 1237 | } 1238 | 1239 | @Test 1240 | public void testRemoveNamespace() throws Exception { 1241 | try (SailConnection sc = getConnection()) { 1242 | String prefix = "foo"; 1243 | String emptyPrefix = ""; 1244 | String name = "http://example.org/foo"; 1245 | 1246 | // Set namespace initially. 1247 | sc.setNamespace(prefix, name); 1248 | commit(sc); 1249 | assertEquals(sc.getNamespace(prefix), name); 1250 | 1251 | // Remove the namespace and make sure it's gone. 1252 | sc.removeNamespace(prefix); 1253 | commit(sc); 1254 | assertNull(sc.getNamespace(prefix)); 1255 | 1256 | // Same thing for the default namespace. 1257 | sc.setNamespace(emptyPrefix, name); 1258 | commit(sc); 1259 | assertEquals(sc.getNamespace(emptyPrefix), name); 1260 | sc.removeNamespace(emptyPrefix); 1261 | commit(sc); 1262 | assertNull(sc.getNamespace(emptyPrefix)); 1263 | } 1264 | } 1265 | 1266 | // connections and transactions //////////////////////////////////////////// 1267 | 1268 | @Test 1269 | public void testPersistentCommits() throws Exception { 1270 | SailConnection sc; 1271 | long count; 1272 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/persistentCommits#a"); 1273 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/persistentCommits#b"); 1274 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/test/persistentCommits#c"); 1275 | sc = getConnection(); 1276 | try { 1277 | count = countStatements(sc, uriA, null, null, false); 1278 | assertEquals(0, count); 1279 | sc.close(); 1280 | 1281 | sc = getConnection(); 1282 | sc.addStatement(uriA, uriB, uriC); 1283 | count = countStatements(sc, uriA, null, null, false); 1284 | assertNotEquals(0, count); 1285 | commit(sc); 1286 | sc.close(); 1287 | 1288 | sc = getConnection(); 1289 | count = countStatements(sc, uriA, null, null, false); 1290 | assertNotEquals(0, count); 1291 | sc.close(); 1292 | 1293 | sc = getConnection(); 1294 | sc.removeStatements(uriA, null, null); 1295 | count = countStatements(sc, uriA, null, null, false); 1296 | assertEquals(0, count); 1297 | commit(sc); 1298 | sc.close(); 1299 | 1300 | sc = getConnection(); 1301 | count = countStatements(sc, uriA, null, null, false); 1302 | assertEquals(0, count); 1303 | } finally { 1304 | sc.close(); 1305 | } 1306 | } 1307 | 1308 | @Test 1309 | public void testVisibilityOfChanges() throws Exception { 1310 | SailConnection sc1, sc2; 1311 | long count; 1312 | IRI uriA = sail.getValueFactory().createIRI( 1313 | "http://example.org/test/visibilityOfChanges#a"); 1314 | sc1 = getConnection(); 1315 | sc2 = getConnection(); 1316 | try { 1317 | sc1.clear(); 1318 | sc2.clear(); 1319 | 1320 | // Statement doesn't exist for either connection. 1321 | count = countStatements(sc1, uriA, null, null); 1322 | assertEquals(0, count); 1323 | count = countStatements(sc2, uriA, null, null); 1324 | assertEquals(0, count); 1325 | // First connection adds a statement. It is visible to the first 1326 | // connection, but not to the second. 1327 | sc1.addStatement(uriA, uriA, uriA); 1328 | count = countStatements(sc1, null, null, null); 1329 | assertEquals(1, count); 1330 | count = countStatements(sc2, uriA, null, null); 1331 | assertEquals(0, count); 1332 | } 1333 | finally { 1334 | sc2.close(); 1335 | sc1.close(); 1336 | } 1337 | } 1338 | 1339 | @Test 1340 | public void testNullContext() throws Exception { 1341 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/test/nullContext#a"); 1342 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/test/nullContext#b"); 1343 | IRI uriC = sail.getValueFactory().createIRI("http://example.org/test/nullContext#c"); 1344 | 1345 | try (SailConnection sc = getConnection()) { 1346 | long count = countStatements(sc, uriA, null, null, false); 1347 | assertEquals(0, count); 1348 | sc.addStatement(uriA, uriB, uriC); 1349 | Statement statement = sc.getStatements(uriA, uriB, uriC, false, new Resource[]{null}).next(); 1350 | Resource context = statement.getContext(); 1351 | assertNull(context); 1352 | sc.removeStatements(uriA, null, null); 1353 | assertFalse(sc.getStatements(uriA, uriB, uriC, false, new Resource[]{null}).hasNext()); 1354 | } 1355 | } 1356 | 1357 | // inference //////////////////////////////////////////////////////////////// 1358 | 1359 | @Ignore 1360 | @Test 1361 | public void testInference() throws Exception { 1362 | if (null != inferencer) { 1363 | IRI uriA = sail.getValueFactory().createIRI("http://example.org/uriA"); 1364 | IRI uriB = sail.getValueFactory().createIRI("http://example.org/uriB"); 1365 | IRI classX = sail.getValueFactory().createIRI("http://example.org/classX"); 1366 | IRI classY = sail.getValueFactory().createIRI("http://example.org/classY"); 1367 | 1368 | try (SailConnection sc = inferencer.getConnection()) { 1369 | sc.begin(); 1370 | sc.clear(); 1371 | 1372 | sc.addStatement(classX, RDFS.SUBCLASSOF, classY); 1373 | sc.addStatement(uriA, RDF.TYPE, classX); 1374 | sc.addStatement(uriB, RDF.TYPE, classY); 1375 | commit(sc); 1376 | 1377 | //showStatements(sc, uriA, RDF.TYPE, null); 1378 | 1379 | assertEquals(3, countStatements(sc, uriA, RDF.TYPE, null, true)); 1380 | assertEquals(1, countStatements(sc, uriA, RDF.TYPE, null, false)); 1381 | assertEquals(2, countStatements(sc, uriB, RDF.TYPE, null, true)); 1382 | assertEquals(1, countStatements(sc, uriB, RDF.TYPE, null, false)); 1383 | 1384 | if (uniqueStatements) { 1385 | sc.addStatement(uriA, RDF.TYPE, classY); 1386 | commit(sc); 1387 | 1388 | //showStatements(sc, uriA, RDF.TYPE, null); 1389 | assertEquals(3, countStatements(sc, uriA, RDF.TYPE, null, true)); 1390 | assertEquals(2, countStatements(sc, uriA, RDF.TYPE, null, false)); 1391 | 1392 | sc.removeStatements(uriA, RDF.TYPE, classY); 1393 | commit(sc); 1394 | 1395 | assertEquals(3, countStatements(sc, uriA, RDF.TYPE, null, true)); 1396 | assertEquals(2, countStatements(sc, uriA, RDF.TYPE, null, false)); 1397 | 1398 | //sc.removeStatements(uriA, RDF.TYPE, classX); 1399 | //commit(sc); 1400 | //assertEquals(1, countStatements(sc, uriA, RDF.TYPE, null)); 1401 | } 1402 | } 1403 | } 1404 | } 1405 | 1406 | // TODO: concurrency testing /////////////////////////////////////////////// 1407 | // ////////////////////////////////////////////////////////////////////////// 1408 | 1409 | private class TestListener implements SailConnectionListener { 1410 | private int added = 0, removed = 0; 1411 | 1412 | public void statementAdded(final Statement statement) { 1413 | added++; 1414 | } 1415 | 1416 | public void statementRemoved(final Statement statement) { 1417 | removed++; 1418 | } 1419 | 1420 | public int getAdded() { 1421 | return added; 1422 | } 1423 | 1424 | public int getRemoved() { 1425 | return removed; 1426 | } 1427 | } 1428 | 1429 | protected void showStatements(final SailConnection sc, 1430 | final Resource subject, 1431 | final IRI predicate, 1432 | final Value object, 1433 | final Resource... contexts) throws SailException { 1434 | int count = 0; 1435 | try (CloseableIteration statements 1436 | = sc.getStatements(subject, predicate, object, true, contexts)) { 1437 | while (statements.hasNext()) { 1438 | System.out.println("" + count + ") " + statements.next()); 1439 | count++; 1440 | } 1441 | } 1442 | } 1443 | 1444 | protected long countStatements(final SailConnection sc, 1445 | final Resource subject, 1446 | final IRI predicate, 1447 | final Value object, 1448 | final Resource... contexts) throws SailException { 1449 | return countStatements(sc, subject, predicate, object, false, contexts); 1450 | } 1451 | 1452 | protected long countStatements(final SailConnection sc, 1453 | final Resource subject, 1454 | final IRI predicate, 1455 | final Value object, 1456 | final boolean includeInferred, 1457 | final Resource... contexts) throws SailException { 1458 | return IterUtils.count(sc.getStatements(subject, predicate, object, includeInferred, contexts)); 1459 | } 1460 | 1461 | private Set toSet(final CloseableIteration i) throws SailException { 1462 | try { 1463 | Set set = new HashSet<>(); 1464 | while (i.hasNext()) { 1465 | set.add(i.next()); 1466 | } 1467 | return set; 1468 | } finally { 1469 | i.close(); 1470 | } 1471 | } 1472 | 1473 | private void addFile(final InputStream in, 1474 | final RDFFormat format) throws Exception { 1475 | Preconditions.checkNotNull(in); 1476 | 1477 | try { 1478 | try (SailConnection sc = getConnection()) { 1479 | RDFHandler h = new SailAdder(sc); 1480 | RDFParser p = Rio.createParser(format); 1481 | p.setRDFHandler(h); 1482 | p.parse(in, "http://example.org/bogusBaseIRI/"); 1483 | commit(sc); 1484 | } 1485 | } finally { 1486 | in.close(); 1487 | } 1488 | } 1489 | 1490 | private SailConnection getConnection() { 1491 | SailConnection sc = sail.getConnection(); 1492 | sc.begin(); 1493 | return sc; 1494 | } 1495 | 1496 | private void commit(final SailConnection conn) { 1497 | conn.commit(); 1498 | conn.begin(); 1499 | } 1500 | 1501 | protected void addTestFile() throws Exception { 1502 | addFile(SailTest.class.getResourceAsStream("graph-example-sail-test.trig"), RDFFormat.TRIG); 1503 | } 1504 | } 1505 | -------------------------------------------------------------------------------- /src/test/resources/net/fortytwo/tpop/sail/graph-example-sail-test.trig: -------------------------------------------------------------------------------- 1 | @prefix foaf: . 2 | @prefix rdfs: . 3 | @prefix xsd: . 4 | @prefix : . 5 | 6 | { 7 | :arthur a foaf:Person; 8 | foaf:knows :ford, :zaphod, :thor ; 9 | rdfs:comment "he's a jerk" . 10 | } 11 | 12 | :ctx1 { 13 | :ford a foaf:Person; 14 | foaf:knows :arthur, :zaphod; 15 | rdfs:comment "he really knows where his towel is". 16 | 17 | :zaphod a foaf:Person; 18 | foaf:knows :ford; 19 | rdfs:comment "so cool you could keep a side of meat in him for a month". 20 | 21 | :thor a foaf:God; 22 | foaf:name "Thor"@en; 23 | foaf:name "\u00DE\u00F3r"@is; 24 | foaf:msnChatID "Thorster123"^^xsd:string. 25 | 26 | :stringValue :hasValue "foo"^^xsd:string. 27 | :byteValue :hasValue "99"^^xsd:byte. 28 | :booleanValue :hasValue "false"^^xsd:boolean. 29 | :intValue :hasValue "42"^^xsd:int. 30 | :shortValue :hasValue "42"^^xsd:short. 31 | :longValue :hasValue "42"^^xsd:long. 32 | :floatValue :hasValue "3.1415926"^^xsd:float. 33 | :doubleValue :hasValue "3.1415926"^^xsd:double. 34 | :dateTimeValue :hasValue "2002-10-10T12:00:00-05:00"^^xsd:dateTime. 35 | 36 | :classA a rdfs:Class. 37 | 38 | :classB a rdfs:Class; 39 | rdfs:subClassOf :classA. 40 | 41 | :instance1 a :classB. 42 | } 43 | --------------------------------------------------------------------------------