├── .gitignore ├── LICENSE ├── README.md ├── assets ├── bookauthors.dot ├── bookauthors.png ├── counselor.dot ├── counselor.png ├── multigraph.dot ├── multigraph.png ├── query-example01.png ├── query-example02.dot ├── query-example02.png ├── query-example03.dot └── query-example03.png ├── src ├── core.org ├── csv.org ├── entities.org ├── index.org ├── inference.org ├── libraryofbabel.org ├── query.org ├── setup.org ├── utils.org └── vocabs.org ├── tangle.sh └── test ├── core.org ├── entities.org └── query.org /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | babel 13 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # thi.ng/trio 2 | 3 | ![Example graph](assets/multigraph.png) 4 | 5 | Simple, extensible and datatype agnostic 6 | [triplestore](http://en.wikipedia.org/wiki/Triplestore) API and 7 | powerful [SPARQL](http://en.wikipedia.org/wiki/SPARQL)-inspired query 8 | engine for Clojure/ClojureScript (without any RDF specifics, but more 9 | flexibility). 10 | 11 | ## Description & usage 12 | 13 | - [Project overview & dependencies](src/index.org) 14 | - [Query engine & examples](src/query.org) 15 | - [Triplestore API & implementations](src/core.org) 16 | 17 | ## Leiningen coordinates 18 | 19 | ```clj 20 | [thi.ng/trio "0.1.0"] 21 | ``` 22 | 23 | ## License 24 | 25 | Copyright © 2012 - 2015 Karsten Schmidt 26 | 27 | Distributed under the [Apache Software License 2.0](http://www.apache.org/licenses/LICENSE-2.0). 28 | -------------------------------------------------------------------------------- /assets/bookauthors.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | node[color="black",style="filled",fontname="Inconsolata",fontcolor="white"]; 3 | edge[fontname="Inconsolata",fontsize="9"]; 4 | "b3" -> "5" [label="price"]; 5 | "b4" -> "20" [label="price"]; 6 | "b2" -> "20" [label="price"]; 7 | "b1" -> "10" [label="price"]; 8 | "a3" -> "b4" [label="author"]; 9 | "a2" -> "b1" [label="author"]; 10 | "a2" -> "b3" [label="author"]; 11 | "a1" -> "b2" [label="author"]; 12 | "a1" -> "b1" [label="author"]; 13 | } -------------------------------------------------------------------------------- /assets/bookauthors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/trio/6716d8fd239ffbaf569755d47a035318d40e00e6/assets/bookauthors.png -------------------------------------------------------------------------------- /assets/counselor.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | node[color="black",style="filled",fontname="Inconsolata",fontcolor="white"]; 3 | edge[fontname="Inconsolata",fontsize="9"]; 4 | 5 | reiner -> malkina [label="loves"]; 6 | laura -> counselor [label="loves"]; 7 | counselor -> laura [label="loves"]; 8 | westray -> reiner [label="friend-of"]; 9 | westray -> wireman2 [label="killed-by"]; 10 | counselor -> reiner [label="friend-of"]; 11 | reiner -> cartel [label="killed-by"]; 12 | wireman -> malkina [label="works-for"]; 13 | wireman2 -> malkina [label="works-for"]; 14 | green_hornet -> wireman [label="killed-by"]; 15 | green_hornet -> jefe [label="works-for"]; 16 | jefe -> cartel [label="works-for"]; 17 | laura -> cartel [label="killed-by"]; 18 | wireman -> cartel [label="killed-by"]; 19 | } -------------------------------------------------------------------------------- /assets/counselor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/trio/6716d8fd239ffbaf569755d47a035318d40e00e6/assets/counselor.png -------------------------------------------------------------------------------- /assets/multigraph.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | size="6,8" 3 | ratio=fill; 4 | rankdir=LR; 5 | ranksep=0.1; 6 | node[color="black",style="filled",fontname="Inconsolata",fontcolor="white",fontsize=11]; 7 | edge[fontname="Inconsolata",fontsize="9"]; 8 | "nyc" -> "8400000.0" [label="population"]; 9 | "nyc" -> "city" [label="type"]; 10 | "nyc" -> "usa" [label="located-in"]; 11 | "sao-paulo" -> "2.03E7" [label="population"]; 12 | "sao-paulo" -> "brazil" [label="located-in"]; 13 | "sao-paulo" -> "city" [label="type"]; 14 | "uk" -> "country" [label="type"]; 15 | "usa" -> "country" [label="type"]; 16 | "brazil" -> "country" [label="type"]; 17 | "london" -> "9800000.0" [label="population"]; 18 | "london" -> "city" [label="type"]; 19 | "london" -> "uk" [label="located-in"]; 20 | "manchester" -> "2550000.0" [label="population"]; 21 | "manchester" -> "city" [label="type"]; 22 | "manchester" -> "uk" [label="located-in"]; 23 | "cambridge" -> "124000.0" [label="population"]; 24 | "cambridge" -> "city" [label="type"]; 25 | "cambridge" -> "uk" [label="located-in"]; 26 | 27 | edge[fontname="Inconsolata",fontsize="9",color=red,fontcolor=red]; 28 | node[color=red,style="filled",fontname="Inconsolata",fontcolor=white,fontsize=11]; 29 | 30 | "CAM" -> "cambridge" [label="name"]; 31 | "CAM" -> "city" [label="type"]; 32 | "LDN" -> "city" [label="type"]; 33 | "LDN" -> "london" [label="name"]; 34 | "MAN" -> "city" [label="type"]; 35 | "MAN" -> "manchester" [label="name"]; 36 | "abbey" -> "CAM" [label="part-of"]; 37 | "abbey" -> "ward" [label="type"]; 38 | "ardwick" -> "MAN" [label="part-of"]; 39 | "ardwick" -> "ward" [label="type"]; 40 | "bradford" -> "MAN" [label="part-of"]; 41 | "bradford" -> "ward" [label="type"]; 42 | "brixton" -> "LDN" [label="part-of"]; 43 | "brixton" -> "borough" [label="type"]; 44 | "camden" -> "LDN" [label="part-of"]; 45 | "camden" -> "borough" [label="type"]; 46 | "castle" -> "CAM" [label="part-of"]; 47 | "castle" -> "ward" [label="type"]; 48 | "hulme" -> "MAN" [label="part-of"]; 49 | "hulme" -> "ward" [label="type"]; 50 | "market" -> "CAM" [label="part-of"]; 51 | "market" -> "ward" [label="type"]; 52 | "westminster" -> "LDN" [label="part-of"]; 53 | "westminster" -> "borough" [label="type"]; 54 | } -------------------------------------------------------------------------------- /assets/multigraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/trio/6716d8fd239ffbaf569755d47a035318d40e00e6/assets/multigraph.png -------------------------------------------------------------------------------- /assets/query-example01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/trio/6716d8fd239ffbaf569755d47a035318d40e00e6/assets/query-example01.png -------------------------------------------------------------------------------- /assets/query-example02.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | ranksep=0.25; 3 | node[color="black",style="filled",fontname="Inconsolata",fontcolor="white",fontsize=11]; 4 | edge[fontname="Inconsolata",fontsize="9",align=center]; 5 | "alice" -> "34" [label="age"]; 6 | "alice" -> "bob" [label="friend"]; 7 | "bob" -> "bobby" [label="nick"]; 8 | "bob" -> "carl" [label="friend"]; 9 | "bob" -> "emily" [label="father"]; 10 | "carl" -> "42" [label="age"]; 11 | "carl" -> "alice" [label="spouse"]; 12 | "carl" -> "donald" [label="father"]; 13 | "emily" -> "23" [label="age"]; 14 | "emily" -> "em" [label="nick"]; 15 | "freya" -> "bob" [label="spouse"]; 16 | "freya" -> "emily" [label="mother"]; 17 | "donald" -> "emily" [label="friend"]; 18 | 19 | edge[fontname="Inconsolata",fontsize="9",color=red,fontcolor=red]; 20 | "alice" -> "donald" [label="parent"]; 21 | "bob" -> "emily" [label="parent"]; 22 | "carl" -> "donald" [label="parent"]; 23 | "freya" -> "emily" [label="parent"]; 24 | "bob" -> "freya" [label="spouse"]; 25 | "alice" -> "carl" [label="spouse"]; 26 | "alice" -> "donald" [label="mother"]; 27 | "donald" -> "alice" [label="child-of"]; 28 | "donald" -> "carl" [label="child-of"]; 29 | "emily" -> "bob" [label="child-of"]; 30 | "emily" -> "freya" [label="child-of"]; 31 | "alice" -> "female" [label="gender"]; 32 | "bob" -> "male" [label="gender"]; 33 | "carl" -> "male" [label="gender"]; 34 | "freya" -> "female" [label="gender"]; 35 | } -------------------------------------------------------------------------------- /assets/query-example02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/trio/6716d8fd239ffbaf569755d47a035318d40e00e6/assets/query-example02.png -------------------------------------------------------------------------------- /assets/query-example03.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | node[color="black",style="filled",fontname="Inconsolata",fontcolor="white",fontsize=11]; 3 | edge[fontname="Inconsolata",fontsize="9"]; 4 | "emily" -> "bob" [label="child-of"]; 5 | "donald" -> "emily" [label="friend"]; 6 | "freya" -> "emily" [label="mother"]; 7 | "emily" -> "freya" [label="child-of"]; 8 | "bob" -> "emily" [label="parent"]; 9 | "bob" -> "emily" [label="father"]; 10 | "freya" -> "emily" [label="parent"]; 11 | 12 | edge[fontname="Inconsolata",fontsize=9,color=red,fontcolor=red]; 13 | "emily" -> "23" [label="age"]; 14 | "emily" -> "em" [label="nick"]; 15 | 16 | } -------------------------------------------------------------------------------- /assets/query-example03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/trio/6716d8fd239ffbaf569755d47a035318d40e00e6/assets/query-example03.png -------------------------------------------------------------------------------- /src/core.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | 3 | * Contents :toc_3_gh: 4 | - [[#namespace-thingtriocore][Namespace: thi.ng.trio.core]] 5 | - [[#protocol-definitions][Protocol definitions]] 6 | - [[#ptriple][PTriple]] 7 | - [[#ptripleseq][PTripleSeq]] 8 | - [[#pmodelconvert][PModelConvert]] 9 | - [[#pmodel][PModel]] 10 | - [[#pmodelupdate][PModelUpdate]] 11 | - [[#pmodelwatch][PModelWatch]] 12 | - [[#pmodelselect][PModelSelect]] 13 | - [[#pdataset][PDataset]] 14 | - [[#paliasmodelsupport][PAliasModelSupport]] 15 | - [[#helper-functions][Helper functions]] 16 | - [[#triple-datatype][Triple datatype]] 17 | - [[#templates][Templates]] 18 | - [[#implementation][Implementation]] 19 | - [[#override-print-methods][Override print methods]] 20 | - [[#swizzling][Swizzling]] 21 | - [[#constructor][Constructor]] 22 | - [[#in-memory-stores][In-memory stores]] 23 | - [[#classic-3-index-implementation][Classic 3-index implementation]] 24 | - [[#store-with-alias-support][Store with alias support]] 25 | - [[#triple-graph-experimental-incomplete][Triple graph (experimental, incomplete)]] 26 | - [[#constructors][Constructors]] 27 | - [[#multi-store-dataset][Multi-store dataset]] 28 | - [[#type-implementation][Type implementation]] 29 | - [[#constructor][Constructor]] 30 | - [[#watched-model-wrapper][Watched model wrapper]] 31 | - [[#constructor][Constructor]] 32 | - [[#regexp-triple-search][Regexp triple search]] 33 | - [[#clojure-collection-conversion][Clojure collection conversion]] 34 | - [[#maps][Maps]] 35 | - [[#sequential-collections][Sequential collections]] 36 | - [[#protocol-implementations][Protocol implementations]] 37 | - [[#clojure-collection-queries-pmodelselect][Clojure collection queries (PModelSelect)]] 38 | - [[#tagged-reader-literals][Tagged reader literals]] 39 | - [[#complete-namespace-definition][Complete namespace definition]] 40 | 41 | * Namespace: thi.ng.trio.core 42 | 43 | This namespace provides various protocols for the low-level triple 44 | store API, as well as a persistent in-memory store implementation and 45 | a flexible dataset type to aggregate models/stores and query multiple 46 | at once. 47 | 48 | ** Protocol definitions 49 | 50 | *** PTriple 51 | 52 | #+BEGIN_SRC clojure :noweb-ref protos 53 | (defprotocol PTriple 54 | (triple-id [_]) 55 | (subject [_]) 56 | (predicate [_]) 57 | (object [_])) 58 | #+END_SRC 59 | 60 | *** PTripleSeq 61 | 62 | This protocol is used to easily convert Clojure collections into a 63 | triple sequence. Implementations are provided for sequences & maps 64 | further below. This protocol is also used by the =PModelConvert= 65 | implementations. 66 | 67 | #+BEGIN_SRC clojure :noweb-ref protos 68 | (defprotocol PTripleSeq 69 | (triple-seq [_])) 70 | #+END_SRC 71 | 72 | *** PModelConvert 73 | 74 | This protocol is used to easily convert Clojure collections into an 75 | triplestore and so allow the query engine to operate directly on these 76 | collections. Implementations are provided for sequences & maps further 77 | below. 78 | 79 | #+BEGIN_SRC clojure :noweb-ref protos 80 | (defprotocol PModelConvert 81 | (as-model [_])) 82 | #+END_SRC 83 | 84 | *** PModel 85 | 86 | This protocol defines various accessors & predicate fns which must be 87 | implemented by a triple store in order to allow the [[query.org][trio query engine]] 88 | to act on it. 89 | 90 | #+BEGIN_SRC clojure :noweb-ref protos 91 | (defprotocol PModel 92 | (subjects [_]) 93 | (predicates [_]) 94 | (objects [_]) 95 | (subject? [_ x]) 96 | (predicate? [_ x]) 97 | (object? [_ x]) 98 | (indexed? [_ x]) 99 | (model-size [_]) 100 | (latest-triple [_]) 101 | (latest-triple-id [_])) 102 | #+END_SRC 103 | 104 | *** PModelUpdate 105 | 106 | This protocol defines accessors to add, update and remove triples from 107 | a store. Bulk operations are defined, in order to allow optimized 108 | implementations relying on connections to external storage layers. 109 | 110 | #+BEGIN_SRC clojure :noweb-ref protos 111 | (defprotocol PModelUpdate 112 | (add-triple [_ s] [_ g s]) 113 | (add-triples [_ triples] [_ g triples]) 114 | (remove-triple [_ s] [_ g s]) 115 | (remove-triples [_ triples] [_ g triples]) 116 | (update-triple [_ s s'] [_ g s s']) 117 | (remove-subject [_ s] [_ g s])) 118 | #+END_SRC 119 | 120 | *** PModelWatch 121 | 122 | This protocol allows for the injection of middleware during model 123 | updates. There're two kinds of hooks, pre-commit and post-commit and 124 | both are triggered for each single triple added or removed. 125 | 126 | Each hook is a function accepting 4 args: 127 | 128 | - commit type (=:add= or =:remove=) 129 | - watched store/model 130 | - triple to add/remove 131 | - watch ID 132 | 133 | **** Pre-commit hooks 134 | 135 | Pre-commit hooks are *always* called, irrespectively of the triple 136 | being already present in the store (when attempting to add) or missing 137 | (during remove). A pre-commit hook *must* return a triple (e.g. the 138 | passed arg) to signal success or =nil= to indicate the add/remove op 139 | should be canceled. This convention therefore allows hooks to be used 140 | as triple verifiers or transformers. 141 | 142 | Since hooks are stored in a map, their call order is undefined. 143 | Subsequent hooks will receive as triple arg the result of earlier 144 | hooks invoked. The model operation (add/remove) will only succeed if 145 | all hooks have returned a triple. If any hook fails, the model remains 146 | untouched. 147 | 148 | **** Post-commit hooks 149 | 150 | Post-commit hooks are only called if a triple has actually been added 151 | or removed from the model. Furthermore, these hooks *must* return a 152 | model instance (usually the passed arg) to signal success or =nil= to 153 | indicate failure and cancel the model operation. This allows 154 | post-commit hooks to further validate and/or transform the model (e.g. 155 | add additional triples to the watched model without invoking any 156 | hooks). 157 | 158 | Apart from that post-hooks follow the same calling order and 159 | all-or-nothing convention as described above for pre-commit hooks. 160 | 161 | **** Example 162 | 163 | #+BEGIN_SRC clojure 164 | (def g 165 | (-> (plain-store) 166 | (watched-model) 167 | ;; this hook stringifies all triples elements 168 | (add-pre-commit-hook 169 | :stringify (fn [_ _ triple _] (mapv str triple))) 170 | ;; trace successful additions/removals 171 | (add-post-commit-hook 172 | :trace (fn [type model triple id] (prn id type triple) model)) 173 | 174 | (add-triple '[s p o]))) 175 | ;; => :trace :add ["s" "p" "o"] 176 | ;; => #'user/g 177 | #+END_SRC 178 | 179 | **** Definition 180 | 181 | #+BEGIN_SRC clojure :noweb-ref protos 182 | (defprotocol PModelWatch 183 | (add-pre-commit-hook [_ id f]) 184 | (add-post-commit-hook [_ id f]) 185 | (remove-pre-commit-hook [_ id]) 186 | (remove-post-commit-hook [_ id])) 187 | #+END_SRC 188 | 189 | *** PModelSelect 190 | 191 | #+BEGIN_SRC clojure :noweb-ref protos 192 | (defprotocol PModelSelect 193 | (select [_] [_ s p o] [_ g s p o]) 194 | (select-with-alts [_ s p o] [_ g s p o])) 195 | #+END_SRC 196 | 197 | The =select= method is plays the main role here and is used to perform 198 | low-level pattern match queries on a triplestore. Given any 199 | combination of subject, predicate or object, the method performs a 200 | search and returns all matching triples. The overall contract is that 201 | if any SPO arg is =nil=, then this triple element matches anything. 202 | Hence, there're eight possible variations to match triples: 203 | 204 | #+BEGIN_SRC clojure 205 | ;; match all triples 206 | (select store nil nil nil) 207 | 208 | ;; match all triples w subject = :s 209 | (select store :s nil nil) 210 | 211 | ;; match all triples w/ pred = :p 212 | (select store nil :p nil) 213 | 214 | ;; match all triples w/ obj = :o 215 | (select store nil nil :o) 216 | 217 | ;; match all triples w/ subj = :s AND pred = :p 218 | (select store :s :p nil) 219 | 220 | ;; match all triples w/ subj= :s AND obj = :o 221 | (select store :s nil :o) 222 | 223 | ;; match all triples w/ pred = :p AND obj = :o 224 | (select store nil :p :o) 225 | 226 | ;; match only given triple [:s :p :o] 227 | (select store :s :p :o) 228 | #+END_SRC 229 | 230 | *** PDataset 231 | 232 | In trio terminology, a dataset constitutes a triplestore, which 233 | logically combines a number of other (independent) =PModel= 234 | implementations. =PDataset= implementations *must* provide a =PModel= 235 | implementation too and any queries run over a dataset will produce a 236 | combined result from all included models. In addition, a dataset 237 | *must* provide a =select= implementation with an extra graph arg to 238 | directly query a specific model, rather than all. 239 | 240 | #+BEGIN_SRC clojure :noweb-ref protos 241 | (defprotocol PDataset 242 | (remove-model [_ id]) 243 | (update-model [_ id m]) 244 | (get-model [_ id])) 245 | #+END_SRC 246 | 247 | *** PAliasModelSupport 248 | 249 | This is an experimental feature to support triplestore implementations 250 | which allow the aliasing/renaming of individual triple elements and 251 | provide potentially optimized solutions. See [[#store-with-alias-support][Store with alias support]] 252 | for more details. 253 | 254 | #+BEGIN_SRC clojure :noweb-ref protos 255 | (defprotocol PAliasModelSupport 256 | (rewrite-alias [_ a b])) 257 | #+END_SRC 258 | 259 | ** Helper functions 260 | 261 | #+BEGIN_SRC clojure :noweb-ref helpers 262 | (def ^:private triple-ident (atom 0)) 263 | 264 | (defn- next-triple-id [] (swap! triple-ident inc)) 265 | 266 | (defn- remove-from-index 267 | [idx i1 i2 i3] 268 | (let [kv (idx i1) 269 | v (disj (kv i2) i3) 270 | kv (if (seq v) kv (dissoc kv i2))] 271 | (if (seq kv) 272 | (if (seq v) 273 | (assoc-in idx [i1 i2] v) 274 | (assoc idx i1 kv)) 275 | (dissoc idx i1)))) 276 | 277 | (defn- rewrite-alias* 278 | [store pred id p q] 279 | (if (pred store p) 280 | (let [xs (apply select store (assoc [nil nil nil] id p)) 281 | store (remove-triples store xs)] 282 | (add-triples store (map #(assoc % id q) xs))) 283 | store)) 284 | 285 | (defn rewrite-alias-naive 286 | [store p q] 287 | (-> store 288 | (rewrite-alias* subject? 0 p q) 289 | (rewrite-alias* predicate? 1 p q) 290 | (rewrite-alias* object? 2 p q))) 291 | 292 | (defn trace [prefix x] (prn prefix x) x) 293 | #+END_SRC 294 | 295 | ** Triple datatype 296 | 297 | The triplestore implementations defined in this namespace use a 298 | custom/optimized 3-element vector type to store triples internally. 299 | This =Triple= type can be used like a normal vector since it 300 | implements all necessary Clojure protocols. In addition to using 301 | numeric indices (0..2) to refer to elements, the keywords =:s=, =:p= 302 | and =:o= can be used alternatively. Furthermore, the =Triple= type 303 | supports swizzling to look up, edit and/or change the order of elements: 304 | 305 | #+BEGIN_SRC clojure 306 | (def t (triple 'karsten 'nickname 'toxi)) 307 | 308 | (:s t) ; => karsten 309 | (:p t) ; => nickname 310 | (:o t) ; => toxi 311 | (:so t) ; => [karsten toxi] 312 | (:ops t) ; => [toxi nickname karsten] 313 | 314 | (assoc t :p 'same-as) ; => [karsten same-as toxi] 315 | (assoc t :po '[author trio]) ; => [karsten author trio] 316 | (assoc t :op '[trio author]) ; => [karsten author trio] 317 | #+END_SRC 318 | 319 | *** Templates 320 | 321 | In order to deduplicate shared code between both the Clojure & 322 | ClojureScript protocol implementations, we first define a few snippets 323 | as re-usable templates, which are injected in the appropriate places 324 | of the code. 325 | 326 | **** apply 327 | #+NAME: tpl-apply 328 | #+BEGIN_SRC emacs-lisp :var fn="" 329 | (replace-regexp-in-string "{{fn}}" fn 330 | "(condp = (count args) 331 | 1 ({{fn}} _ (first args) nil) 332 | 2 ({{fn}} _ (first args) (second args)) 333 | (err/arity-error! (count args)))") 334 | #+END_SRC 335 | **** assocN 336 | #+BEGIN_SRC clojure :noweb-ref tpl-assoc-n 337 | (case (int k) 338 | 0 (Triple. v p o (next-triple-id) nil) 339 | 1 (Triple. s v o (next-triple-id) nil) 340 | 2 (Triple. s p v (next-triple-id) nil) 341 | (err/key-error! k)) 342 | #+END_SRC 343 | **** containsKey 344 | #+BEGIN_SRC clojure :noweb-ref tpl-contains-key 345 | (not (nil? (#{0 1 2 :s :p :o} k))) 346 | #+END_SRC 347 | **** equals / equiv 348 | #+NAME: tpl-equals 349 | #+BEGIN_SRC emacs-lisp :var fn="" 350 | (replace-regexp-in-string 351 | "{{fn}}" fn 352 | "(if (instance? Triple x) 353 | (and ({{fn}} s (.-s ^Triple x)) 354 | ({{fn}} p (.-p ^Triple x)) 355 | ({{fn}} o (.-o ^Triple x))) 356 | (and (instance? java.util.Collection x) 357 | (== 3 (count x)) 358 | ({{fn}} s (nth x 0)) 359 | ({{fn}} p (nth x 1)) 360 | ({{fn}} o (nth x 2))))") 361 | #+END_SRC 362 | **** nth w/ error 363 | #+BEGIN_SRC clojure :noweb-ref tpl-nth-err 364 | (case (int k) 0 s, 1 p, 2 o, (err/key-error! k)) 365 | #+END_SRC 366 | **** nth w/ notfound 367 | #+BEGIN_SRC clojure :noweb-ref tpl-nth-nf 368 | (case (int k) 0 s, 1 p, 2 o, nf) 369 | #+END_SRC 370 | **** compare 371 | #+BEGIN_SRC clojure :noweb-ref tpl-compare 372 | (if (instance? Triple x) 373 | (let [c (compare s (.-s ^Triple x))] 374 | (if (== 0 c) 375 | (let [c (compare p (.-p ^Triple x))] 376 | (if (== 0 c) 377 | (compare o (.-o ^Triple x)) 378 | c)) 379 | c)) 380 | (let [c (count x)] 381 | (if (== 3 c) (compare x _) (- 3 c)))) 382 | #+END_SRC 383 | **** hashCode 384 | #+BEGIN_SRC clojure :noweb-ref tpl-hashcode 385 | (-> 31 386 | (unchecked-add-int (hash s)) 387 | (unchecked-multiply-int 31) 388 | (unchecked-add-int (hash p)) 389 | (unchecked-multiply-int 31) 390 | (unchecked-add-int (hash o))) 391 | #+END_SRC 392 | **** rest 393 | #+BEGIN_SRC clojure :noweb-ref tpl-rest 394 | (cons p (cons o nil)) 395 | #+END_SRC 396 | **** reduce 397 | #+BEGIN_SRC clojure :noweb-ref tpl-reduce 398 | (let [acc (f s p)] 399 | (if (reduced? acc) 400 | @acc 401 | (let [acc (f acc o)] 402 | (if (reduced? acc) 403 | @acc 404 | acc)))) 405 | #+END_SRC 406 | **** reduce w/ init 407 | #+BEGIN_SRC clojure :noweb-ref tpl-reduce-start 408 | (let [acc (f start s)] 409 | (if (reduced? acc) 410 | @acc 411 | (let [acc (f acc p)] 412 | (if (reduced? acc) 413 | @acc 414 | (let [acc (f acc o)] 415 | (if (reduced? acc) 416 | @acc 417 | acc)))))) 418 | #+END_SRC 419 | 420 | *** Implementation 421 | #+BEGIN_SRC clojure :noweb-ref triple 422 | (deftype Triple 423 | #?(:clj [s p o id ^:unsynchronized-mutable __hash]) 424 | #?(:cljs [s p o id ^:mutable __hash]) 425 | #+END_SRC 426 | **** Clojure 427 | #+BEGIN_SRC clojure :noweb-ref triple 428 | #?@(:clj 429 | [clojure.lang.ILookup 430 | (valAt [_ k] (swizzle _ k nil)) 431 | (valAt [_ k nf] (swizzle _ k nf)) 432 | 433 | java.util.concurrent.Callable 434 | (call [_] (.invoke ^clojure.lang.IFn _)) 435 | java.lang.Runnable 436 | (run [_] (.invoke ^clojure.lang.IFn _)) 437 | 438 | clojure.lang.IFn 439 | (invoke [_ k] (swizzle _ k nil)) 440 | (invoke [_ k nf] (swizzle _ k nf)) 441 | (applyTo 442 | [_ args] 443 | <>) 444 | 445 | clojure.lang.IPersistentVector 446 | clojure.lang.Associative 447 | (count [_] 3) 448 | (length [_] 3) 449 | (containsKey [_ k] <>) 450 | (entryAt [_ k] (clojure.lang.MapEntry. k <>)) 451 | (assoc [_ k v] (swizzle-assoc _ k v)) 452 | (assocN 453 | [_ k v] 454 | <>) 455 | 456 | java.util.Collection 457 | (isEmpty [_] false) 458 | (iterator [_] (.iterator ^java.util.Collection (list s p o))) 459 | (toArray [_] (object-array _)) 460 | (size [_] 3) 461 | 462 | clojure.lang.Sequential 463 | clojure.lang.Seqable 464 | (seq [_] (seq [s p o])) 465 | (cons [_ x] [s p o x]) 466 | (peek [_] o) 467 | (pop [_] [s p]) 468 | (rseq [_] (seq [o p s])) 469 | (nth [_ k] <>) 470 | (nth [_ k nf] <>) 471 | (equiv 472 | [_ x] 473 | <>) 474 | (equals 475 | [_ x] 476 | <>) 477 | (hashCode 478 | [_] 479 | <>) 480 | 481 | clojure.lang.IHashEq 482 | (hasheq 483 | [_] 484 | (or __hash (set! __hash 485 | (mix-collection-hash 486 | <> 487 | 3)))) 488 | 489 | Comparable 490 | (compareTo 491 | [_ x] 492 | <>) 493 | 494 | cp/InternalReduce 495 | (internal-reduce 496 | [_ f start] 497 | <>) 498 | 499 | cp/CollReduce 500 | (coll-reduce 501 | [_ f] 502 | <>) 503 | (coll-reduce 504 | [_ f start] 505 | <>) 506 | 507 | Object 508 | (toString 509 | [_] 510 | (.toString 511 | (doto (StringBuilder. "#trio/Triple [") 512 | (.append (pr-str s)) 513 | (.append " ") 514 | (.append (pr-str p)) 515 | (.append " ") 516 | (.append (pr-str o)) 517 | (.append " ") 518 | (.append (pr-str id)) 519 | (.append "]")))) 520 | ]) 521 | #+END_SRC 522 | **** ClojureScript protocols 523 | #+BEGIN_SRC clojure :noweb-ref triple 524 | #?@(:cljs 525 | [ICloneable 526 | (-clone 527 | [_] (Triple. s p o id __hash)) 528 | 529 | ILookup 530 | (-lookup [_ k] (swizzle _ k nil)) 531 | (-lookup [_ k nf] (swizzle _ k nf)) 532 | 533 | IFn 534 | (-invoke [_ k] (swizzle _ k nil)) 535 | (-invoke [_ k nf] (swizzle _ k nf)) 536 | 537 | ICounted 538 | (-count [_] 3) 539 | 540 | IAssociative 541 | (-contains-key? [_ k] <>) 542 | (-assoc [_ k v] (swizzle-assoc _ k v)) 543 | 544 | IVector 545 | (-assoc-n 546 | [_ k v] 547 | <>) 548 | 549 | ISequential 550 | ISeq 551 | (-first [_] s) 552 | (-rest [_] <>) 553 | 554 | INext 555 | (-next [_] <>) 556 | 557 | ISeqable 558 | (-seq [_] _) 559 | 560 | IReversible 561 | (-rseq [_] (seq o p s)) 562 | 563 | IIndexed 564 | (-nth [_ k] <>) 565 | (-nth [_ k nf] <>) 566 | 567 | ICollection 568 | (-conj [_ x] [s p o x]) 569 | 570 | IStack 571 | (-peek [_] o) 572 | (-pop [_] [s p]) 573 | 574 | IComparable 575 | (-compare 576 | [_ x] 577 | <>) 578 | 579 | IHash 580 | (-hash 581 | [_] 582 | (or __hash 583 | (set! (.-__hash _) 584 | (mix-collection-hash 585 | (-> 31 (+ (hash s)) 586 | (bit-or 0) 587 | (imul 31) (+ (hash p)) 588 | (bit-or 0) 589 | (imul 31) (+ (hash o)) 590 | (bit-or 0)) 591 | 3)))) 592 | 593 | IEquiv 594 | (-equiv 595 | [_ x] 596 | (if (instance? Triple x) 597 | (and (= s (.-s ^Triple x)) (= p (.-p ^Triple x)) (= o (.-o ^Triple x))) 598 | (and (sequential? x) (== 3 (count x)) 599 | (= s (nth x 0)) (= p (nth x 1)) (= o (nth x 2))))) 600 | 601 | IReduce 602 | (-reduce 603 | [coll f] 604 | <>) 605 | (-reduce 606 | [coll f start] 607 | <>) 608 | 609 | Object 610 | (toString 611 | [_] 612 | (str "#trio/Triple [" 613 | (pr-str s) " " 614 | (pr-str p) " " 615 | (pr-str o) " " 616 | (pr-str id) "]")) 617 | ]) 618 | #+END_SRC 619 | 620 | **** PTriple 621 | #+BEGIN_SRC clojure :noweb-ref triple 622 | PTriple 623 | (triple-id [_] id) 624 | (subject [_] s) 625 | (predicate [_] p) 626 | (object [_] o) 627 | #+END_SRC 628 | **** End of triple type 629 | #+BEGIN_SRC clojure :noweb-ref triple 630 | ) 631 | #+END_SRC 632 | 633 | *** Override print methods 634 | #+BEGIN_SRC clojure :noweb-ref triple 635 | #?(:clj 636 | (defmethod clojure.pprint/simple-dispatch Triple 637 | [^Triple o] ((get-method clojure.pprint/simple-dispatch clojure.lang.IPersistentVector) o))) 638 | #?(:clj 639 | (defmethod print-method Triple 640 | [^Triple o ^java.io.Writer w] (.write w (.toString o)))) 641 | #+END_SRC 642 | 643 | *** Swizzling 644 | #+BEGIN_SRC clojure triple :noweb-ref triple 645 | (defn- lookup3 646 | [^Triple _ k nf] 647 | (case k 648 | \s (.-s _) 649 | \p (.-p _) 650 | \o (.-o _) 651 | (or nf (err/key-error! k)))) 652 | 653 | (defn- swizzle 654 | [^Triple _ k default] 655 | (if (number? k) 656 | (case (int k) 657 | 0 (.-s _) 658 | 1 (.-p _) 659 | 2 (.-o _) 660 | (or default (err/key-error! k))) 661 | (case k 662 | :s (.-s _) 663 | :p (.-p _) 664 | :o (.-o _) 665 | (let [n (name k) c (count n)] 666 | (case c 667 | 2 [(lookup3 _ (nth n 0) default) 668 | (lookup3 _ (nth n 1) default)] 669 | 3 (Triple. 670 | (lookup3 _ (nth n 0) default) 671 | (lookup3 _ (nth n 1) default) 672 | (lookup3 _ (nth n 2) default) 673 | (next-triple-id) 674 | nil) 675 | (or default (err/key-error! k))))))) 676 | 677 | (defn- swizzle-assoc* 678 | [_ keymap k v] 679 | (let [n (name k) 680 | c (count n)] 681 | (if (and (<= c (count keymap)) (== c (count v) (count (into #{} n)))) 682 | (loop [acc (vec _), i 0, n n] 683 | (if n 684 | (recur (assoc acc (keymap (first n)) (v i)) (inc i) (next n)) 685 | (Triple. (acc 0) (acc 1) (acc 2) (next-triple-id) nil))) 686 | (err/key-error! k)))) 687 | 688 | (defn- swizzle-assoc 689 | [^Triple _ k v] 690 | (case k 691 | :s (Triple. v (.-p _) (.-o _) (next-triple-id) nil) 692 | :p (Triple. (.-s _) v (.-o _) (next-triple-id) nil) 693 | :o (Triple. (.-s _) (.-p _) v (next-triple-id) nil) 694 | 0 (Triple. v (.-p _) (.-o _) (next-triple-id) nil) 695 | 1 (Triple. (.-s _) v (.-o _) (next-triple-id) nil) 696 | 2 (Triple. (.-s _) (.-p _) v (next-triple-id) nil) 697 | (swizzle-assoc* _ {\s 0 \p 1 \o 2} k v))) 698 | #+END_SRC 699 | 700 | *** Constructor 701 | #+BEGIN_SRC clojure :noweb-ref triple 702 | (defn triple 703 | ([s p o] (Triple. s p o (next-triple-id) nil)) 704 | ([t] (if (instance? Triple t) 705 | t 706 | (Triple. 707 | (first t) (nth t 1) (nth t 2) 708 | (if (= 4 (count t)) (nth t 3) (next-triple-id)) nil)))) 709 | #+END_SRC 710 | 711 | ** In-memory stores 712 | 713 | *** Classic 3-index implementation 714 | 715 | #+BEGIN_SRC clojure :noweb-ref memstore 716 | (defn- select-with-alts-1 717 | [coll idx] 718 | (->> (set/intersection coll (set (keys idx))) 719 | (mapcat #(->> % idx vals (apply concat))))) 720 | 721 | (defn- select-with-alts-2 722 | [outer inner idx] 723 | (mapcat 724 | (fn [o] 725 | (let [out (idx o)] 726 | (->> (set (keys out)) 727 | (set/intersection inner) 728 | (mapcat out)))) 729 | outer)) 730 | 731 | (defn- select-with-alts-3 732 | [outer inner preds idx lookup] 733 | (reduce 734 | (fn [acc o] 735 | (let [out (idx o)] 736 | (->> (set (keys out)) 737 | (set/intersection preds) 738 | (reduce 739 | (fn [acc p] 740 | (if-let [t (some #(if (inner (lookup %)) %) (out p))] 741 | (conj acc t) 742 | acc)) 743 | acc)))) 744 | [] outer)) 745 | 746 | (deftype PlainMemoryStore [spo pos osp size] 747 | Object 748 | (toString [_] (str "#trio/MemStore " (pr-str (vec (select _ nil nil nil))))) 749 | PModelUpdate 750 | (add-triple 751 | [_ [s p o :as t]] 752 | (if (-> spo (get s nil) (get p nil) (get t nil)) _ 753 | (let [s (get (find spo s) 0 s) 754 | p (get (find pos p) 0 p) 755 | o (get (find osp o) 0 o) 756 | t (Triple. s p o (next-triple-id) nil)] 757 | (PlainMemoryStore. 758 | (update-in spo [s p] d/set-conj t) 759 | (update-in pos [p o] d/set-conj t) 760 | (update-in osp [o s] d/set-conj t) 761 | (inc size))))) 762 | (add-triples [_ triples] 763 | (loop [changed? false, spo spo, pos pos, osp osp, size size, xs triples] 764 | (if xs 765 | (let [[s p o :as t] (first xs)] 766 | (if (-> spo (get s nil) (get p nil) (get t nil)) 767 | (recur changed? spo pos osp size (next xs)) 768 | (let [s (get (find spo s) 0 s) 769 | p (get (find pos p) 0 p) 770 | o (get (find osp o) 0 o) 771 | t (Triple. s p o (next-triple-id) nil)] 772 | (recur 773 | true 774 | (update-in spo [s p] d/set-conj t) 775 | (update-in pos [p o] d/set-conj t) 776 | (update-in osp [o s] d/set-conj t) 777 | (inc size) 778 | (next xs))))) 779 | (if changed? 780 | (PlainMemoryStore. spo pos osp size) 781 | _)))) 782 | (remove-triple [_ [s p o :as t]] 783 | (if (-> spo (get s nil) (get p nil) (get t nil)) 784 | (PlainMemoryStore. 785 | (remove-from-index spo s p t) 786 | (remove-from-index pos p o t) 787 | (remove-from-index osp o s t) 788 | (dec size)) 789 | _)) 790 | (remove-triples [_ triples] 791 | (loop [changed? false, spo spo, pos pos, osp osp, size size, xs triples] 792 | (if xs 793 | (let [[s p o :as t] (first xs)] 794 | (if (-> spo (get s nil) (get p nil) (get t nil)) 795 | (recur 796 | true 797 | (remove-from-index spo s p t) 798 | (remove-from-index pos p o t) 799 | (remove-from-index osp o s t) 800 | (dec size) 801 | (next xs)) 802 | (recur changed? spo pos osp size (next xs)))) 803 | (if changed? 804 | (PlainMemoryStore. spo pos osp size) 805 | _)))) 806 | (update-triple [_ s1 s2] 807 | (add-triple (remove-triple _ s1) s2)) 808 | (remove-subject [_ s] 809 | (remove-triples _ (select _ s nil nil))) 810 | 811 | PModel 812 | (subject? [_ x] 813 | (if (spo x) x)) 814 | (predicate? [_ x] 815 | (if (pos x) x)) 816 | (object? [_ x] 817 | (if (osp x) x)) 818 | (indexed? [_ x] 819 | (if (or (spo x) (pos x) (osp x)) x)) 820 | (subjects [_] (keys spo)) 821 | (predicates [_] (keys pos)) 822 | (objects [_] (keys osp)) 823 | (model-size [_] size) 824 | (latest-triple [_] 825 | (->> spo vals (mapcat vals) (apply concat) ;; select all 826 | (reduce 827 | (fn [a b] (if (< (triple-id a) (triple-id b)) b a))))) 828 | (latest-triple-id [_] 829 | (triple-id (latest-triple _))) 830 | 831 | PModelSelect 832 | (select [_] 833 | (select _ nil nil nil)) 834 | (select 835 | [_ s p o] 836 | (if s 837 | (if p 838 | (if o 839 | ;; s p o 840 | (let [t (triple s p o)] 841 | (if (-> spo (get s nil) (get p nil) (get t nil)) [t])) 842 | ;; s p nil 843 | (-> spo (get s nil) (get p nil))) 844 | ;; s nil o / s nil nil 845 | (if o 846 | (-> osp (get o nil) (get s nil)) 847 | (->> (spo s) vals (apply concat)))) 848 | (if p 849 | (if o 850 | ;; nil p o 851 | (-> pos (get p nil) (get o nil)) 852 | ;; nil p nil 853 | (->> (pos p) vals (apply concat))) 854 | (if o 855 | ;; nil nil o 856 | (->> (osp o) vals (apply concat)) 857 | ;; nil nil nil 858 | (->> spo vals (mapcat vals) (apply concat)))))) 859 | 860 | (select-with-alts 861 | [_ s p o] 862 | (let [s (if (set? s) (if-not (empty? s) s) (if s #{s})) 863 | p (if (set? p) (if-not (empty? p) p) (if p #{p})) 864 | o (if (set? o) (if-not (empty? o) o) (if o #{o}))] 865 | (if s 866 | (if p 867 | (if o 868 | (select-with-alts-3 s o p spo peek) 869 | (select-with-alts-2 s p spo)) 870 | (if o 871 | (select-with-alts-2 o s osp) 872 | (select-with-alts-1 s spo))) 873 | (if p 874 | (if o 875 | (select-with-alts-2 p o pos) 876 | (select-with-alts-1 p pos)) 877 | (if o 878 | (select-with-alts-1 o osp) 879 | (->> spo vals (mapcat vals) (apply concat))))))) 880 | 881 | PAliasModelSupport 882 | (rewrite-alias 883 | [_ a b] (rewrite-alias-naive _ a b))) 884 | 885 | #?(:clj 886 | (defmethod print-method 887 | PlainMemoryStore [^PlainMemoryStore o ^java.io.Writer w] (.write w (.toString o)))) 888 | #+END_SRC 889 | 890 | *** Store with alias support 891 | 892 | Some use cases require support for aliased resources stored in 893 | triples. For example in RDF resources can be explicitly declared as 894 | equal via an =owl:sameAs= relationship. 895 | 896 | The =AliasMemoryStore= defined below achieves this goal by wrapping an 897 | existing triplestore and combines it with an Union Find index to allow 898 | registration of aliases and queries using them. 899 | 900 | Union Find is based on the concept of disjoint sets of connected 901 | components. For each component (set) a single canonical value is 902 | chosen. In the case of the =AliasMemoryStore= this means any triples 903 | containing aliased values will be rewritten to contain only canonical 904 | values. This happens with already existing triples and newly added 905 | ones. Aliases can be defined and removed at any time, although it's 906 | more efficient to declare aliases before inserting new triples to 907 | avoid major rewrite operations. 908 | 909 | #+BEGIN_SRC clojure :noweb-ref alias-store 910 | (defrecord AliasMemoryStore [store aliases] 911 | PModelUpdate 912 | (add-triple 913 | [_ [s p o]] 914 | (let [t [(or (u/canonical aliases s) s) 915 | (or (u/canonical aliases p) p) 916 | (or (u/canonical aliases o) o)]] 917 | (AliasMemoryStore. (add-triple store t) aliases))) 918 | (add-triples 919 | [_ triples] 920 | (loop [store store, xs triples] 921 | (if xs 922 | (let [[s p o] (first xs) 923 | t [(or (u/canonical aliases s) s) 924 | (or (u/canonical aliases p) p) 925 | (or (u/canonical aliases o) o)]] 926 | (recur (add-triple store t) (next xs))) 927 | (AliasMemoryStore. store aliases)))) 928 | 929 | PModel 930 | (subject? [_ x] 931 | (subject? store (or (u/canonical aliases x) x))) 932 | (predicate? [_ x] 933 | (predicate? store (or (u/canonical aliases x) x))) 934 | (object? [_ x] 935 | (object? store (or (u/canonical aliases x) x))) 936 | (indexed? [_ x] 937 | (indexed? store (or (u/canonical aliases x) x))) 938 | (subjects [_] (subjects store)) 939 | (predicates [_] (predicates store)) 940 | (objects [_] (objects store)) 941 | (model-size [_] (model-size store)) 942 | 943 | PModelSelect 944 | (select 945 | [_] (select _ nil nil nil)) 946 | (select 947 | [_ s p o] 948 | (let [s (or (u/canonical aliases s) s) 949 | p (or (u/canonical aliases p) p) 950 | o (or (u/canonical aliases o) o)] 951 | (select store s p o))) 952 | 953 | u/PUnionFind 954 | (canonical [_ p] (u/canonical aliases p)) 955 | (canonical? [_ p] (u/canonical? aliases p)) 956 | (component [_ p] (u/component aliases p)) 957 | (disjoint-components [_] (u/disjoint-components aliases)) 958 | (register [_ p] (AliasMemoryStore. store (u/register aliases p))) 959 | (unregister 960 | [_ p] 961 | (if (u/canonical? aliases p) 962 | (let [q (first (disj (u/component aliases p) p)) 963 | aliases (u/unregister aliases p) 964 | store (if q (rewrite-alias store p q) store)] 965 | (AliasMemoryStore. store aliases)) 966 | (AliasMemoryStore. store (u/unregister aliases p)))) 967 | (unified? [_ p q] (u/unified? aliases p q)) 968 | (union 969 | [_ p q] 970 | (if (and p q) 971 | (let [aliases (u/union aliases p q) 972 | canon (u/canonical aliases p) 973 | store (if (= p canon) 974 | (rewrite-alias store q canon) 975 | (rewrite-alias store p canon))] 976 | (AliasMemoryStore. store aliases)) 977 | (err/illegal-arg! (str "aliases must be both non-nil values: " [p q]))))) 978 | #+END_SRC 979 | *** Triple graph (experimental, incomplete) 980 | 981 | Currently still inefficient (1.5-2x slower than =PlainMemoryStore=), 982 | but still thinking this approach of treating triples (incl. with 983 | wildcards) as graph, might have better milage later as part of the 984 | implementation for an efficient inferencer... 985 | 986 | #+BEGIN_SRC clojure :noweb-ref memstore 987 | (defprotocol PNode 988 | (get-children [_]) 989 | (add-child [_ c]) 990 | (remove-child [_ c]) 991 | (is-leaf? [_])) 992 | 993 | (defprotocol PGraph 994 | (add-node [_ n] [_ n parents]) 995 | (get-node [_ t]) 996 | (get-node-for-id [_ id]) 997 | (get-ids [_]) 998 | (get-nodes [_])) 999 | 1000 | (deftype TripleNode [triple ^:unsynchronized-mutable children] 1001 | PNode 1002 | (get-children 1003 | [_] children) 1004 | (add-child 1005 | [_ c] (set! children (conj (or children #{}) c)) _) 1006 | (remove-child 1007 | [_ c] (set! children (disj children c)) _) 1008 | (is-leaf? 1009 | [_] (and (first triple) (nth triple 1) (nth triple 2))) 1010 | 1011 | Object 1012 | (toString [_] (str (pr-str triple) " " (pr-str children)))) 1013 | 1014 | (def ^:private root [nil nil nil]) 1015 | 1016 | (defn tg-select-with-alts 1017 | [_ s p o] 1018 | (if (and (seq s) (seq p) (seq o)) 1019 | (mapcat 1020 | (fn [[s p o]] (select _ s p o)) 1021 | (d/cartesian-product s p o)))) 1022 | 1023 | (declare index-branch) 1024 | 1025 | (deftype TripleGraph [nodes ids next-id size] 1026 | Object 1027 | (toString 1028 | [_] (str ":nodes " (pr-str nodes) 1029 | " :ids " (pr-str ids) 1030 | " :next " next-id)) 1031 | 1032 | PGraph 1033 | (add-node 1034 | [_ n] (add-node _ n nil)) 1035 | (add-node 1036 | [_ n parents] 1037 | (let [g (TripleGraph. 1038 | (assoc nodes next-id n) 1039 | (assoc ids (.-triple ^TripleNode n) next-id) 1040 | (inc next-id) 1041 | (if (is-leaf? n) (inc size) size))] 1042 | (when (seq parents) 1043 | (doseq [^TripleNode p parents] 1044 | (add-child p next-id))) 1045 | g)) 1046 | (get-node 1047 | [_ t] (nodes (ids t))) 1048 | (get-node-for-id 1049 | [_ id] (nodes id)) 1050 | 1051 | PModel 1052 | (subject? 1053 | [_ x] ((subjects _) x)) 1054 | (predicate? 1055 | [_ x] ((predicates _) x)) 1056 | (object? 1057 | [_ x] ((objects _) x)) 1058 | (indexed? 1059 | [_ x] (or (subject? _ x) (predicate? _ x) (object? _ x))) 1060 | (subjects 1061 | [_] (->> (nodes 0) 1062 | (get-children) 1063 | (map #(first (.-triple ^TripleNode (nodes %)))) 1064 | (filter identity) 1065 | (set))) 1066 | (predicates 1067 | [_] (->> (nodes 0) 1068 | (get-children) 1069 | (map #(nth (.-triple ^TripleNode (nodes %)) 1)) 1070 | (filter identity) 1071 | (set))) 1072 | (objects 1073 | [_] (->> (nodes 0) 1074 | (get-children) 1075 | (map #(nth (.-triple ^TripleNode (nodes %)) 2)) 1076 | (filter identity) 1077 | (set))) 1078 | (model-size 1079 | [_] size) 1080 | 1081 | PModelUpdate 1082 | (add-triple 1083 | [_ [s p o :as t]] 1084 | (if-not (ids t) 1085 | (let [id (.-next-id _) 1086 | ^TripleGraph g (add-node _ (TripleNode. (Triple. s p o (next-triple-id) nil) nil) nil)] 1087 | (-> (index-branch g id [[s p nil] [s nil nil] root]) 1088 | (index-branch id [[nil p o] [nil p nil] root]) 1089 | (index-branch id [[s nil o] [nil nil o] root]))) 1090 | _)) 1091 | (add-triples 1092 | [_ triples] (reduce add-triple _ triples)) 1093 | 1094 | PModelSelect 1095 | (select 1096 | [_] (select _ nil nil nil)) 1097 | (select 1098 | [_ s p o] 1099 | (let [ids (if (or s p o) 1100 | (if-let [id (ids (triple s p o))] [id]) 1101 | (->> (nodes 0) 1102 | (get-children) 1103 | (filter #(first (.-triple ^TripleNode (nodes %))))))] 1104 | (if (seq ids) 1105 | (loop [acc (transient []) 1106 | ids (into #?(:clj clojure.lang.PersistentQueue/EMPTY :cljs cljs.core.PersistentQueue.EMPTY) ids)] 1107 | (if (seq ids) 1108 | (let [^TripleNode n (nodes (peek ids)) 1109 | c (get-children n)] 1110 | (if c 1111 | (recur acc (into (pop ids) c)) 1112 | (recur (conj! acc (.-triple n)) (pop ids)))) 1113 | (persistent! acc)))))) 1114 | (select-with-alts 1115 | [_ s p o] 1116 | (let [s (if (set? s) (if-not (empty? s) s) (if s #{s})) 1117 | p (if (set? p) (if-not (empty? p) p) (if p #{p})) 1118 | o (if (set? o) (if-not (empty? o) o) (if o #{o}))] 1119 | (if s 1120 | (if p 1121 | (if o 1122 | (tg-select-with-alts 1123 | _ 1124 | (set/intersection s (subjects _)) 1125 | (set/intersection p (predicates _)) 1126 | (set/intersection o (objects _))) 1127 | (tg-select-with-alts 1128 | _ (set/intersection s (subjects _)) (set/intersection p (predicates _)) #{nil})) 1129 | (if o 1130 | (tg-select-with-alts 1131 | _ (set/intersection s (subjects _)) #{nil} (set/intersection o (objects _))) 1132 | (tg-select-with-alts 1133 | _ (set/intersection s (subjects _)) #{nil} #{nil}))) 1134 | (if p 1135 | (if o 1136 | (tg-select-with-alts 1137 | _ #{nil} (set/intersection p (predicates _)) (set/intersection o (objects _))) 1138 | (tg-select-with-alts 1139 | _ #{nil} (set/intersection p (predicates _)) #{nil})) 1140 | (if o 1141 | (tg-select-with-alts 1142 | _ #{nil} #{nil} (set/intersection o (objects _))) 1143 | (select _ nil nil nil))))))) 1144 | 1145 | (defn- index-branch 1146 | [g id patterns] 1147 | (loop [^TripleGraph g g, id id, ps patterns] 1148 | (if ps 1149 | (let [id' ((.-ids g) (first ps))] 1150 | (if id' 1151 | (do 1152 | (add-child ^TripleNode ((.-nodes g) id') id) 1153 | g) 1154 | (recur 1155 | (add-node g (TripleNode. (triple (first ps)) #{id}) nil) 1156 | (.-next-id g) 1157 | (next ps)))) 1158 | g))) 1159 | 1160 | #?(:clj 1161 | (defn triple-graph->dot 1162 | ([g] 1163 | (str "digraph g {\n" 1164 | "node[color=black,style=filled,fontname=Inconsolata,fontcolor=white,fontsize=9];\n" 1165 | "edge[fontname=Inconsolata,fontsize=9];\n" 1166 | (triple-graph->dot g 0 "") 1167 | "}")) 1168 | ([g id dot] 1169 | (let [n (get-node-for-id g id) 1170 | t (.-triple ^TripleNode n) 1171 | d (format 1172 | "%d[label=\"%d: %s\",color=%s];\n" 1173 | id id (pr-str t) 1174 | (if (is-leaf? n) "red" "grey"))] 1175 | (reduce 1176 | (fn [dot id'] (triple-graph->dot g id' (str dot (format "%d -> %d;\n" id id')))) 1177 | (str dot d) 1178 | (get-children n)))))) 1179 | #+END_SRC 1180 | 1181 | *** Constructors 1182 | #+BEGIN_SRC clojure :noweb-ref ctors 1183 | (defn plain-store 1184 | [& triples] 1185 | (add-triples (PlainMemoryStore. (hash-map) (hash-map) (hash-map) 0) triples)) 1186 | 1187 | (defn plain-store-from-reader 1188 | [triples] (apply plain-store triples)) 1189 | 1190 | (defn alias-store 1191 | [store aliases & triples] 1192 | (add-triples (reduce (partial apply u/union) (AliasMemoryStore. store (u/disjoint-set)) aliases) triples)) 1193 | 1194 | (defn triple-graph 1195 | [] 1196 | (let [root' (TripleNode. root nil)] 1197 | (add-node (TripleGraph. {} {} 0 0) root'))) 1198 | #+END_SRC 1199 | 1200 | ** Multi-store dataset 1201 | 1202 | *** Type implementation 1203 | 1204 | #+BEGIN_SRC clojure :noweb-ref dataset 1205 | (defrecord PlainDataset [models] 1206 | PModelUpdate 1207 | (add-triple [_ s] 1208 | (add-triple _ :default s)) 1209 | (add-triple [_ g s] 1210 | (update-in _ [:models g] add-triple s)) 1211 | (add-triples [_ triples] 1212 | (add-triples _ :default triples)) 1213 | (add-triples [_ g triples] 1214 | (update-in _ [:models g] add-triples triples)) 1215 | (remove-triple [_ s] 1216 | (remove-triple _ :default s)) 1217 | (remove-triple [_ g s] 1218 | (update-in _ [:models g] remove-triple s)) 1219 | (remove-triples [_ triples] 1220 | (remove-triples _ :default triples)) 1221 | (remove-triples [_ g triples] 1222 | (update-in _ [:models g] remove-triples triples)) 1223 | (remove-subject [_ s] 1224 | (remove-subject _ :default s)) 1225 | (remove-subject [_ g s] 1226 | (update-in _ [:models g] remove-subject s)) 1227 | 1228 | PModel 1229 | (subject? [_ x] 1230 | (some #(subject? % x) (vals models))) 1231 | (predicate? [_ x] 1232 | (some #(predicate? % x) (vals models))) 1233 | (object? [_ x] 1234 | (some #(object? % x) (vals models))) 1235 | (indexed? [_ x] 1236 | (some #(indexed? % x) (vals models))) 1237 | (subjects [_] 1238 | (set (mapcat subjects (vals models)))) 1239 | (predicates [_] 1240 | (set (mapcat predicates (vals models)))) 1241 | (objects [_] 1242 | (set (mapcat objects (vals models)))) 1243 | (model-size [_] 1244 | (reduce + (map model-size (vals models)))) 1245 | 1246 | PModelSelect 1247 | (select [_] 1248 | (select _ nil nil nil)) 1249 | (select [_ s p o] 1250 | (mapcat #(select % s p o) (vals models))) 1251 | (select [_ g s p o] 1252 | (if-let [g (models g)] (select g s p o))) 1253 | 1254 | PDataset 1255 | (update-model [_ id m] 1256 | (assoc-in _ [:models id] m)) 1257 | (remove-model [_ id] 1258 | (update-in _ [:models] dissoc id)) 1259 | (get-model [_ id] 1260 | (models id))) 1261 | #+END_SRC 1262 | 1263 | *** Constructor 1264 | 1265 | #+BEGIN_SRC clojure :noweb-ref ctors 1266 | (defn plain-dataset 1267 | [& {:as models}] 1268 | (PlainDataset. (assoc models :default (plain-store)))) 1269 | #+END_SRC 1270 | 1271 | ** Watched model wrapper 1272 | 1273 | #+BEGIN_SRC clojure :noweb-ref watched-model 1274 | (defrecord WatchedModel 1275 | [model pre-hooks post-hooks] 1276 | PModelWatch 1277 | (add-pre-commit-hook 1278 | [_ id hook-fn] 1279 | (assoc-in _ [:pre-hooks id] hook-fn)) 1280 | (remove-pre-commit-hook 1281 | [_ id] 1282 | (update-in _ [:pre-hooks] dissoc id)) 1283 | (add-post-commit-hook 1284 | [_ id hook-fn] 1285 | (assoc-in _ [:post-hooks id] hook-fn)) 1286 | (remove-post-commit-hook 1287 | [_ id] 1288 | (update-in _ [:post-hooks] dissoc id)) 1289 | 1290 | PModel 1291 | (subject? 1292 | [_ x] (subject? model x)) 1293 | (predicate? 1294 | [_ x] (predicate? model x)) 1295 | (object? 1296 | [_ x] (object? model x)) 1297 | (indexed? 1298 | [_ x] (indexed? model x)) 1299 | (subjects 1300 | [_] (subjects model)) 1301 | (predicates 1302 | [_] (predicates model)) 1303 | (objects 1304 | [_] (objects model)) 1305 | (model-size 1306 | [_] (model-size model)) 1307 | 1308 | PModelUpdate 1309 | (add-triple 1310 | [_ t] 1311 | (let [t (reduce-kv 1312 | (fn [t k v] (if-let [t' (v :add model t k)] t' (reduced nil))) 1313 | t pre-hooks)] 1314 | (if-not (seq (apply select model t)) 1315 | (let [m' (add-triple model t) 1316 | m' (reduce-kv 1317 | (fn [m k v] (if-let [m' (v :add m t k)] m' (reduced nil))) 1318 | m' post-hooks)] 1319 | (if m' (assoc _ :model m') _)) 1320 | _))) 1321 | (add-triples 1322 | [_ triples] 1323 | (reduce add-triple model triples)) 1324 | (remove-triple 1325 | [_ t] 1326 | (let [t (reduce-kv 1327 | (fn [t k v] (if-let [t' (v :remove model t k)] t' (reduced nil))) 1328 | t pre-hooks)] 1329 | (if (seq (apply select model t)) 1330 | (let [m' (remove-triple model t) 1331 | m' (reduce-kv 1332 | (fn [m k v] (if-let [m' (v :remove m t k)] m' (reduced nil))) 1333 | m' post-hooks)] 1334 | (if m' (assoc _ :model m') _)) 1335 | _))) 1336 | (remove-triples 1337 | [_ triples] 1338 | (reduce add-triple model triples)) 1339 | (update-triple 1340 | [_ t t']) 1341 | (remove-subject 1342 | [_ s]) 1343 | 1344 | PModelSelect 1345 | (select 1346 | [_] (select model nil nil nil)) 1347 | (select 1348 | [_ s p o] (select model s p o)) 1349 | (select-with-alts 1350 | [_ s p o] (select-with-alts model s p o))) 1351 | #+END_SRC 1352 | 1353 | *** Constructor 1354 | 1355 | #+BEGIN_SRC clojure :noweb-ref watched-model 1356 | (defn watched-model 1357 | [model] (WatchedModel. model {} {})) 1358 | #+END_SRC 1359 | 1360 | ** Regexp triple search 1361 | 1362 | Whereas =select-with-alts= enables the pre-constrained search of 1363 | triples using sets of possible values for each SPO, the =search= 1364 | function below allows us to specify search patterns using Regular 1365 | Expressions. Different patterns can be given for S, P, O and =nil= 1366 | values indicate a "match-all" wildcard (just as with =select= & 1367 | =select-with-alts=). For example: 1368 | 1369 | #+BEGIN_SRC clojure 1370 | (def g (as-model '[[karsten nick toxi] [mia nickname miaki] [nicolas nic nick]])) 1371 | 1372 | (search g nil #"nick.*" nil) 1373 | ;; => ([karsten nick toxi] [giedre nickname mia]) 1374 | 1375 | ;; [nicolas nic nick] is not matched, since the `nic` pred is misspelled 1376 | #+END_SRC 1377 | 1378 | The actual =search= function is using =select-with-alts= itself and 1379 | acts merely as a pre-processing step to build up value sets based on 1380 | regexp matches. *Note:* Non-string values are cast to strings in order to 1381 | apply the regexps. 1382 | 1383 | #+BEGIN_SRC clojure :noweb-ref search 1384 | #?(:clj (defn regexp? [x] (instance? java.util.regex.Pattern x))) 1385 | 1386 | (defn regexp-matches 1387 | [ds f re] 1388 | ;;(into #{} (filter #(if (string? %) (re-find x %)) (f ds))) 1389 | (into #{} (filter #(re-find re (if (string? %) % (str %))) (f ds)))) 1390 | 1391 | (defn search 1392 | [ds s p o] 1393 | (->> [s p o] 1394 | (map #(if (regexp? %2) (regexp-matches ds % %2) %2) 1395 | [subjects predicates objects]) 1396 | (apply select-with-alts ds))) 1397 | #+END_SRC 1398 | 1399 | ** Clojure collection conversion 1400 | 1401 | Using the =PTripleSeq= and =PModelConvert= protocols defined above, we 1402 | can provide a mechanism to automatically convert Clojure collections 1403 | into triple seqs or a triplestore. Of course, such an approach will 1404 | involve some assumptions about the internal structure of these 1405 | collections, but the implementations below are quite flexible and 1406 | allow for very succinct definitions of graph structures by specifying 1407 | shared subjects, predicates or objects within a given triple pattern. 1408 | The =PModelConvert= simply wraps the =PTripleSeq= implementation and 1409 | produces a new =PlainMemoryStore= with the supplied triples. 1410 | 1411 | *Note:* The triple seqs produced are all lazy (using =mapcat=). 1412 | 1413 | *** Maps 1414 | 1415 | To convert maps into a flat sequence of triples, the converter assumes 1416 | the data layout shown below. Unlike with sequences (discussed next), 1417 | which allow multiple subjects, predicates or objects per pattern, for 1418 | maps we can only support multiple object values for a given pair of 1419 | subject/predicate: 1420 | 1421 | #+BEGIN_SRC clojure 1422 | (as-model 1423 | {:s1 {:p1 [:s2 :s3 :s4] :p2 23} 1424 | :s2 {:p2 "foo"} 1425 | :s3 {:p3 :s1}}) 1426 | 1427 | ;; => [[:s1 :p1 :s2] [:s1 :p1 :s3] [:s1 :p1 :s4] [:s1 :p2 23] ...] 1428 | #+END_SRC 1429 | 1430 | The map converter then simply flattens each SP tuple: 1431 | 1432 | #+BEGIN_SRC clojure :noweb-ref convert 1433 | (defn triple-seq-associative 1434 | "Converts a single nested map into a seq of triples. 1435 | Each key must have another map as value. Toplevel keys become 1436 | subjects, value map keys predicates, inner map values objects. Each 1437 | predicate key can define a seq of values to produce multiple 1438 | triples." 1439 | [coll] 1440 | (mapcat 1441 | (fn [[s v]] 1442 | (mapcat 1443 | (fn [[p o]] 1444 | (if (sequential? o) 1445 | (mapv (fn [o] [s p o]) o) 1446 | [[s p o]])) 1447 | v)) 1448 | coll)) 1449 | #+END_SRC 1450 | 1451 | *** Sequential collections 1452 | 1453 | Clojure sequences offer more flexibility than maps to encode graph 1454 | structures and furthermore can contain maps themselves. The 1455 | conversion supports any mixture of formats shown below by computing 1456 | the cartesian product of each individual pattern to produce a flat 1457 | sequence of triples. If a sequence item is a map, it will be converted 1458 | with the =triple-seq-associative= fn. 1459 | 1460 | #+BEGIN_SRC clojure 1461 | (as-model 1462 | '[[s p o] ;; => [s p o] 1463 | [s p [o1 o2]] ;; => [s p o1] [s p o2] 1464 | [s [p1 p2] o] ;; => [s p1 o] [s p2 o] 1465 | [s [p1 p2] [o1 o2]] ;; => [s p1 o1] [s p2 o1] [s p1 o2] [s p2 o2] 1466 | [[s1 s2] p o] ;; => [s1 p o] [s2 p o] 1467 | [[s1 s2] [p1 p2] o] ;; => [s1 p1 o] [s1 p2 o] [s2 p1 o] [s2 p2 o] 1468 | [[s1 s2] [p1 p2] [o1 o2]]]) ;; => [s1 p1 o1] [s1 p2 o2] [s2 p1 o1] ... 1469 | 1470 | (as-model 1471 | '[{s1 {p1 [o1 o2]} ;; => [s1 p1 o1] [s1 p1 o2] 1472 | s2 {p1 [o3 o4]}} ;; => [s2 p1 o3] [s2 p1 o4] 1473 | {s1 {p2 o1, p3 o5}}]) ;; => [s1 p2 o1] [s1 p3 o5] 1474 | #+END_SRC 1475 | 1476 | #+BEGIN_SRC clojure :noweb-ref convert 1477 | (defn triple-seq-sequential 1478 | [coll] 1479 | (mapcat 1480 | (fn [triple] 1481 | (if (map? triple) 1482 | (triple-seq triple) 1483 | (->> triple 1484 | (map #(if (sequential? %) % [%])) 1485 | (apply d/cartesian-product)))) 1486 | coll)) 1487 | #+END_SRC 1488 | 1489 | *** Protocol implementations 1490 | 1491 | #+BEGIN_SRC clojure :noweb-ref convert 1492 | (extend-protocol PTripleSeq 1493 | #?(:clj clojure.lang.Sequential 1494 | :cljs PersistentVector) 1495 | (triple-seq [_] (triple-seq-sequential _)) 1496 | #?@(:cljs 1497 | [List 1498 | (triple-seq [_] (triple-seq-sequential _)) 1499 | LazySeq 1500 | (triple-seq [_] (triple-seq-sequential _)) 1501 | IndexedSeq 1502 | (triple-seq [_] (triple-seq-sequential _))]) 1503 | #?(:clj clojure.lang.IPersistentMap 1504 | :cljs PersistentHashMap) 1505 | (triple-seq [_] (triple-seq-associative _)) 1506 | #?@(:cljs 1507 | [PersistentArrayMap 1508 | (triple-seq [_] (triple-seq-associative _))])) 1509 | 1510 | (extend-protocol PModelConvert 1511 | #?(:clj clojure.lang.Sequential 1512 | :cljs PersistentVector) 1513 | (as-model [_] (apply plain-store (triple-seq-sequential _))) 1514 | #?@(:cljs 1515 | [List 1516 | (as-model [_] (apply plain-store (triple-seq-sequential _))) 1517 | LazySeq 1518 | (as-model [_] (apply plain-store (triple-seq-sequential _))) 1519 | IndexedSeq 1520 | (as-model [_] (apply plain-store (triple-seq-sequential _)))]) 1521 | #?(:clj clojure.lang.IPersistentMap 1522 | :cljs PersistentHashMap) 1523 | (as-model [_] (apply plain-store (triple-seq-associative _))) 1524 | #?@(:cljs 1525 | [PersistentArrayMap 1526 | (as-model [_] (apply plain-store (triple-seq-associative _)))])) 1527 | #+END_SRC 1528 | 1529 | ** Clojure collection queries (PModelSelect) 1530 | 1531 | #+BEGIN_SRC clojure :noweb-ref coll-select 1532 | (defn select-from-seq 1533 | [_ s p o] 1534 | (-> (if s 1535 | (if p 1536 | (if o 1537 | (fn [[s' p' o']] (and (= s s') (= p p') (= o o'))) 1538 | (fn [[s' p']] (if (= s s') (= p p')))) 1539 | (if o 1540 | (fn [[s' _ o']] (if (= s s') (= o o'))) 1541 | (fn [t] (= s (first t))))) 1542 | (if p 1543 | (if o 1544 | (fn [[_ p' o']] (if (= p p') (= o o'))) 1545 | (fn [t] (= p (nth t 1)))) 1546 | (if o 1547 | (fn [t] (= o (nth t 2))) 1548 | identity))) 1549 | (filter _))) 1550 | 1551 | (extend-protocol PModelSelect 1552 | #?(:clj clojure.lang.Sequential 1553 | :cljs PersistentVector) 1554 | (select 1555 | ([_] (select-from-seq _ nil nil nil)) 1556 | ([_ s p o] (select-from-seq _ s p o))) 1557 | #?@(:cljs 1558 | [List 1559 | (select 1560 | ([_] (select-from-seq _ nil nil nil)) 1561 | ([_ s p o] (select-from-seq _ s p o))) 1562 | LazySeq 1563 | (select 1564 | ([_] (select-from-seq _ nil nil nil)) 1565 | ([_ s p o] (select-from-seq _ s p o))) 1566 | IndexedSeq 1567 | (select 1568 | ([_] (select-from-seq _ nil nil nil)) 1569 | ([_ s p o] (select-from-seq _ s p o)))])) 1570 | #+END_SRC 1571 | 1572 | ** Tagged reader literals 1573 | 1574 | #+BEGIN_SRC clojure :tangle ../babel/src/data_readers.clj 1575 | {trio/Triple thi.ng.trio.core/triple 1576 | trio/MemStore thi.ng.trio.core/plain-store-from-reader} 1577 | #+END_SRC 1578 | 1579 | ** Complete namespace definition 1580 | 1581 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/core.cljc :noweb yes :mkdirp yes :padline no 1582 | (ns thi.ng.trio.core 1583 | (:refer-clojure :exclude [object? indexed?]) 1584 | (:require 1585 | [thi.ng.dstruct.core :as d] 1586 | [thi.ng.dstruct.unionfind :as u] 1587 | [thi.ng.xerror.core :as err] 1588 | [clojure.set :as set] 1589 | #?@(:clj 1590 | [[clojure.pprint] 1591 | [clojure.core.protocols :as cp]]))) 1592 | 1593 | <> 1594 | 1595 | <> 1596 | 1597 | (declare swizzle swizzle-assoc) 1598 | 1599 | <> 1600 | 1601 | <> 1602 | 1603 | <> 1604 | 1605 | <> 1606 | 1607 | <> 1608 | 1609 | <> 1610 | 1611 | <> 1612 | 1613 | <> 1614 | 1615 | <> 1616 | #+END_SRC 1617 | -------------------------------------------------------------------------------- /src/csv.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: "setup.org" 2 | 3 | * Namespace: thi.ng.trio.io.csv 4 | 5 | ** CSV parsing & transforming functions 6 | 7 | #+BEGIN_SRC clojure :noweb-ref parser 8 | #?(:clj 9 | (defn parse-csv-with 10 | "Takes a CSV input and fn. Applies the CSV header row vector to fn which 11 | must return another fn used to convert remaining rows into triples. 12 | This conversion fn takes 2 args, the row index & column vector, 13 | and must return a seq of triples or nil to skip a row. The 2-step 14 | approach is used to allow the converter fn to work with column names 15 | rather than just indices. See `column-mapper` for further details." 16 | ([src f] (parse-csv-with src f {})) 17 | ([src f opts] 18 | (with-open [in (io/reader src)] 19 | (let [[hd & more] (csv/read-csv in) 20 | f (f hd)] 21 | (doall 22 | (mapcat 23 | (fn [[i data]] (f i data)) 24 | (zipmap (range) more)))))))) 25 | 26 | (defn column-mapper 27 | "Takes a map of CSV field names to handler fns and optional `row-handler` 28 | fn and `map-row?` flag, returns a dispatch fn for parse-csv-with, which 29 | then in turn returns a row converter fn and which dispatches each row field 30 | to assigned handlers given. 31 | 32 | A field handler fn takes 4 arguments: the row index, field index, field value 33 | and the complete row, optionally as map with field names as keys 34 | (controlled by `map-row?`, default true). That way each handler has access 35 | to the complete row and can return aggregated data, e.g. a computed 36 | timestamp of a date specified as multiple columns or GPS coordinates of 37 | separate lat/lon values. 38 | 39 | The optional `row-handler` fn takes 2 args: the original row and the seq of 40 | transformed values produced by field handlers. The fn can be used a 41 | post-processing step to further accumulate values and/or as an alternative 42 | to field handlers." 43 | ([columns] (column-mapper columns nil true)) 44 | ([columns row-handler] (column-mapper columns row-handler true)) 45 | ([columns row-handler map-row?] 46 | (fn [header] 47 | (let [handlers (map-indexed #(or (columns %2) (columns %)) header)] 48 | (fn [i row] 49 | (let [rowmap (if map-row? (zipmap header row) row) 50 | transformed (mapcat 51 | (fn [handler [j data]] 52 | (when handler (handler i j data rowmap))) 53 | handlers (map-indexed #(vector % %2) row))] 54 | (if row-handler 55 | (row-handler row transformed) 56 | transformed))))))) 57 | #+END_SRC 58 | 59 | ** Type parsing helpers 60 | 61 | #+BEGIN_SRC clojure :noweb-ref helpers 62 | #?(:clj 63 | (defn numberformat 64 | [fmt] 65 | (let [nf (DecimalFormat/getInstance)] (.applyPattern nf fmt) nf))) 66 | #?(:clj 67 | (defn parse-number 68 | [^DecimalFormat nf ^String x] (.parse nf x))) 69 | 70 | (defn value-map 71 | ([key] (fn [_ _ d _] [{key d}])) 72 | ([key f] (fn [_ _ d _] [{key (f d)}]))) 73 | #+END_SRC 74 | 75 | ** Usage example 76 | 77 | #+BEGIN_SRC clojure 78 | (def cols {"a" (fn [i j d r] [[j d]]) 2 (fn [_ j d r] [[j d]])}) 79 | (def d ((column-mapper cols) ["b" "a" "c"])) 80 | (d 1 ["b" "a" "c"]) 81 | 82 | (let [tpl '[[?p "a" "foaf:Person"] [?p "foaf:knows/foaf:name" ?n]] 83 | tpl (res/resolve-patterns api/default-prefixes nil tpl) 84 | mapper (column-mapper 85 | {} (fn [[p n] _] 86 | (apply-template 87 | tpl {'?p (api/resource p) '?n (api/literal n)})) 88 | false)] 89 | (parse-csv-with "peeps.csv" mapper)) 90 | #+END_SRC 91 | 92 | ** Namespace declaration 93 | 94 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/io/csv.cljc :noweb yes :mkdirp yes :padline no 95 | (ns thi.ng.trio.io.csv 96 | (:require 97 | [thi.ng.trio.core :as api] 98 | [clojure.string :as str] 99 | #?@(:clj [[clojure.java.io :as io] 100 | [clojure.data.csv :as csv]])) 101 | #?(:clj (:import java.text.DecimalFormat))) 102 | 103 | <> 104 | 105 | <> 106 | #+END_SRC 107 | -------------------------------------------------------------------------------- /src/entities.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | 3 | * Contents :toc_3_gh: 4 | - [[#namespace-thingtrioentities][Namespace: thi.ng.trio.entities]] 5 | - [[#defentity-macro][defentity macro]] 6 | - [[#example][Example]] 7 | - [[#generated-results][Generated results]] 8 | - [[#field-specification-options][Field specification options]] 9 | - [[#initializing-order][Initializing order]] 10 | - [[#implementation][Implementation]] 11 | - [[#helper-functions][Helper functions]] 12 | - [[#complete-namespace-definitions][Complete namespace definitions]] 13 | 14 | * Namespace: thi.ng.trio.entities 15 | 16 | ** defentity macro 17 | 18 | The =defentity= macro shown below provides a mechanism to define 19 | graph-based entities as =defrecord= instances and supplements these 20 | with various other options, like defaults, initializers & validation. 21 | We first specify an example entity and then discuss each element in 22 | detail: 23 | 24 | *** Example 25 | 26 | This entity defines a hypothetical user account with 6 properties and 27 | their constraints and serialization mappings when the entity is 28 | represented/serialized as triples. 29 | 30 | #+BEGIN_SRC clojure 31 | (defentity User "foaf:Agent" 32 | {:user-name {:prop "foaf:nick" 33 | :validate [(v/string) (v/max-length 32)]} 34 | :name {:prop "foaf:name" 35 | :validate [(v/string) (v/max-length 64)] 36 | :optional true} 37 | :mbox {:prop "foaf:mbox" 38 | :validate [(v/mailto)] 39 | :optional true} 40 | :homepage {:prop "foaf:homepage" 41 | :validate [(v/url)] 42 | :card :* 43 | :order :asc 44 | :optional true} 45 | :password {:prop "imago:passwordSha256Hash" 46 | :validate [(v/string) (v/fixed-length 64)] 47 | :init (fn [{:keys [user-name password]}] 48 | (if (== 64 (count password)) 49 | password 50 | (utils/sha-256 user-name password salt))) 51 | :optional true} 52 | :created {:prop "dcterms:created" 53 | :validate [(v/number) (v/pos)] 54 | :default (fn [_] (utils/timestamp))}}) 55 | #+END_SRC 56 | 57 | *** Generated results 58 | 59 | The above call to =defentity= will result in the following: 60 | 61 | - a new a new =defrecord= called =User= (defined in the current ns), 62 | which implements the =trio.core/PTripleSeq= protocol 63 | - a factory function called =make-user=, which should be used in place 64 | of Clojure's auto-generated =map->User= fn. This ctor also takes a 65 | single map of args, but first transforms it with the initializers & 66 | default value options specified and then also validates all fields 67 | before constructing the =User= instance 68 | - a function called =describe-user=, which executes a =:describe= 69 | query for the given entity ID and returns a seq of triples 70 | associated with this entity 71 | - a factory function called =describe-as-user=, which is based on 72 | =describe-user=, but parses the found triples and constructs a 73 | =User= instance from these. 74 | 75 | *Note:* Doc-strings are generated for all factory fns. 76 | 77 | *** Field specification options 78 | 79 | | *Key* | *Description* | *Optional?* | 80 | |-----------+-------------------------------------------------------------------------+-------------| 81 | | :prop | Value of the field's predicate when (de)serializing to/from triples | N | 82 | | :validate | Vector of validation fns to apply for this field | Y | 83 | | :init | Custom field initializer/transformer fn | Y | 84 | | :default | Custom field default value generator fn | Y | 85 | | :optional | true, if field does not need to be specified during construction | Y | 86 | | :card | field cardinality, currently only =:*= supported (assumes 1 if missing) | Y | 87 | 88 | **** :prop 89 | **** :validate 90 | **** :init 91 | **** :default 92 | **** :optional 93 | **** :card 94 | 95 | *** Initializing order 96 | 97 | 1. Inject =:id= & =:type= props in arg map 98 | 2. Apply specified & auto-generated initializers 99 | 3. Inject default values for missing props 100 | 4. Apply validators 101 | 5. Construct entity =defrecord= instance 102 | 103 | *** Implementation 104 | 105 | #+BEGIN_SRC clojure :noweb-ref macros 106 | (defmacro defentity 107 | [name type props] 108 | (let [->sym (comp symbol clojure.core/name) 109 | props (merge {:type {:prop (:type rdf)}} props) 110 | fields (cons 'id (map ->sym (keys props))) 111 | ctor-name (utils/->kebab-case name) 112 | ctor (symbol (str 'make- ctor-name)) 113 | dctor (symbol (str 'describe- ctor-name)) 114 | dctor-as (symbol (str 'describe-as- ctor-name)) 115 | mctor (symbol (str 'map-> name)) 116 | wrap-props (symbol (str ctor-name '-props))] 117 | `(let [props# ~props 118 | type# ~type 119 | type-prop# (-> props# :type :prop) 120 | validators# (eu/build-validators props#) 121 | inits# (eu/build-initializers props#) 122 | defaults# (eu/build-defaults props#)] 123 | ;;(prn "-------- " ~type) 124 | ;;(prn :props ~props) 125 | ;;(prn :fields ~fields) 126 | ;;(prn :validators validators#) 127 | ;;(prn :inits inits#) 128 | ;;(prn :defaults defaults#) 129 | 130 | (defn ~wrap-props [] props#) 131 | 132 | (defrecord ~name [~@fields] 133 | api/PTripleSeq 134 | (~'triple-seq 135 | [_#] (eu/filtered-triple-seq {~'id (eu/triple-map (~wrap-props) _#)}))) 136 | 137 | (defn ~ctor 138 | {:doc ~(str "Constructs a new `" (namespace-munge *ns*) "." `~name "` entity from given map.\n" 139 | " Applies entity's property intializers, defaults & validation.\n" 140 | " In case of validation errors, throws map of errors using `slingshot/throw+`.") 141 | :arglists '([~'props])} 142 | [opts#] 143 | (let [[opts# err#] 144 | (-> opts# 145 | (assoc :id (or (:id opts#) (utils/new-uuid))) 146 | (assoc :type (or (:type opts#) type#)) 147 | (eu/apply-initializers inits#) 148 | (eu/inject-defaults defaults#) 149 | (v/validate validators#))] 150 | (if (nil? err#) 151 | (~mctor opts#) 152 | (err/illegal-arg! (pr-str err#))))) 153 | 154 | (defn ~dctor 155 | {:doc ~(str "Executes a :describe query for given entity ID in graph.\n" 156 | " Returns seq of triples. ") 157 | :arglists '([~'graph ~'id])} 158 | [g# id#] 159 | (q/query 160 | {:describe '~'?id 161 | :from g# 162 | :query [{:where [['~'?id type-prop# type#]]}] 163 | :values {'~'?id #{id#}}})) 164 | 165 | (defn ~dctor-as 166 | {:doc ~(str "Constructs a new `" (namespace-munge *ns*) "." `~name "` entity based on a graph query\n" 167 | " for given entity ID and its specified props/rels (any additional rels will be included\n" 168 | " too, but are not validated during construction). If returned query results conflict\n" 169 | " with entity validation, throws map of errors using `slingshot/throw+`.") 170 | :arglists '([~'graph ~'id])} 171 | [g# id#] 172 | (-> (~dctor g# id#) 173 | (eu/entity-map-from-triples props# id#) 174 | (~ctor)))))) 175 | #+END_SRC 176 | 177 | ** Helper functions 178 | 179 | #+BEGIN_SRC clojure :noweb-ref helpers 180 | (defn filtered-triple-seq 181 | [xs] (->> xs (api/triple-seq) (filter #(-> % last nil? not)))) 182 | 183 | (defn triple-map 184 | [props rec] 185 | (->> props 186 | (map 187 | (fn [[k f]] 188 | [(:prop f) 189 | (if-let [ser (:values f)] 190 | (ser (k rec)) 191 | (k rec))])) 192 | (into {}) 193 | (merge (->> props keys (apply dissoc rec :id) keys (select-keys rec))))) 194 | 195 | (defn pick-id 196 | [id] (fn [x] (let [k (id x)] (if (map? k) (:id k) k)))) 197 | 198 | (defn pick-id-coll 199 | [id] 200 | (fn [x] 201 | (let [k (id x) 202 | k (if (or (string? k) (number? k) (map? k)) [k] k)] 203 | (mapv #(if (map? %) (:id %) %) k)))) 204 | 205 | (defn wrap-optional [vals] (mapv #(v/optional %) vals)) 206 | 207 | (defn build-validators 208 | [props] 209 | (reduce-kv 210 | (fn [acc k {:keys [validate optional card]}] 211 | (if validate 212 | (let [v (if (and optional (not (map? validate))) 213 | (wrap-optional validate) 214 | validate)] 215 | (assoc acc k (if (and (= card :*) (not (map? v))) {:* v} v))) 216 | acc)) 217 | {} props)) 218 | 219 | (defn build-sorter 220 | [id dir] 221 | (fn [props] 222 | (let [p (props id)] 223 | (if (coll? p) 224 | (vec (if (= :asc dir) (sort p) (reverse (sort p)))) 225 | p)))) 226 | 227 | (defn build-initializers 228 | [props] 229 | (reduce-kv 230 | (fn [acc k {:keys [init card order]}] 231 | (let [ordered (if order (build-sorter k order))] 232 | (cond 233 | init (assoc acc k (if order (fn [_] (-> _ order init)) init)) 234 | ordered (assoc acc k ordered) 235 | (= :* card) (assoc acc k (pick-id-coll k)) 236 | :else acc))) 237 | {} props)) 238 | 239 | (defn apply-initializers 240 | [props inits] 241 | (reduce-kv 242 | (fn [acc k v] (if (acc k) (assoc acc k (v acc)) acc)) 243 | props inits)) 244 | 245 | (defn build-defaults 246 | [props] 247 | (reduce-kv 248 | (fn [acc k {:keys [card default]}] 249 | (cond 250 | default (assoc acc k default) 251 | (= :* card) (assoc acc k (fn [_] [])) 252 | :else acc)) 253 | {} props)) 254 | 255 | (defn inject-defaults 256 | [props defaults] 257 | (reduce-kv 258 | (fn [acc k v] (if (nil? (acc k)) (assoc acc k (if (fn? v) (v acc) v)) acc)) 259 | props defaults)) 260 | 261 | (defn entity-map-from-triples 262 | [triples props id] 263 | (let [iprop (reduce-kv (fn [acc k v] (assoc acc (:prop v) k)) {} props) 264 | conj* (fnil conj [])] 265 | (->> triples 266 | (filter #(= id (:s %))) 267 | (reduce 268 | (fn [acc [_ p o]] 269 | (let [p' (iprop p p) 270 | op (props p')] 271 | (if (and op (-> op :card (not= :*))) 272 | (assoc acc p' o) 273 | (update-in acc [p'] conj* o)))) 274 | {:id id})))) 275 | #+END_SRC 276 | 277 | ** Complete namespace definitions 278 | 279 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/entities.clj :noweb yes :mkdirp yes :padline no 280 | (ns thi.ng.trio.entities 281 | (:require 282 | [thi.ng.trio.core :as api] 283 | [thi.ng.trio.query :as q] 284 | [thi.ng.trio.utils :as utils] 285 | [thi.ng.trio.entities.utils :as eu] 286 | [thi.ng.trio.vocabs.rdf :refer [rdf]] 287 | [thi.ng.validate.core :as v] 288 | [thi.ng.xerror.core :as err])) 289 | 290 | <> 291 | #+END_SRC 292 | 293 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/entities/utils.cljc :noweb yes :mkdirp yes :padline no 294 | (ns thi.ng.trio.entities.utils 295 | (:require 296 | [thi.ng.trio.core :as api] 297 | [thi.ng.validate.core :as v])) 298 | 299 | <> 300 | #+END_SRC 301 | -------------------------------------------------------------------------------- /src/index.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | #+TITLE: thi.ng/trio 3 | 4 | * Contents :toc_3_gh: 5 | - [[#about-the-project][About the project]] 6 | - [[#overview][Overview]] 7 | - [[#status][Status]] 8 | - [[#example-usage][Example usage]] 9 | - [[#namespaces][Namespaces]] 10 | - [[#tests][Tests]] 11 | - [[#project-definition][Project definition]] 12 | - [[#injected-properties][Injected properties]] 13 | - [[#dependencies][Dependencies]] 14 | - [[#runtime][Runtime]] 15 | - [[#development][Development]] 16 | - [[#leiningen-coordinates][Leiningen coordinates]] 17 | - [[#building-this-project][Building this project]] 18 | - [[#testing][Testing]] 19 | - [[#working-with-the-repl][Working with the REPL]] 20 | - [[#leiningen-project-file][Leiningen project file]] 21 | - [[#clojurescript-html-harness][ClojureScript HTML harness]] 22 | - [[#accessing-library-version-during-runtime][Accessing library version during runtime]] 23 | - [[#version-namespace][Version namespace]] 24 | - [[#release-history][Release history]] 25 | - [[#contributors][Contributors]] 26 | - [[#license][License]] 27 | 28 | * About the project 29 | 30 | ** Overview 31 | 32 | This project provides a simple, yet extensible and datatype agnostic 33 | [[http://en.wikipedia.org/wiki/Triplestore][triplestore]] API and powerful [[http://en.wikipedia.org/wiki/SPARQL][SPARQL]]-like query engine for 34 | Clojure/ClojureScript. This library is the result of extracting & 35 | refactoring non-RDF related functionality from the upcoming 36 | *thi.ng/sema* toolkit and so making the underlying graph tools more 37 | accessible to a wider audience. The library is developed in a literal 38 | programming format and contains many examples and extensive 39 | documentation for all parts. A test suite is being worked on too. 40 | 41 | Triple stores are usually associated with [[http://en.wikipedia.org/wiki/Semantic_web][Semantic Web]] & [[http://en.wikipedia.org/wiki/Linked_Data][Linked Data]] 42 | applications, however encoding data as triples is natural & useful too 43 | for a large number of other use cases and this library is aiming to 44 | address some of these. Although currently only a couple of in-memory 45 | stores are available, the intention is to eventually offer a pluggable 46 | storage layer (a bit like Datomic). Some prototype implementations for 47 | Redis, CouchDB & Cassandra have been in development since last year 48 | (but currently stalled due to other priorities). The aim is to develop 49 | & bundle these in isolation as separate support libs for trio. 50 | 51 | ** Status 52 | 53 | ALPHA quality, subject to frequent changes until further notice. 54 | 55 | ** Example usage 56 | 57 | The [[query.org][query engine namespace]] and growing [[../test/][test suite]] is currently the 58 | best source of information and concrete usage examples. Please take a 59 | look! 60 | 61 | * Namespaces 62 | 63 | - [[core.org][thi.ng.trio.core]] 64 | - [[query.org][thi.ng.trio.query]] 65 | 66 | **** Non-complete / in development 67 | 68 | - [[./entities.org][thi.ng.trio.entities]] (schema based entities w/ triple serialization) 69 | - [[./vocabs.org][thi.ng.trio.vocabs]] (optional RDF related vocab stubs) 70 | - [[csv.org][thi.ng.trio.csv]] (CSV -> triple/graph mapping) 71 | - [[./inference.org][thi.ng.trio.inference]] (rule based inferencing) 72 | 73 | * Tests 74 | 75 | - [[../test/core.org][thi.ng.trio.test.core]] 76 | - [[../test/query.org][thi.ng.trio.test.query]] 77 | 78 | * Project definition 79 | 80 | ** Injected properties :noexport: 81 | 82 | #+BEGIN_SRC clojure :noweb-ref version 83 | 0.2.0-SNAPSHOT 84 | #+END_SRC 85 | #+BEGIN_SRC clojure :exports none :noweb-ref project-url 86 | http://thi.ng/trio 87 | #+END_SRC 88 | #+BEGIN_SRC clojure :exports none :noweb yes :noweb-ref cljs-artefact-path 89 | target/trio-<>.js 90 | #+END_SRC 91 | 92 | ** Dependencies 93 | *** Runtime 94 | **** [[https://github.com/clojure/clojure][Clojure]] 95 | #+BEGIN_SRC clojure :noweb-ref dep-clj 96 | [org.clojure/clojure "1.7.0"] 97 | #+END_SRC 98 | 99 | **** [[https://github.com/clojure/clojurescript][ClojureScript]] 100 | #+BEGIN_SRC clojure :noweb-ref dep-cljs 101 | [org.clojure/clojurescript "0.0-3308"] 102 | #+END_SRC 103 | 104 | **** [[https://github.com/thi-ng/dstruct][thi.ng/dstruct]] 105 | #+NAME: dep-dstruct 106 | #+BEGIN_SRC clojure 107 | [thi.ng/dstruct "0.1.0"] 108 | #+END_SRC 109 | 110 | **** [[https://github.com/thi-ng/math][thi.ng/math]] 111 | #+NAME: dep-math 112 | #+BEGIN_SRC clojure 113 | [thi.ng/math "0.1.4"] 114 | #+END_SRC 115 | 116 | **** [[https://github.com/thi-ng/strf][thi.ng/strf]] 117 | #+NAME: dep-strf 118 | #+BEGIN_SRC clojure 119 | [thi.ng/strf "0.1.0"] 120 | #+END_SRC 121 | 122 | **** [[https://github.com/thi-ng/validate/][thi.ng/validate]] 123 | #+BEGIN_SRC clojure :noweb-ref dep-validate 124 | [thi.ng/validate "0.1.3"] 125 | #+END_SRC 126 | 127 | **** [[https://github.com/thi-ng/xerror][thi.ng/xerror]] 128 | #+NAME: dep-xerror 129 | #+BEGIN_SRC clojure 130 | [thi.ng/xerror "0.1.0"] 131 | #+END_SRC 132 | 133 | **** [[https://github.com/clojure/data.csv][data.csv]] 134 | #+BEGIN_SRC clojure :noweb-ref dep-csv 135 | [org.clojure/data.csv "0.1.2"] 136 | #+END_SRC 137 | 138 | **** [[https://github.com/ptaoussanis/timbre][timbre]] 139 | #+BEGIN_SRC clojure :noweb-ref dep-timbre 140 | [com.taoensso/timbre "3.3.1"] 141 | #+END_SRC 142 | 143 | **** [[https://github.com/postspectacular/cljs-log][cljs-log]] 144 | #+BEGIN_SRC clojure :noweb-ref dep-cljs-log 145 | [cljs-log "0.2.1"] 146 | #+END_SRC 147 | 148 | *** Development 149 | **** [[https://github.com/hugoduncan/criterium][Criterium]] 150 | #+BEGIN_SRC clojure :noweb-ref dep-criterium 151 | [criterium "0.4.3"] 152 | #+END_SRC 153 | 154 | **** [[https://github.com/cemerick/clojurescript.test][clojurescript.test]] 155 | #+BEGIN_SRC clojure :noweb-ref dep-cljs-test 156 | [com.cemerick/clojurescript.test "0.3.3"] 157 | #+END_SRC 158 | 159 | **** [[https://github.com/emezeske/lein-cljsbuild][Cljsbuild]] 160 | #+BEGIN_SRC clojure :noweb-ref dep-cljsbuild 161 | [lein-cljsbuild "1.0.6"] 162 | #+END_SRC 163 | 164 | ** Leiningen coordinates 165 | 166 | #+BEGIN_SRC clojure :noweb yes :noweb-ref lein-coords 167 | [thi.ng/trio "0.2.0-SNAPSHOT"] 168 | #+END_SRC 169 | 170 | ** Building this project 171 | 172 | This project is written in a literate programming format and requires 173 | [[https://www.gnu.org/software/emacs/][Emacs]] & [[http://orgmode.org][Org-mode]] to generate usable source code. Assuming 174 | both tools are installed, the easiest way to generate a working 175 | project is via command line (make sure =emacs= is on your path or else 176 | edit its path in =tangle.sh=): 177 | 178 | #+BEGIN_SRC bash 179 | git clone https://github.com/thi.ng/trio.git 180 | cd trio 181 | ./tangle.sh src/*.org test/*.org 182 | #+END_SRC 183 | 184 | Tangling is the process of extracting & combining source blocks from 185 | =.org= files into an actual working project/source tree. Once tangling 186 | is complete, you can =cd= into the generated project directory 187 | (=babel=) and then use =lein= as usual. 188 | 189 | *** Testing 190 | 191 | The =project.clj= file defines an alias to trigger a complete build & 192 | tests for both CLJ & CLJS versions. 193 | 194 | #+BEGIN_SRC bash 195 | cd babel 196 | lein cleantest 197 | #+END_SRC 198 | 199 | To build the Clojurescript version simply run =lein cljsbuild test= 200 | from the same directory. A small HTML harness for the resulting JS 201 | file is also located in that folder (=babel/index.html=), allowing for 202 | further experimentation in the browser. 203 | 204 | *** Working with the REPL 205 | 206 | Editing code blocks or files in Org-mode, then re-loading & testing 207 | changes is quite trivial. Simply launch a REPL (via =lein= or Emacs) 208 | as usual. Everytime you've made changes to an =.org= file, re-tangle 209 | it from Emacs (=C-c C-v t=) or =tangle.sh=, then reload the namespace 210 | in the REPL via =(require 'thi.ng.trio... :reload)= or similar. 211 | 212 | ** Leiningen project file :noexport: 213 | 214 | #+BEGIN_SRC clojure :tangle ../babel/project.clj :noweb yes :mkdirp yes :padline no 215 | (defproject thi.ng/trio "<>" 216 | :description "Generic triple store API & SPARQL-like query engine" 217 | :url "<>" 218 | :license {:name "Apache Software License 2.0" 219 | :url "http://www.apache.org/licenses/LICENSE-2.0" 220 | :distribution :repo} 221 | :scm {:name "git" 222 | :url "git@github.com:thi-ng/trio.git"} 223 | 224 | :min-lein-vesion "2.4.0" 225 | 226 | :dependencies [<> 227 | <> 228 | <> 229 | <> 230 | <> 231 | <> 232 | <> 233 | <> 234 | <> 235 | <>] 236 | 237 | :profiles {:dev {:dependencies [<>] 238 | :plugins [<> 239 | <>] 240 | :global-vars {*warn-on-reflection* true} 241 | :jvm-opts ^:replace [] 242 | :aliases {"cleantest" ["do" "clean," "test," "cljsbuild" "test"]}}} 243 | 244 | :cljsbuild {:builds [{:id "simple" 245 | :source-paths ["src" "test"] 246 | :compiler {:output-to "<>" 247 | :optimizations :whitespace 248 | :pretty-print true}}] 249 | :test-commands {"unit-tests" ["phantomjs" :runner "<>"]}} 250 | 251 | :pom-addition [:developers [:developer 252 | [:name "Karsten Schmidt"] 253 | [:url "http://postspectacular.com"] 254 | [:timezone "0"]]]) 255 | #+END_SRC 256 | 257 | ** ClojureScript HTML harness :noexport: 258 | 259 | #+BEGIN_SRC html :tangle ../babel/index.html :noweb yes :mkdirp yes :padline no 260 | 261 | 262 | 263 | <<lein-coords>> test 264 | 265 | 266 | 267 | 268 | 269 | #+END_SRC 270 | 271 | ** Accessing library version during runtime 272 | 273 | The autogenerated namespace =thi.ng.trio.version= contains a single 274 | symbol =version= holding the version string defined above: 275 | 276 | #+BEGIN_SRC clojure :noweb yes 277 | (use '[thi.ng.trio.version]) 278 | 279 | (prn version) 280 | ; "<>" 281 | #+END_SRC 282 | 283 | *** Version namespace :noexport: 284 | 285 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/version.cljc :noweb yes :mkdirp yes :padline no :exports none 286 | (ns thi.ng.trio.version) 287 | 288 | (def version "<>") 289 | #+END_SRC 290 | 291 | ** Release history 292 | 293 | | *Version* | *Released* | *Description* | *Lein coordinates* | *Tagged Github URL* | 294 | |----------------+------------+----------------------+----------------------------------+---------------------| 295 | | 0.2.0-SNAPSHOT | n/a | CLJ1.7.0 refactoring | =[thi.ng/trio "0.2.0-SNAPSHOT"]= | | 296 | | 0.1.0 | 2015-02-25 | 1st public release | =[thi.ng/trio "0.1.0"]= | [[https://github.com/thi-ng/trio/tree/0.1.0][0.1.0]] | 297 | 298 | ** Contributors 299 | 300 | | *Name* | *Role* | *Website* | 301 | |-----------------+---------------------------------+----------------------------| 302 | | [[k@thi.ng][Karsten Schmidt]] | initiator & principal developer | http://postspectacular.com | 303 | 304 | I've got a fairly detailed roadmap and task list to implement over the 305 | coming months, but am always happy to receive feedback & suggestions 306 | and have issues filed. Once the core engine is more refined I'll be 307 | gladly welcoming other contributions. Thanks for understanding! 308 | 309 | ** License 310 | 311 | This project is open source and licensed under the [[http://www.apache.org/licenses/LICENSE-2.0][Apache Software License 2.0]]. 312 | -------------------------------------------------------------------------------- /src/inference.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | 3 | * Contents :toc_3_gh: 4 | - [[#rulebased-inferencing-owl2-rl-subset][Rulebased inferencing (OWL2-RL subset)]] 5 | - [[#rules][Rules]] 6 | - [[#property-rules][Property rules]] 7 | - [[#class-rules][Class rules]] 8 | - [[#class-semantics][Class semantics]] 9 | - [[#semantics-of-schema-vocabulary][Semantics of Schema Vocabulary]] 10 | - [[#inferencer][Inferencer]] 11 | - [[#inf-model][Inf model]] 12 | - [[#complete-namespace-definition][Complete namespace definition]] 13 | 14 | * Rulebased inferencing (OWL2-RL subset) 15 | 16 | - http://www.w3.org/TR/owl2-profiles/#OWL_2_RL 17 | 18 | ** Rules 19 | *** Property rules 20 | 21 | #+BEGIN_SRC clojure :noweb-ref rules 22 | (def prop-rules 23 | {:prp-dom {:q [{:where [['?p (:domain rdfs) '?c] 24 | ['?x '?p '?y]]}] 25 | :c [['?x (:type rdf) '?c]]} 26 | 27 | :prp-rng {:q [{:where [['?p (:range rdfs) '?c] 28 | ['?x '?p '?y]]}] 29 | :c [['?y (:type rdf) '?c]]} 30 | 31 | :prp-fp {:q [{:where [['?p (:type rdf) (:FunctionalProperty owl)] 32 | ['?x '?p '?y1] 33 | ['?x '?p '?y2]]}] 34 | :c [['?y1 (:sameAs owl) '?y2]]} 35 | 36 | :prp-ifp {:q [{:where [['?p (:type rdf) (:InverseFunctionalProperty owl)] 37 | ['?x1 '?p '?y] 38 | ['?x2 '?p '?y]]}] 39 | :c [['?x1 (:sameAs owl) '?x2]]} 40 | 41 | :prp-irp {:q [{:where [['?p (:type rdf) (:IrreflexiveProperty owl)] 42 | ['?x '?p '?x]]}] 43 | :c false} 44 | 45 | :prp-symp {:q [{:where [['?p (:type rdf) (:SymmetricProperty owl)] 46 | ['?x '?p '?y]]}] 47 | :c [['?y '?p '?x]]} 48 | 49 | :prp-asyp {:q [{:where [['?p (:type rdf) (:AsymmetricProperty owl)] 50 | ['?x '?p '?y] 51 | ['?y '?p '?x]]}] 52 | :c false} 53 | 54 | :prp-trp {:q [{:where [['?p (:type rdf) (:TransitiveProperty owl)] 55 | ['?x '?p '?y] 56 | ['?y '?p '?z]]}] 57 | :c [['?x '?p '?z]]} 58 | 59 | :prp-spo1 {:q [{:where [['?p1 (:subPropertyOf rdfs) '?p2] 60 | ['?x '?p1 '?y]]}] 61 | :c [['?x '?p2 '?y]]} 62 | 63 | ;; :prp-spo2 nil ;; TODO requires property chains 64 | 65 | :prp-eqp1 {:q [{:where [['?p1 (:equivalentProperty owl) '?p2] 66 | ['?x '?p1 '?y]]}] 67 | :c [['?x '?p2 '?y]]} 68 | 69 | :prp-eqp2 {:q [{:where [['?p1 (:equivalentProperty owl) '?p2] 70 | ['?x '?p2 '?y]]}] 71 | :c [['?x '?p1 '?y]]} 72 | 73 | :prp-pdw {:q [{:where [['?p1 (:propertyDisjointWith owl) '?p2] 74 | ['?x '?p1 '?y] 75 | ['?x '?p2 '?y]]}] 76 | :c false} 77 | 78 | ;; :prp-adp nil ;; TODO required property chains 79 | 80 | :prp-inv1 {:q [{:where [['?p1 (:inverseOf owl) '?p2] 81 | ['?x '?p1 '?y]]}] 82 | :c [['?y '?p2 '?x]]} 83 | 84 | :prp-inv2 {:q [{:where [['?p1 (:inverseOf owl) '?p2] 85 | ['?x '?p2 '?y]]}] 86 | :c [['?y '?p1 '?x]]} 87 | 88 | ;; :prp-key nil ;; TODO required property chains 89 | 90 | :prp-npa1 {:q [{:where [['?x (:sourceIndividual owl) '?i1] 91 | ['?x (:assertionProperty owl) '?p] 92 | ['?x (:targetIndividual owl) '?i2] 93 | ['?i1 '?p '?i2]]}] 94 | :c false} 95 | 96 | :prp-npa2 {:q [{:where [['?x (:sourceIndividual owl) '?i] 97 | ['?x (:assertionProperty owl) '?p] 98 | ['?x (:targetValue owl) '?lt] 99 | ['?i '?p '?lt]]}] 100 | :c false} 101 | }) 102 | 103 | #+END_SRC 104 | 105 | *** Class rules 106 | 107 | #+BEGIN_SRC clojure :noweb-ref rules 108 | (def class-rules 109 | {:cls-thing {:q true 110 | :c [[(:Thing owl) (:type rdf) (:Class owl)]]} 111 | 112 | :cls-nothing1 {:q true 113 | :c [[(:Nothing owl) (:type rdf) (:Class owl)]]} 114 | 115 | :cls-nothing2 {:q [{:where [['?x (:type rdf) (:Nothing owl)]]}] 116 | :c false} 117 | 118 | ;; :cls-int1 nil ;; TODO 119 | 120 | ;; :cls-int2 nil ;; TODO 121 | 122 | ;; :cls-uni nil ;; TODO 123 | 124 | :cls-com {:q [{:where [['?c1 (:complementOf owl) '?c2] 125 | ['?x (:type rdf) '?c1]]}] 126 | :c [['?x (:type rdf) '?c2]]} 127 | 128 | :cls-svf1 {:q [{:where [['?x (:someValuesFrom owl) '?y] 129 | ['?x (:onProperty owl) '?p] 130 | ['?u '?p '?v] 131 | ['?v (:type rdf) '?y]]}] 132 | :c [['?u (:type rdf) '?x]]} 133 | 134 | :cls-svf2 {:q [{:where [['?x (:someValuesFrom owl) (:Thing owl)] 135 | ['?x (:onProperty owl) '?p] 136 | ['?u '?p '?v]]}] 137 | :c [['?u (:type rdf) '?x]]} 138 | 139 | :cls-avf {:q [{:where [['?x (:allValuesFrom owl) '?y] 140 | ['?x (:onProperty owl) '?p] 141 | ['?u (:type rdf) '?x] 142 | ['?u '?p '?v]]}] 143 | :c [['?v (:type rdf) '?y]]} 144 | 145 | :cls-hv1 {:q [{:where [['?x (:hasValue owl) '?y] 146 | ['?x (:onProperty owl) '?p] 147 | ['?u (:type rdf) '?x]]}] 148 | :c [['?u '?p '?y]]} 149 | 150 | :cls-hv2 {:q [{:where [['?x (:hasValue owl) '?y] 151 | ['?x (:onProperty owl) '?p] 152 | ['?u '?p '?y]]}] 153 | :c [['?u (:type rdf) '?x]]} 154 | 155 | :cls-maxc1 {:q [{:where [['?x (:maxCardinality owl) 0] 156 | ['?x (:onProperty owl) '?p] 157 | ['?u (:type rdf) '?x] 158 | ['?u '?p '?y]]}] 159 | :c false} 160 | 161 | :cls-maxc2 {:q [{:where [['?x (:maxCardinality owl) 1] 162 | ['?x (:onProperty owl) '?p] 163 | ['?u (:type rdf) '?x] 164 | ['?u '?p '?y1] 165 | ['?u '?p '?y2]]}] 166 | :c [['?y1 (:sameAs owl) '?y2]]} 167 | 168 | :cls-maxqc1 {:q [{:where [['?x (:maxQualifiedCardinality owl) 0] 169 | ['?x (:onProperty owl) '?p] 170 | ['?x (:onClass owl) '?c] 171 | ['?u (:type rdf) '?x] 172 | ['?u '?p '?y] 173 | ['?y (:type rdf) '?c]]}] 174 | :c false} 175 | 176 | :cls-maxqc2 {:q [{:where [['?x (:maxQualifiedCardinality owl) 0] 177 | ['?x (:onProperty owl) '?p] 178 | ['?x (:onClass owl) (:Thing owl)] 179 | ['?u (:type rdf) '?x] 180 | ['?u '?p '?y]]}] 181 | :c false} 182 | 183 | :cls-maxqc3 {:q [{:where [['?x (:maxQualifiedCardinality owl) 1] 184 | ['?x (:onProperty owl) '?p] 185 | ['?x (:onClass owl) '?c] 186 | ['?u (:type rdf) '?x] 187 | ['?u '?p '?y1] 188 | ['?y1 (:type rdf) '?c] 189 | ['?u '?p '?y2] 190 | ['?y2 (:type rdf) '?c]]}] 191 | :c [['?y1 (:sameAs owl) '?y2]]} 192 | 193 | :cls-maxqc4 {:q [{:where [['?x (:maxQualifiedCardinality owl) 1] 194 | ['?x (:onProperty owl) '?p] 195 | ['?x (:onClass owl) (:Thing owl)] 196 | ['?u (:type rdf) '?x] 197 | ['?u '?p '?y1] 198 | ['?u '?p '?y2]]}] 199 | :c [['?y1 (:sameAs owl) '?y2]]} 200 | 201 | ;; :cls-oo nil ;; TODO 202 | }) 203 | 204 | #+END_SRC 205 | 206 | *** Class semantics 207 | 208 | #+BEGIN_SRC clojure :noweb-ref rules 209 | (def class-semantics 210 | {:cax-sco {:q [{:where [['?c1 (:subClassOf rdfs) '?c2] 211 | ['?x (:type rdf) '?c1]]}] 212 | :c [['?x (:type rdf) '?c2]]} 213 | 214 | :cax-eqc1 {:q [{:where [['?c1 (:equivalentClass owl) '?c2] 215 | ['?x (:type rdf) '?c1]]}] 216 | :c [['?x (:type rdf) '?c2]]} 217 | 218 | :cax-eqc2 {:q [{:where [['?c1 (:equivalentClass owl) '?c2] 219 | ['?x (:type rdf) '?c2]]}] 220 | :c [['?x (:type rdf) '?c1]]} 221 | 222 | :cax-dw {:q [{:where [['?c1 (:disjointWith owl) '?c2] 223 | ['?x (:type rdf) '?c1] 224 | ['?x (:type rdf) '?c2]]}] 225 | :c false} 226 | 227 | ;; :cax-adc nil ;; TODO 228 | }) 229 | 230 | #+END_SRC 231 | 232 | *** Semantics of Schema Vocabulary 233 | 234 | #+BEGIN_SRC clojure :noweb-ref rules 235 | (def schema-rules 236 | {:scm-cls {:q [{:where [['?c (:type rdf) (:Class owl)]]}] 237 | :c [['?c (:subClassOf rdfs) '?c] 238 | ['?c (:equivalentClass owl) '?c] 239 | ['?c (:subClassOf rdfs) (:Thing owl)] 240 | [(:Nothing owl) (:subClassOf rdfs) '?c]]} 241 | 242 | :scm-sco {:q [{:where [['?c1 (:subClassOf rdfs) '?c2] 243 | ['?c2 (:subClassOf rdfs) '?c3]]}] 244 | :c [['?c1 (:subClassOf rdfs) '?c3]]} 245 | 246 | :scm-eqc1 {:q [{:where [['?c1 (:equivalentClass owl) '?c2]]}] 247 | :c [['?c1 (:subClassOf rdfs) '?c2] 248 | ['?c2 (:subClassOf rdfs) '?c1]]} 249 | 250 | :scm-eqc2 {:q [{:where [['?c1 (:subClassOf rdfs) '?c2] 251 | ['?c2 (:subClassOf rdfs) '?c1]]}] 252 | :c [['?c1 (:equivalentClass owl) '?c2]]} 253 | 254 | :scm-op {:q [{:where [['?p (:type rdf) (:ObjectProperty owl)]]}] 255 | :c [['?p (:subPropertyOf rdfs) '?p] 256 | ['?p (:equivalentProperty owl) '?p]]} 257 | 258 | :scm-dp {:q [{:where [['?p (:type rdf) (:DatatypeProperty owl)]]}] 259 | :c [['?p (:subPropertyOf rdfs) '?p] 260 | ['?p (:equivalentProperty owl) '?p]]} 261 | 262 | :scm-spo {:q [{:where [['?p1 (:subPropertyOf rdfs) '?p2] 263 | ['?p2 (:subPropertyOf rdfs) '?p3]]}] 264 | :c [['?p1 (:subPropertyOf rdfs) '?p3]]} 265 | 266 | :scm-eqp1 {:q [{:where [['?p1 (:equivalentProperty owl) '?p2]]}] 267 | :c [['?p1 (:subPropertyOf rdfs) '?p2] 268 | ['?p2 (:subPropertyOf rdfs) '?p1]]} 269 | 270 | :scm-eqp2 {:q [{:where [['?p1 (:subPropertyOf rdfs) '?p2] 271 | ['?p2 (:subPropertyOf rdfs) '?p1]]}] 272 | :c [['?p1 (:equivalentProperty owl) '?p2]]} 273 | 274 | :scm-dom1 {:q [{:where [['?p (:domain rdfs) '?c1] 275 | ['?c1 (:subClassOf rdfs) '?c2]]}] 276 | :c [['?p (:domain rdfs) '?c2]]} 277 | 278 | :scm-dom2 {:q [{:where [['?p2 (:domain rdfs) '?c] 279 | ['?p1 (:subPropertyOf rdfs) '?p2]]}] 280 | :c [['?p1 (:domain rdfs) '?c]]} 281 | 282 | :scm-rng1 {:q [{:where [['?p (:range rdfs) '?c1] 283 | ['?c1 (:subClassOf rdfs) '?c2]]}] 284 | :c [['?p (:range rdfs) '?c2]]} 285 | 286 | :scm-rng2 {:q [{:where [['?p2 (:range rdfs) '?c] 287 | ['?p1 (:subPropertyOf rdfs) '?p2]]}] 288 | :c [['?p1 (:range rdfs) '?c]]} 289 | 290 | :scm-hv {:q [{:where [['?c1 (:hasValue owl) '?i] 291 | ['?c1 (:onProperty owl) '?p1] 292 | ['?c2 (:hasValue owl) '?i] 293 | ['?c2 (:onProperty owl) '?p2] 294 | ['?p1 (:subPropertyOf rdfs) '?p2]]}] 295 | :c [['?c1 (:subClassOf rdfs) '?c2]]} 296 | 297 | :scm-svf1 {:q [{:where [['?c1 (:someValuesFrom owl) '?y1] 298 | ['?c1 (:onProperty owl) '?p] 299 | ['?c2 (:someValuesFrom owl) '?y2] 300 | ['?c2 (:onProperty owl) '?p] 301 | ['?y1 (:subClassOf rdfs) '?y2]]}] 302 | :c [['?c1 (:subClassOf rdfs) '?c2]]} 303 | 304 | :scm-svf2 {:q [{:where [['?c1 (:someValuesFrom owl) '?y] 305 | ['?c1 (:onProperty owl) '?p1] 306 | ['?c2 (:someValuesFrom owl) '?y] 307 | ['?c2 (:onProperty owl) '?p2] 308 | ['?p1 (:subPropertyOf rdfs) '?p2]]}] 309 | :c [['?c1 (:subClassOf rdfs) '?c2]]} 310 | 311 | :scm-avf1 {:q [{:where [['?c1 (:allValuesFrom owl) '?y1] 312 | ['?c1 (:onProperty owl) '?p] 313 | ['?c2 (:allValuesFrom owl) '?y2] 314 | ['?c2 (:onProperty owl) '?p] 315 | ['?y1 (:subClassOf rdfs) '?y2]]}] 316 | :c [['?c1 (:subClassOf rdfs) '?c2]]} 317 | 318 | :scm-avf2 {:q [{:where [['?c1 (:allValuesFrom owl) '?y] 319 | ['?c1 (:onProperty owl) '?p1] 320 | ['?c2 (:allValuesFrom owl) '?y] 321 | ['?c2 (:onProperty owl) '?p2] 322 | ['?p1 (:subPropertyOf rdfs) '?p2]]}] 323 | :c [['?c2 (:subClassOf rdfs) '?c1]]} 324 | 325 | ;; :scm-int nil ;; TODO 326 | 327 | ;; :scm-uni nil ;; TODO 328 | }) 329 | 330 | #+END_SRC 331 | 332 | ** Inferencer 333 | 334 | #+BEGIN_SRC clojure :noweb-ref helpers 335 | (defn pattern-equiv 336 | [[s p o] [s' p' o']] 337 | (and 338 | (if (q/qvar? s) (q/qvar? s') (= s s')) 339 | (if (q/qvar? p) (q/qvar? p') (= p p')) 340 | (if (q/qvar? o) (q/qvar? o') (= o o')))) 341 | 342 | (defn subsumes? 343 | [[s p o] [s' p' o']] 344 | (and 345 | (or (q/qvar? s) (= s s')) 346 | (or (q/qvar? p) (= p p')) 347 | (or (q/qvar? o) (= o o')))) 348 | 349 | (defn grounded? 350 | [[s p o]] (not (or (q/qvar? s) (q/qvar? p) (q/qvar? o)))) 351 | 352 | (defn normalize 353 | [t] (mapv #(if (q/qvar? %) nil %) t)) 354 | #+END_SRC 355 | 356 | #+BEGIN_SRC clojure 357 | (require '[thi.ng.trio.query :as q]) 358 | (require '[thi.ng.trio.inference :as inf] :reload) 359 | (require '[thi.ng.trio.vocabs.rdf :refer [rdf]]) 360 | (require '[thi.ng.trio.vocabs.rdfs :refer [rdfs]]) 361 | (require '[thi.ng.trio.vocabs.owl :refer [owl]]) 362 | 363 | (def ds 364 | (api/as-model 365 | [["knows" (:type rdf) (:TransitiveProperty owl)] 366 | ["friend" (:subPropertyOf rdfs) "knows"] 367 | ["knows" (:domain rdfs) "Person"] 368 | ["knows" (:range rdfs) "Person"] 369 | [["friend" "knows"] (:type rdf) (:SymmetricProperty owl)] 370 | ["a" "knows" "b"] 371 | ["b" "friend" "c"] 372 | ["c" "knows" "d"] 373 | ["e" "friend" "f"]])) 374 | #+END_SRC 375 | 376 | #+BEGIN_SRC clojure :noweb-ref inferencer 377 | (defn infer-rule 378 | [ds id {:keys [q c]} inf] 379 | (let [rule {:construct c :query q}] 380 | (loop [ds ds, inf inf] 381 | (let [inf' (->> inf 382 | (set/difference (q/query (assoc rule :from ds))) 383 | (filter #(nil? (seq (apply api/select ds %)))))] 384 | (debug :inf inf') 385 | (if (seq inf') 386 | (recur 387 | (if id 388 | (api/add-triples ds id inf') 389 | (api/add-triples ds inf')) 390 | (if (< (count inf) (count inf')) 391 | (into inf' inf) 392 | (into inf inf'))) 393 | [ds inf]))))) 394 | 395 | (defn infer-rules 396 | "Takes a PModel or PDataset, a map of rule specs and applies 397 | infer-rule to all rules over max-passes. Stops as soon as no new 398 | triples can be inferred for any rule. Accepts an optional graph name 399 | `g` as target for inferred triples within a PDataSet. Returns 2-elem 400 | vector of [updated-model inf-map] where inf-map is a map of inferred 401 | triples with rule IDs as their keys." 402 | ([ds rules max-passes] 403 | (infer-rules ds nil rules max-passes)) 404 | ([ds g rules max-passes] 405 | (loop [state [ds {} false] i 1] 406 | (if (> i max-passes) 407 | (butlast state) 408 | (let [state (reduce 409 | (fn [[ds inf :as state] [id rule]] 410 | (debug :rule id) 411 | (let [[ds inf'] (infer-rule ds g rule #{})] 412 | (if (seq inf') 413 | [ds (assoc inf id (into (or (inf id) #{}) inf')) true] 414 | state))) 415 | (assoc state 2 false) rules)] 416 | (debug :pass i :state (peek state)) 417 | (if (peek state) 418 | (recur state (inc i)) 419 | (butlast state))))))) 420 | #+END_SRC 421 | 422 | ** Inf model 423 | 424 | #+BEGIN_SRC clojure :noweb-ref infstore 425 | 426 | #+END_SRC 427 | 428 | ** Complete namespace definition 429 | 430 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/inference.cljc :noweb yes :mkdirp yes :padline no 431 | (ns thi.ng.trio.inference 432 | #?(:cljs 433 | (:require-macros 434 | [cljs-log.core :refer [debug info warn severe]])) 435 | (:require 436 | [thi.ng.trio.core :as api] 437 | [thi.ng.trio.query :as q] 438 | [thi.ng.trio.vocabs.rdf :refer [rdf]] 439 | [thi.ng.trio.vocabs.rdfs :refer [rdfs]] 440 | [thi.ng.trio.vocabs.owl :refer [owl]] 441 | [clojure.set :as set] 442 | #?(:clj [taoensso.timbre :refer [debug info warn error]]))) 443 | 444 | <> 445 | 446 | <> 447 | 448 | <> 449 | #+END_SRC 450 | -------------------------------------------------------------------------------- /src/libraryofbabel.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | 3 | * The trio Library of Babel 4 | 5 | This file contains shared and configurable code templates for various 6 | parts of this project. 7 | 8 | #+NAME: lob-make-dot 9 | #+BEGIN_SRC emacs-lisp :var graph-table="" :results output 10 | (defun toxi-table->digraph (wrap table) 11 | (mapcar 12 | (lambda (x) (format "\"%s\" -> \"%s\"[label=\"%s\"];\n" (first x) (nth 2 x) (nth 1 x))) 13 | table)) 14 | 15 | (defun toxi-print-concat (coll) 16 | (princ (apply #'concat coll))) 17 | 18 | (toxi-print-concat (toxi-table->digraph nil (-drop 2 graph-table))) 19 | #+END_SRC 20 | -------------------------------------------------------------------------------- /src/setup.org: -------------------------------------------------------------------------------- 1 | #+SEQ_TODO: TODO(t) INPROGRESS(i) WAITING(w@) | DONE(d) CANCELED(c@) 2 | #+TAGS: write(w) fix(f) verify(v) noexport(n) template(t) usetemplate(u) toc@3@gh 3 | #+EXPORT_EXCLUDE_TAGS: noexport 4 | #+AUTHOR: Karsten Schmidt 5 | #+EMAIL: k@thi.ng 6 | #+LANGUAGE: en 7 | #+OPTIONS: toc:3 h:4 html-postamble:auto html-preamble:t tex:t 8 | #+HTML_CONTAINER: div 9 | #+HTML_DOCTYPE: 10 | #+HTML_HEAD: 11 | #+HTML_HEAD: 12 | -------------------------------------------------------------------------------- /src/utils.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | 3 | * Contents :toc_3_gh: 4 | - [[#namespace-thingtrioutils][Namespace: thi.ng.trio.utils]] 5 | - [[#case-conversion][Case conversion]] 6 | - [[#uuid-generation][UUID generation]] 7 | - [[#complete-namespace-definition][Complete namespace definition]] 8 | 9 | * Namespace: thi.ng.trio.utils 10 | 11 | ** Case conversion 12 | 13 | #+BEGIN_SRC clojure :noweb-ref case-convert 14 | (defn ->kebab-case 15 | [x] (-> x (str/replace #"([a-z\d])([A-Z])" "$1-$2") str/lower-case)) 16 | #+END_SRC 17 | 18 | ** UUID generation 19 | 20 | #+BEGIN_SRC clojure :noweb-ref uuid 21 | (def ^:private format-16bit-hex (f/pad-left 4 "0")) 22 | 23 | (defn rand-bits 24 | [bits] (int (* (rand) (bit-shift-left 1 bits)))) 25 | 26 | (defn rand-bits-hex 27 | [bits] ((f/pad-left (Math/ceil (/ bits 4)) "0") (.toString (rand-bits bits) 16))) 28 | 29 | (defn rand-16bits-hex 30 | [] (format-16bit-hex (.toString (rand-int 0x10000) 16))) 31 | 32 | (defn new-uuid 33 | [] 34 | #?(:clj 35 | (str (java.util.UUID/randomUUID)) 36 | :cljs (str 37 | (rand-16bits-hex) (rand-16bits-hex) 38 | "-" (rand-16bits-hex) 39 | "-" (-> (rand-bits 16) (bit-and 0x0fff) (bit-or 0x4000) (.toString 16) (format-16bit-hex)) 40 | "-" (-> (rand-bits 16) (bit-and 0x3fff) (bit-or 0x8000) (.toString 16) (format-16bit-hex)) 41 | "-" (rand-16bits-hex) (rand-16bits-hex) (rand-16bits-hex)))) 42 | #+END_SRC 43 | 44 | ** Complete namespace definition 45 | 46 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/utils.cljc :noweb yes :mkdirp yes :padline no 47 | (ns thi.ng.trio.utils 48 | (:require 49 | [thi.ng.strf.core :as f] 50 | [clojure.string :as str])) 51 | 52 | <> 53 | 54 | <> 55 | #+END_SRC 56 | -------------------------------------------------------------------------------- /src/vocabs.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | 3 | * Contents :toc_3_gh: 4 | - [[#namespace-thingtriovocabs][Namespace: thi.ng.trio.vocabs]] 5 | - [[#common-linked-data-terms][Common linked data terms]] 6 | - [[#rdf][RDF]] 7 | - [[#rdfs][RDFS]] 8 | - [[#owl][OWL]] 9 | - [[#dublincore][Dublincore]] 10 | - [[#vocab-construction-helpers][Vocab construction helpers]] 11 | - [[#complete-namespace-definitions][Complete namespace definitions]] 12 | 13 | * Namespace: thi.ng.trio.vocabs 14 | 15 | ** Common linked data terms 16 | 17 | *** RDF 18 | 19 | #+BEGIN_SRC clojure :noweb-ref rdf 20 | (defvocab rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#" 21 | :Alt 22 | :Bag 23 | :HTML 24 | :List 25 | :PlainLiteral 26 | :Property 27 | :Resource 28 | :Seq 29 | :Statement 30 | :XMLLiteral 31 | :first 32 | :langString 33 | :nil 34 | :object 35 | :predicate 36 | :rest 37 | :subject 38 | :type 39 | :value) 40 | #+END_SRC 41 | 42 | *** RDFS 43 | 44 | #+BEGIN_SRC clojure :noweb-ref rdfs 45 | (defvocab rdfs "http://www.w3.org/2000/01/rdf-schema#" 46 | :Class 47 | :Container 48 | :ContainerMembershipProperty 49 | :Datatype 50 | :Literal 51 | :Resource 52 | :comment 53 | :domain 54 | :isDefinedBy 55 | :label 56 | :member 57 | :range 58 | :seeAlso 59 | :subClassOf 60 | :subPropertyOf) 61 | #+END_SRC 62 | 63 | *** OWL 64 | 65 | #+BEGIN_SRC clojure :noweb-ref owl 66 | (defvocab owl "http://www.w3.org/2002/07/owl#" 67 | :AllDifferent 68 | :AllDisjointClasses 69 | :AllDisjointProperties 70 | :Annotation 71 | :AnnotationProperty 72 | :AsymmetricProperty 73 | :Axiom 74 | :Class 75 | :DataRange 76 | :DatatypeProperty 77 | :DeprecatedClass 78 | :DeprecatedProperty 79 | :FunctionalProperty 80 | :InverseFunctionalProperty 81 | :IrreflexiveProperty 82 | :NamedIndividual 83 | :NegativePropertyAssertion 84 | :Nothing 85 | :ObjectProperty 86 | :Ontology 87 | :OntologyProperty 88 | :ReflexiveProperty 89 | :Restriction 90 | :SymmetricProperty 91 | :Thing 92 | :TransitiveProperty 93 | :allValuesFrom 94 | :annotatedProperty 95 | :annotatedSource 96 | :annotatedTarget 97 | :assertionProperty 98 | :backwardCompatibleWith 99 | :bottomDataProperty 100 | :bottomObjectProperty 101 | :cardinality 102 | :complementOf 103 | :datatypeComplementOf 104 | :deprecated 105 | :differentFrom 106 | :disjointUnionOf 107 | :disjointWith 108 | :distinctMembers 109 | :equivalentClass 110 | :equivalentProperty 111 | :hasKey 112 | :hasSelf 113 | :hasValue 114 | :imports 115 | :incompatibleWith 116 | :intersectionOf 117 | :inverseOf 118 | :maxCardinality 119 | :maxQualifiedCardinality 120 | :members 121 | :minCardinality 122 | :minQualifiedCardinality 123 | :onClass 124 | :onDataRange 125 | :onDatatype 126 | :onProperties 127 | :onProperty 128 | :oneOf 129 | :priorVersion 130 | :propertyChainAxiom 131 | :propertyDisjointWith 132 | :qualifiedCardinality 133 | :sameAs 134 | :someValuesFrom 135 | :sourceIndividual 136 | :targetIndividual 137 | :targetValue 138 | :topDataProperty 139 | :topObjectProperty 140 | :unionOf 141 | :versionIRI 142 | :versionInfo 143 | :withRestrictions) 144 | #+END_SRC 145 | 146 | *** Dublincore 147 | 148 | #+BEGIN_SRC clojure :noweb-ref dcterms 149 | (defvocab dcterms "http://purl.org/dc/terms/" 150 | :abstract 151 | :accessRights 152 | :contributor 153 | :created 154 | :creator 155 | :dateSubmitted 156 | :description 157 | :format 158 | :hasPart 159 | :hasVersion 160 | :isPartOf 161 | :license 162 | :modified 163 | :publisher 164 | :references 165 | :rights 166 | :title) 167 | #+END_SRC 168 | 169 | ** Vocab construction helpers 170 | 171 | #+BEGIN_SRC clojure :noweb-ref helpers 172 | (defn expand-pname 173 | "Takes a prefix map and pname (\"prefix:name\") and if a valid 174 | prefix, returns expanded URI or else nil." 175 | [prefixes x] 176 | (if (string? x) 177 | (let [[[_ p n]] (re-seq #"^([A-Za-z0-9\-_]+):([A-Za-z0-9\-_]*)$" x)] 178 | (if-let [p (prefixes p)] 179 | (str p n))))) 180 | 181 | (defn expand-pname-maybe 182 | "Like expand-pname, but returns original, if it can't be expanded." 183 | [prefixes x] 184 | (or (expand-pname prefixes x) x)) 185 | 186 | (defn expand-pnames-in-triple 187 | "Attempts to expand all pnames in given triple using expand-pname-maybe." 188 | [prefixes [s p o]] 189 | (api/triple 190 | (expand-pname-maybe prefixes s) 191 | (expand-pname-maybe prefixes p) 192 | (expand-pname-maybe prefixes o))) 193 | 194 | (defn find-prefix 195 | "Takes a prefix map and URI, attempts to find pname prefix and if 196 | found returns pname as 2-elem vector of [prefix name], else nil." 197 | [prefixes ^String uri] 198 | (loop [[[pre puri] & more] (seq prefixes)] 199 | (when pre 200 | (if (== 0 (.indexOf uri ^String puri)) 201 | [pre (subs uri (count puri))] 202 | (recur more))))) 203 | 204 | (defn vocabs-from-model 205 | [prefixes graph] 206 | (->> graph 207 | (api/subjects) 208 | (reduce 209 | (fn [vocabs uri] 210 | (let [[p n] (find-prefix prefixes uri)] 211 | (if p 212 | (assoc-in vocabs [(keyword p) (keyword n)] uri) 213 | vocabs))) 214 | {}))) 215 | 216 | (defn triple-seq-with-prefixes 217 | [prefixes triples] 218 | (->> triples 219 | (api/triple-seq) 220 | (mapv #(expand-pnames-in-triple prefixes %)))) 221 | 222 | (defn make-vocab 223 | [uri xs] 224 | (->> xs (map (fn [x] [x (str uri (name x))])) (into {}))) 225 | 226 | #?(:clj 227 | (defn load-vocab-triples 228 | [src] 229 | (let [{:keys [prefixes triples]} 230 | (->> src 231 | (io/input-stream) 232 | (slurp) 233 | (edn/read-string))] 234 | {:prefixes prefixes 235 | :triples (triple-seq-with-prefixes prefixes triples)}))) 236 | 237 | #?(:clj 238 | (defn load-vocabs-as-model 239 | [src] 240 | (let [{:keys [prefixes triples]} (load-vocab-triples src)] 241 | (vocabs-from-model prefixes (api/as-model triples))))) 242 | #+END_SRC 243 | 244 | ** Complete namespace definitions 245 | 246 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/vocabs.clj :mkdirp yes :padline no 247 | (ns thi.ng.trio.vocabs 248 | (:require 249 | [thi.ng.trio.vocabs.utils :as vu])) 250 | 251 | (defmacro defvocab 252 | [id uri & xs] 253 | (let [voc (vu/make-vocab uri xs)] 254 | `(def ~id ~voc))) 255 | #+END_SRC 256 | 257 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/vocabs/utils.cljc :noweb yes :mkdirp yes :padline no 258 | (ns thi.ng.trio.vocabs.utils 259 | (:require 260 | [thi.ng.trio.core :as api] 261 | [clojure.string :as str] 262 | #?@(:clj [[clojure.java.io :as io] 263 | [clojure.edn :as edn]]))) 264 | 265 | <> 266 | #+END_SRC 267 | 268 | #+BEGIN_SRC clojure :noweb-ref incl-macros 269 | #?(:cljs (:require-macros [thi.ng.trio.vocabs :refer [defvocab]]) 270 | :clj (:require [thi.ng.trio.vocabs :refer [defvocab]])) 271 | #+END_SRC 272 | 273 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/vocabs/rdf.cljc :noweb yes :mkdirp yes :padline no 274 | (ns thi.ng.trio.vocabs.rdf 275 | <>) 276 | 277 | <> 278 | #+END_SRC 279 | 280 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/vocabs/rdfs.cljc :noweb yes :mkdirp yes :padline no 281 | (ns thi.ng.trio.vocabs.rdfs 282 | <>) 283 | 284 | <> 285 | #+END_SRC 286 | 287 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/vocabs/dcterms.cljc :noweb yes :mkdirp yes :padline no 288 | (ns thi.ng.trio.vocabs.dcterms 289 | <>) 290 | 291 | <> 292 | #+END_SRC 293 | 294 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/trio/vocabs/owl.cljc :noweb yes :mkdirp yes :padline no 295 | (ns thi.ng.trio.vocabs.owl 296 | <>) 297 | 298 | <> 299 | #+END_SRC 300 | -------------------------------------------------------------------------------- /tangle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIR=`pwd` 4 | FILES="" 5 | 6 | # wrap each argument in the code required to call tangle on it 7 | for i in $@; do 8 | FILES="$FILES \"$i\"" 9 | done 10 | 11 | emacs -Q --batch \ 12 | --eval \ 13 | "(progn 14 | (require 'org)(require 'ob)(require 'ob-tangle)(require 'ob-lob) 15 | (org-babel-lob-ingest \"src/libraryofbabel.org\") 16 | (setq org-confirm-babel-evaluate nil) 17 | (mapc (lambda (file) 18 | (find-file (expand-file-name file \"$DIR\")) 19 | (org-babel-tangle) 20 | (kill-buffer)) '($FILES)))" \ 21 | #2>&1 | grep Tangled 22 | -------------------------------------------------------------------------------- /test/core.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: "../src/setup.org" 2 | 3 | * thi.ng.trio.test.core 4 | 5 | ** Triplestore tests 6 | 7 | #+BEGIN_SRC clojure :noweb-ref tests 8 | (def g1 9 | '[[[alice bob carl] friend ed] 10 | [alice mother [fred gemma]] 11 | [bob father fred] 12 | [gemma mother henry] 13 | [fred father [inigo james]]]) 14 | 15 | (defrecord Foo [x y] 16 | api/PTripleSeq 17 | (triple-seq [_] (api/triple-seq {x {:p y} y {:q x}}))) 18 | 19 | (defn select-set 20 | [ds & [s p o]] (set (api/select ds s p o))) 21 | 22 | (defn select-with-alts-set 23 | [ds & [s p o]] (set (api/select-with-alts ds s p o))) 24 | 25 | (deftest record-conversions 26 | (let [foo (Foo. 23 42) 27 | bar (Foo. 42 23)] 28 | (is (= [[23 :p 42] [42 :q 23]] (api/triple-seq foo))) 29 | (is (= [[23 :p 42] [42 :q 23]] (api/triple-seq [foo]))) 30 | (is (= [[foo :p2 bar]] (api/triple-seq {foo {:p2 bar}}))) 31 | (is (= [[23 :p 42] [42 :q 23] [23 :p2 bar] [42 :p2 bar]] 32 | (api/triple-seq [foo [[23 42] :p2 bar]]))))) 33 | 34 | (deftest conversions 35 | (let [t1 '[[s1 p o] ;; 1 36 | [[s1 s2] p o] ;; 2 37 | [s3 [p1 p2] o] ;; 2 38 | [s4 p [o1 o2]] ;; 2 39 | [[s5 s6] [p1 p2] [o1 o2]] ;; 8 40 | [[s7 s8] [p1 p2] o] ;; 4 41 | [[s9 s0] p1 [o1 o2]] ;; 4 42 | [s0 [p2 p3] [o1 o2]]] ;; 4 43 | t2 '{s1 {p1 o1 p2 o2} ;; 2 44 | s2 {p1 [o1 o1 o2] p3 o3}}] ;; 4 45 | (is (satisfies? api/PTripleSeq [])) 46 | #?(:clj (is (satisfies? api/PTripleSeq (list)))) 47 | #?(:clj (is (satisfies? api/PTripleSeq (cons '[s p o] nil)))) 48 | (is (satisfies? api/PTripleSeq (lazy-seq []))) 49 | (is (satisfies? api/PTripleSeq {})) 50 | (is (satisfies? api/PModelConvert [])) 51 | #?(:clj (is (satisfies? api/PModelConvert (list)))) 52 | #?(:clj (is (satisfies? api/PModelConvert (cons '[s p o] nil)))) 53 | (is (satisfies? api/PModelConvert (lazy-seq []))) 54 | (is (satisfies? api/PModelConvert {})) 55 | (is (== 27 (count (api/triple-seq t1)))) 56 | (is (== 6 (count (api/triple-seq t2)))) 57 | (is (== 26 (api/model-size (api/as-model t1)))) 58 | (is (== 5 (api/model-size (api/as-model t2)))))) 59 | 60 | (defn test-store-api 61 | [name ds] 62 | (testing 63 | (str "testing store: " name) 64 | (is (== 9 (api/model-size ds) (count (select-set ds)))) 65 | (is (= (set (api/triple-seq-sequential g1)) (set (select-set ds)))) 66 | (is (= '#{alice bob carl gemma fred} (set (api/subjects ds)))) 67 | (is (= '#{friend mother father} (set (api/predicates ds)))) 68 | (is (= '#{ed fred gemma henry inigo james} (set (api/objects ds)))) 69 | ;; s ? ? 70 | (is (= '#{[bob friend ed] [bob father fred]} 71 | (select-set ds 'bob nil nil))) 72 | ;; ? p ? 73 | (is (= '#{[alice mother fred] [alice mother gemma] [gemma mother henry]} 74 | (select-set ds nil 'mother nil))) 75 | ;; ? ? o 76 | (is (= '#{[alice friend ed] [bob friend ed] [carl friend ed]} 77 | (select-set ds nil nil 'ed))) 78 | ;; s p ? 79 | (is (= '#{[alice mother fred] [alice mother gemma]} 80 | (select-set ds 'alice 'mother nil))) 81 | ;; s ? o 82 | (is (= '#{[fred father inigo]} 83 | (select-set ds 'fred nil 'inigo))) 84 | ;; ? p o 85 | (is (= '#{[gemma mother henry]} 86 | (select-set ds nil 'mother 'henry))) 87 | ;; s p o 88 | (is (= '#{[gemma mother henry]} 89 | (select-set ds 'gemma 'mother 'henry))) 90 | ;; n/a 91 | (is (= #{} (select-set ds 'henry 'friend 'bob))))) 92 | 93 | (defn test-select-with-alts 94 | [name ds] 95 | (testing 96 | (str "testing alt select: " name) 97 | ;; select-with-alts 98 | ;; s ? ? 99 | (is (= '#{[bob friend ed] [bob father fred]} 100 | (select-with-alts-set ds 'bob nil nil))) 101 | (is (= '#{[bob friend ed] [bob father fred] [fred father inigo] [fred father james]} 102 | (select-with-alts-set ds '#{bob fred} nil nil))) 103 | ;; ? p ? 104 | (is (= '#{[alice mother fred] [alice mother gemma] [gemma mother henry]} 105 | (select-with-alts-set ds nil 'mother nil))) 106 | (is (= '#{[alice mother fred] [alice mother gemma] [gemma mother henry] 107 | [bob father fred] [fred father inigo] [fred father james]} 108 | (select-with-alts-set ds nil '#{mother father} nil))) 109 | ;; ? ? o 110 | (is (= '#{[alice mother fred] [bob father fred]} 111 | (select-with-alts-set ds nil nil 'fred))) 112 | (is (= '#{[alice mother fred] [bob father fred] [fred father inigo]} 113 | (select-with-alts-set ds nil nil '#{fred inigo}))) 114 | ;; s p ? 115 | (is (= '#{[alice mother fred] [alice mother gemma]} 116 | (select-with-alts-set ds 'alice 'mother nil))) 117 | (is (= '#{[alice mother fred] [alice mother gemma] [bob father fred]} 118 | (select-with-alts-set ds '#{alice bob} '#{mother father} nil))) 119 | ;; s ? o 120 | (is (= '#{[fred father inigo]} 121 | (select-with-alts-set ds 'fred nil 'inigo))) 122 | (is (= '#{[fred father inigo] [gemma mother henry]} 123 | (select-with-alts-set ds '#{fred gemma} nil '#{inigo henry}))) 124 | ;; ? p o 125 | (is (= '#{[gemma mother henry]} 126 | (select-with-alts-set ds nil 'mother 'henry))) 127 | (is (= '#{[gemma mother henry] [alice mother fred] [alice mother gemma]} 128 | (select-with-alts-set ds nil '#{mother friend} '#{henry gemma fred}))) 129 | ;; s p o 130 | (is (= '#{[gemma mother henry]} 131 | (select-with-alts-set ds 'gemma 'mother 'henry))) 132 | (is (= '#{[bob friend ed] [gemma mother henry]} 133 | (select-with-alts-set ds '#{bob gemma} '#{friend mother} '#{ed henry}))))) 134 | 135 | (deftest plainstore-api 136 | (let [ds (api/as-model g1)] 137 | (test-store-api "plain-store" ds) 138 | (test-select-with-alts "plain-store" ds))) 139 | 140 | (deftest aliasstore-api 141 | (let [ds (api/alias-store (api/as-model g1) nil) 142 | ds2 (api/alias-store (api/as-model g1) '{alice a, gemma g, mother parent}) 143 | ac (u/canonical ds2 'alice) 144 | mc (u/canonical ds2 'mother) 145 | gc (u/canonical ds2 'gemma)] 146 | (test-store-api "alias-store1" ds) 147 | (is (every? #(contains? (set (api/subjects ds2)) %) [ac gc])) 148 | (is (contains? (set (api/predicates ds2)) mc)) 149 | (is (contains? (set (api/objects ds2)) gc)) 150 | (is (api/subject? ds2 'alice)) 151 | (is (api/predicate? ds2 'mother)) 152 | (is (api/object? ds2 'gemma)) 153 | (is (= #{[ac 'friend 'ed] [ac mc 'fred] [ac mc gc]} 154 | (select-set ds2 'alice nil nil))))) 155 | #+END_SRC 156 | 157 | ** Namespace declaration 158 | 159 | #+BEGIN_SRC clojure :tangle ../babel/test/thi/ng/trio/test/core.cljc :noweb yes :mkdirp yes :padline no 160 | (ns thi.ng.trio.test.core 161 | #?(:cljs 162 | (:require-macros 163 | [cemerick.cljs.test :refer [is deftest with-test testing]])) 164 | (:require 165 | [thi.ng.trio.core :as api] 166 | [thi.ng.trio.query :as q] 167 | [thi.ng.dstruct.unionfind :as u] 168 | #?(:clj 169 | [clojure.test :refer :all] 170 | :cljs 171 | [cemerick.cljs.test :as t]))) 172 | 173 | <> 174 | #+END_SRC 175 | -------------------------------------------------------------------------------- /test/entities.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: "../src/setup.org" 2 | 3 | * thi.ng.trio.test.entities 4 | 5 | ** Entities 6 | 7 | #+BEGIN_SRC clojure :noweb-ref tests 8 | (defentity TestEntity "trio:TestEntity" 9 | {:title {:prop (:title dcterms) 10 | :validate [(v/string)] 11 | :default (fn [_] "Untitled")} 12 | :creator {:prop (:creator dcterms) 13 | :validate [(v/uuid4)] 14 | :init (eu/pick-id :creator)} 15 | :created {:prop (:created dcterms) 16 | :validate [(v/number) (v/pos)] 17 | :default (fn [_] 1000)} 18 | :modified {:prop (:modified dcterms) 19 | :validate [(v/number) (v/pos)] 20 | :default (fn [_] [1001]) 21 | :card :* 22 | :order :asc} 23 | :items {:prop (:hasPart dcterms) 24 | :validate {:* [(v/uuid4)]} 25 | :optional true 26 | :card :*}}) 27 | #+END_SRC 28 | 29 | #+BEGIN_SRC clojure :noweb-ref tests 30 | (deftest test-entity 31 | (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (make-test-entity {}))) 32 | (let [cid (utils/new-uuid) 33 | e (make-test-entity {:creator cid})] 34 | (is (= {:creator cid :title "Untitled" :type "trio:TestEntity" 35 | :created 1000 :modified [1001] :items []} 36 | (dissoc e :id)))) 37 | (let [cid (utils/new-uuid) 38 | mod [8 5 13 21 3 1 2] 39 | e (make-test-entity {:creator cid :modified mod})] 40 | (is (= {:creator cid :title "Untitled" :type "trio:TestEntity" 41 | :created 1000 :modified (vec (sort mod)) :items []} 42 | (dissoc e :id))))) 43 | #+END_SRC 44 | 45 | ** Namespace declaration 46 | 47 | #+BEGIN_SRC clojure :tangle ../babel/test/thi/ng/trio/test/entities.cljc :noweb yes :mkdirp yes :padline no 48 | (ns thi.ng.trio.test.entities 49 | #?(:cljs 50 | (:require-macros 51 | [cemerick.cljs.test :refer [is deftest with-test testing]] 52 | [thi.ng.trio.entities :as e :refer [defentity]])) 53 | (:require 54 | [thi.ng.validate.core :as v] 55 | [thi.ng.trio.core :as api] 56 | [thi.ng.trio.query :as q] 57 | [thi.ng.trio.utils :as utils] 58 | [thi.ng.trio.vocabs.dcterms :refer [dcterms]] 59 | [thi.ng.trio.entities.utils :as eu] 60 | #?@(:clj 61 | [[thi.ng.trio.entities :refer [defentity]] 62 | [clojure.test :refer :all]] 63 | :cljs 64 | [[cemerick.cljs.test :as t]]))) 65 | 66 | <> 67 | #+END_SRC 68 | -------------------------------------------------------------------------------- /test/query.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: "../src/setup.org" 2 | 3 | * thi.ng.trio.test.query 4 | 5 | ** Lowlevel query tests 6 | 7 | #+BEGIN_SRC clojure :noweb-ref tests 8 | (def g1 9 | '[[[alice bob carl] friend ed] 10 | [alice mother [fred gemma]] 11 | [bob father fred] 12 | [gemma mother henry] 13 | [fred father [inigo james]]]) 14 | 15 | (defn select-and-accumulate 16 | [ds bind opts pattern] 17 | (->> pattern 18 | (q/select-with-bindings ds bind opts) 19 | (q/accumulate-result-vars))) 20 | 21 | (deftest accu-res-vars 22 | (let [acc (q/accumulate-result-vars '({:a 23 :b 42 :c 66} {:a 42 :b 23 :c 66}))] 23 | (is (= #{23 42} (:a acc))) 24 | (is (= #{23 42} (:b acc))) 25 | (is (= #{66} (:c acc))))) 26 | 27 | (deftest select-with-bindings 28 | (let [ds (api/as-model g1) 29 | acc1 (select-and-accumulate ds {} {} '[?a friend ?b]) 30 | acc2 (select-and-accumulate ds '{?a alice} {} '[?a friend ?b]) 31 | acc3 (select-and-accumulate ds '{?a #{alice bob} ?b #{ed henry}} {} '[?a friend ?b])] 32 | (is (and (= '#{alice bob carl} (acc1 '?a)) (= '#{ed} (acc1 '?b)))) 33 | (is (and (= '#{alice} (acc2 '?a)) (= '#{ed} (acc2 '?b)))) 34 | (is (and (= '#{alice bob} (acc3 '?a)) (= '#{ed} (acc3 '?b)))))) 35 | #+END_SRC 36 | 37 | ** Subquery :values merging 38 | 39 | #+BEGIN_SRC clojure :noweb-ref tests 40 | (deftest subq-value-merge 41 | (let [spec {:select '?m 42 | :from g1 43 | :query [{:where '[[?m mother ?c]] :values '{?m #{alice}}}] 44 | :values '{?m #{gemma}}}] 45 | (is (= '{?m #{alice gemma}} 46 | (-> spec q/query q/accumulate-result-vars))) 47 | (is (= '{?m #{alice}} 48 | (-> spec (dissoc :values) q/query q/accumulate-result-vars))) 49 | (is (= '{?m #{gemma}} 50 | (-> spec (assoc-in [:query 0 :values] nil) q/query q/accumulate-result-vars))))) 51 | #+END_SRC 52 | 53 | ** Namespace declaration 54 | 55 | #+BEGIN_SRC clojure :tangle ../babel/test/thi/ng/trio/test/query.cljc :noweb yes :mkdirp yes :padline no 56 | (ns thi.ng.trio.test.query 57 | #?(:cljs 58 | (:require-macros 59 | [cemerick.cljs.test :refer [is deftest with-test testing]])) 60 | (:require 61 | [thi.ng.validate.core] 62 | [thi.ng.trio.core :as api] 63 | [thi.ng.trio.query :as q] 64 | [thi.ng.dstruct.unionfind :as u] 65 | #?(:clj 66 | [clojure.test :refer :all] 67 | :cljs 68 | [cemerick.cljs.test :as t]))) 69 | 70 | <> 71 | #+END_SRC 72 | --------------------------------------------------------------------------------