├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json ├── project.clj ├── src ├── datascript.clj ├── datascript.cljs └── datascript │ ├── core.cljs │ ├── js.cljs │ ├── preamble.js │ └── query.cljs └── test ├── js └── index.js └── test └── datascript.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | web/out 11 | web/*.js 12 | perf 13 | node_modules 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | script: lein2 cljsbuild test 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Dataquery v 0.1.0 2 | 3 | Forked project and renamed to "dataquery". Changed to allow for asynchronous queries and external indexes. Removed all local storage capacity. 4 | 5 | # 0.4.0 6 | 7 | Cosmetic changes to better mimic Datomic API. Useful for sharing code between Datomic and DataScript: 8 | 9 | - Added `tempid`, `resolve-tempid`, `db`, `transact`, `transact-async`, `index-range`, `squuid`, `squuid-time-millis` 10 | - [ BREAKING ] renamed `transact` to `with`, `with` to `db-with` 11 | 12 | # 0.3.1 13 | 14 | - Optimized speed of DB’s `equiv` and `hash`, Datom’s `hash` 15 | - Entity’s `touch` call accessible through `datascript` namespace 16 | - Accept sets in entity maps as values for `:db.cardinality/many` attributes 17 | 18 | # 0.3.0 19 | 20 | Proper entities implementation: 21 | 22 | - Entities are now lazy and implement usual Map protocols 23 | - [ BREAKING ] When accessing attribute of `:db/valueType :db.type/ref`, its value will be automatically expanded to entites, allowing for recursive exploration of entities graphs (e.g. `(-> (d/entity db 42) :parent :parent :children)`) 24 | - Entities support backwards navigation (e.g. `(:person/_friends (d/entity db 42))`) 25 | 26 | # 0.2.1 27 | 28 | - Externs file now can be referred as `:externs [datascript/externs.js"]` 29 | 30 | # 0.2.0 31 | 32 | Big performance improvements: 33 | 34 | - New B-tree based indexes 35 | - New set-at-a-time, hash-join powered query and rules engine 36 | - Queries now up to 10× times faster 37 | - You can specify DB for rule call (like `($db follows ?e1 ?e2)`) 38 | - Datoms are required to have integer id and keyword attributes, but no restriction on types of values 39 | 40 | # 0.1.6 41 | 42 | - Clojure reader support (pr/read) for DB and Datom 43 | 44 | # 0.1.5 45 | 46 | - `datoms` and `seek-datoms` API calls 47 | - referencing other entities’ tempids in transaction data (issue #10) 48 | 49 | # 0.1.4 50 | 51 | - Transactor functions via `:db.fn/call` (thx [@thegeez](https://github.com/thegeez)) 52 | - Vanilla JS API bindings 53 | - [ BREAKING ] Schema keywords namespaced on a par with Datomic schema 54 | 55 | # 0.1.3 56 | 57 | - `entity` added 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DataQuery 2 | 3 | Forked the query language from [Datascript](https://github.com/tonsky/datascript), and changed to only utilize external indexes in an asynchronous manner. 4 | 5 | # To-Do 6 | 7 | - More documentation. 8 | - More examples. 9 | - More tests. 10 | 11 | # Usage 12 | 13 | For instance, say you have a PouchDB database with two views, one called `eav/eav` and one called `ave/ave`. These views would have array keys, such as [entity,attribute,value] and [attribute,value,entity], respectively, and both (for ease in our example) would have values of [entity,attribute,value]. (Another way of thinking of these triples is [id,label,value].) 14 | 15 | Then, you could query the database as follows: 16 | 17 | ```javascript 18 | var d = require( "dataquery" ); 19 | var PouchDB = require( "pouchdb" ); 20 | var db = new PouchDB( "dataquery" ); 21 | 22 | var searchPouchIndex = function( db, index ) { 23 | return function( search, callback ) { 24 | var view = index + "/" + index; 25 | var endkey = search.map( function( el ) { 26 | if ( el === null ) { 27 | return {}; 28 | } 29 | return el; 30 | }); 31 | db.query( view, { 32 | startkey: search, 33 | endkey: endkey 34 | }, function( error, data ) { 35 | callback( data.rows.map( function( el ) { 36 | return el.value; 37 | }) ); 38 | }) 39 | } 40 | }; 41 | 42 | var initPouchDB = function() { 43 | return d.db( db, searchPouchIndex( db, "eav" ), searchPouchIndex( db, "ave" ) ); 44 | }; 45 | 46 | var datalog = initPouchDB(); 47 | d.q( '[:find ?id :in :where [?id "last_name" "benson"]]', function( data ) { 48 | console.log( "Query results: ", data ); 49 | }, datalog ); 50 | ``` 51 | 52 | In fact, as long as you can provide functions to search the "eav" and "ave" indexes on any dataset, returning "eav" triples, you can use Dataquery to query those indexes. This should include in-memory indexes (though I'd suggest using [Datascript](https://github.com/tonsky/datascript) as it has been optimized for that use-case), IndexedDB, or pretty much any persistent store that would allow you to define indexes as necessary. 53 | 54 | # Installation 55 | 56 | ``` 57 | npm install --save dataquery 58 | ``` 59 | 60 | # Status 61 | 62 | Quite alpha. Contributions/suggestions/constructive critique very welcome! -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dataquery 3 | * Copyright 2014 David Alan Hjelle & Icon Systems, Inc. 4 | * 5 | * Query Engine originally from Datascript v0.4.0 6 | * which is Copyright 2014 Nikita Prokopov 7 | * 8 | * Licensed under Eclipse Public License; 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://github.com/dahjelle/dataquery/blob/master/LICENSE 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | if(typeof Math.imul == "undefined" || (Math.imul(0xffffffff,5) == 0)) { 21 | Math.imul = function (a, b) { 22 | var ah = (a >>> 16) & 0xffff; 23 | var al = a & 0xffff; 24 | var bh = (b >>> 16) & 0xffff; 25 | var bl = b & 0xffff; 26 | // the shift by 0 fixes the sign on the high part 27 | // the final |0 converts the unsigned value into a signed value 28 | return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)|0); 29 | } 30 | } 31 | 32 | ;(function(){ 33 | var k; 34 | function p(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"== 35 | b&&"undefined"==typeof a.call)return"object";return b}var aa="closure_uid_"+(1E9*Math.random()>>>0),ba=0;function ca(a,b){for(var c in a)b.call(void 0,a[c],c,a)};function da(a,b){null!=a&&this.append.apply(this,arguments)}da.prototype.Ra="";da.prototype.append=function(a,b,c){this.Ra+=a;if(null!=b)for(var d=1;db?1:a>>16&65535)*d+c*(b>>>16&65535)<<16>>>0)|0};function Kb(a){a=Jb(a,3432918353);return Jb(a<<15|a>>>-15,461845907)}function Lb(a,b){var c=a^b;return Jb(c<<13|c>>>-13,5)+3864292196} 55 | function Mb(a,b){var c=a^b,c=Jb(c^c>>>16,2246822507),c=Jb(c^c>>>13,3266489909);return c^c>>>16}function Nb(a){var b;a:{b=1;for(var c=0;;)if(b>2)}function Ub(a){return a instanceof B}function Vb(a,b){if(s(C.a?C.a(a,b):C.call(null,a,b)))return 0;if(s(function(){var c=ua(a.ja);return c?b.ja:c}()))return-1;if(s(a.ja)){if(ua(b.ja))return 1;var c=function(){var c=a.ja,d=b.ja;return Wb.a?Wb.a(c,d):Wb.call(null,c,d)}();if(0===c){var c=a.name,d=b.name;return Wb.a?Wb.a(c,d):Wb.call(null,c,d)}return c}c=a.name;d=b.name;return Wb.a?Wb.a(c,d):Wb.call(null,c,d)} 58 | function B(a,b,c,d,e){this.ja=a;this.name=b;this.Ka=c;this.Qa=d;this.ea=e;this.i=2154168321;this.p=4096}k=B.prototype;k.u=function(a,b){return rb(b,this.Ka)};k.w=function(){var a=this.Qa;return null!=a?a:this.Qa=a=Tb(Nb(this.name),Rb(this.ja))};k.I=function(a,b){return new B(this.ja,this.name,this.Ka,this.Qa,b)};k.F=function(){return this.ea}; 59 | k.call=function(){var a=null,a=function(a,c,d){switch(arguments.length){case 2:return Oa.c(c,this,null);case 3:return Oa.c(c,this,d)}throw Error("Invalid arity: "+arguments.length);};a.a=function(a,c){return Oa.c(c,this,null)};a.c=function(a,c,d){return Oa.c(c,this,d)};return a}();k.apply=function(a,b){return this.call.apply(this,[this].concat(xa(b)))};k.b=function(a){return Oa.c(a,this,null)};k.a=function(a,b){return Oa.c(a,this,b)};k.t=function(a,b){return b instanceof B?this.Ka===b.Ka:!1}; 60 | k.toString=function(){return this.Ka};var Xb=function(){function a(a,b){var c=null!=a?""+x.b(a)+"/"+x.b(b):b;return new B(a,b,c,null,null)}function b(a){return a instanceof B?a:c.a(null,a)}var c=null,c=function(c,e){switch(arguments.length){case 1:return b.call(this,c);case 2:return a.call(this,c,e)}throw Error("Invalid arity: "+arguments.length);};c.b=b;c.a=a;return c}(); 61 | function D(a){if(null==a)return null;if(a&&(a.i&8388608||a.$b))return a.H(null);if(a instanceof Array||"string"===typeof a)return 0===a.length?null:new Yb(a,0);if(t(kb,a))return lb(a);throw Error(""+x.b(a)+" is not ISeqable");}function H(a){if(null==a)return null;if(a&&(a.i&64||a.Ya))return a.P(null);a=D(a);return null==a?null:Ka(a)}function I(a){return null!=a?a&&(a.i&64||a.Ya)?a.V(null):(a=D(a))?La(a):J:J}function L(a){return null==a?null:a&&(a.i&128||a.vb)?a.Y(null):D(I(a))} 62 | var C=function(){function a(a,b){return null==a?null==b:a===b||ib(a,b)}var b=null,c=function(){function a(b,d,h){var l=null;2g?1:c.j(a,b,f,0)}var c=null,c=function(c,e,f,g){switch(arguments.length){case 2:return b.call(this,c,e);case 4:return a.call(this,c,e,f,g)}throw Error("Invalid arity: "+arguments.length);};c.a=b;c.j=a;return c}(); 105 | function Sc(a){return C.a(a,Wb)?Wb:function(b,c){var d=a.a?a.a(b,c):a.call(null,b,c);return"number"===typeof d?d:s(d)?-1:s(a.a?a.a(c,b):a.call(null,c,b))?1:0}} 106 | var Uc=function(){function a(a,b){if(D(b)){var c=Tc.b?Tc.b(b):Tc.call(null,b),g=Sc(a);ja(c,g);return D(c)}return J}function b(a){return c.a(Wb,a)}var c=null,c=function(c,e){switch(arguments.length){case 1:return b.call(this,c);case 2:return a.call(this,c,e)}throw Error("Invalid arity: "+arguments.length);};c.b=b;c.a=a;return c}(),oc=function(){function a(a,b,c){for(c=D(c);;)if(c){var g=H(c);b=a.a?a.a(b,g):a.call(null,b,g);if(fc(b))return $a(b);c=L(c)}else return b}function b(a,b){var c=D(b);if(c){var g= 107 | H(c),c=L(c);return z.c?z.c(a,g,c):z.call(null,a,g,c)}return a.m?a.m():a.call(null)}var c=null,c=function(c,e,f){switch(arguments.length){case 2:return b.call(this,c,e);case 3:return a.call(this,c,e,f)}throw Error("Invalid arity: "+arguments.length);};c.a=b;c.c=a;return c}();function Vc(a){a=Tc.b?Tc.b(a):Tc.call(null,a);for(var b=Math.random,c=a.length-1;0c)if(L(d))a= 118 | c,c=H(d),d=L(d);else return c>H(d);else return!1}a.h=2;a.e=function(a){var c=H(a);a=L(a);var g=H(a);a=I(a);return b(c,g,a)};a.d=b;return a}(),a=function(a,d,e){switch(arguments.length){case 1:return!0;case 2:return a>d;default:return b.d(a,d,M(arguments,2))}throw Error("Invalid arity: "+arguments.length);};a.h=2;a.e=b.e;a.b=function(){return!0};a.a=function(a,b){return a>b};a.d=b.d;return a}(),fd=function(){var a=null,b=function(){function a(c,f,g){var h=null;2=c)if(L(d))a=c,c=H(d),d=L(d);else return c>=H(d);else return!1}a.h=2;a.e=function(a){var c=H(a);a=L(a);var g=H(a);a=I(a);return b(c,g,a)};a.d=b;return a}(),a=function(a,d,e){switch(arguments.length){case 1:return!0;case 2:return a>=d;default:return b.d(a,d,M(arguments,2))}throw Error("Invalid arity: "+arguments.length);};a.h=2;a.e=b.e;a.b=function(){return!0};a.a=function(a,b){return a>=b};a.d=b.d;return a}(); 120 | function gd(a){return a-1} 121 | var hd=function(){function a(a,b){return a>b?a:b}var b=null,c=function(){function a(b,d,h){var l=null;2d?a:d,e)}a.h=2;a.e=function(a){var b=H(a);a=L(a);var d=H(a);a=I(a);return c(b,d,a)};a.d=c;return a}(),b=function(b,e,f){switch(arguments.length){case 1:return b;case 2:return a.call(this,b,e);default:return c.d(b,e,M(arguments,2))}throw Error("Invalid arity: "+arguments.length); 122 | };b.h=2;b.e=c.e;b.b=function(a){return a};b.a=a;b.d=c.d;return b}(),id=function(){function a(a,b){return a>1&1431655765;a=(a&858993459)+(a>>2&858993459);return 16843009*(a+(a>>4)&252645135)>>24} 125 | var pd=function(){var a=null,b=function(){function b(a,c,g){var h=null;2a?0:a-1>>>5<<5}function ze(a,b,c){for(;;){if(0===b)return c;var d=xe(a);d.f[0]=c;c=d;b-=5}} 206 | var Be=function Ae(b,c,d,e){var f=new ve(d.v,xa(d.f)),g=b.k-1>>>c&31;5===c?f.f[g]=e:(d=d.f[g],b=null!=d?Ae(b,c-5,d,e):ze(null,c-5,e),f.f[g]=b);return f};function Ce(a,b){throw Error("No item "+x.b(a)+" in vector of length "+x.b(b));}function De(a,b){if(b>=ye(a))return a.X;for(var c=a.root,d=a.shift;;)if(0>>d&31],d=e;else return c.f}function Ee(a,b){return 0<=b&&b>>c&31;b=Fe(b,c-5,d.f[h],e,f);g.f[h]=b}return g};function Z(a,b,c,d,e,f){this.l=a;this.k=b;this.shift=c;this.root=d;this.X=e;this.n=f;this.i=167668511;this.p=8196}k=Z.prototype;k.toString=function(){return Ib(this)};k.C=function(a,b){return Oa.c(this,b,null)};k.D=function(a,b,c){return"number"===typeof b?A.c(this,b,c):c};k.O=function(a,b){return Ee(this,b)[b&31]}; 208 | k.Z=function(a,b,c){return 0<=b&&b=this.k)return new Yb(this.X,0);var a;a:{a=this.root;for(var b=this.shift;;)if(0this.k-ye(this)){for(var c=this.X.length,d=Array(c+1),e=0;;)if(e>>5>1<b)a=new Z(null,b,5,$,a,null);else{for(var e=32,f=(new Z(null,32,5,$,a.slice(0,32),null)).Sa(null);;)if(eb||this.end<=this.start+b?Ce(b,this.end-this.start):A.a(this.Ca,this.start+b)};k.Z=function(a,b,c){return 0>b||this.end<=this.start+b?c:A.c(this.Ca,this.start+b,c)}; 220 | k.pb=function(a,b,c){var d=this.start+b;a=this.l;c=xc.c(this.Ca,d,c);b=this.start;var e=this.end,d=d+1,d=e>d?e:d;return Pe.s?Pe.s(a,c,b,d,null):Pe.call(null,a,c,b,d,null)};k.F=function(){return this.l};k.L=function(){return this.end-this.start};k.gb=function(){return this.start!==this.end?new kc(this,this.end-this.start-1,null):null};k.w=function(){var a=this.n;return null!=a?a:this.n=a=$b(this)};k.t=function(a,b){return lc(this,b)};k.N=function(){return nc(sc,this.l)}; 221 | k.S=function(a,b){return gc.a(this,b)};k.T=function(a,b,c){return gc.c(this,b,c)};k.Da=function(a,b,c){if("number"===typeof b)return Za(this,b,c);throw Error("Subvec's key for assoc must be a number.");};k.H=function(){var a=this;return function(b){return function d(e){return e===a.end?null:O(A.a(a.Ca,e),new Dd(null,function(){return function(){return d(e+1)}}(b),null,null))}}(this)(a.start)}; 222 | k.I=function(a,b){var c=this.Ca,d=this.start,e=this.end,f=this.n;return Pe.s?Pe.s(b,c,d,e,f):Pe.call(null,b,c,d,e,f)};k.G=function(a,b){var c=this.l,d=Za(this.Ca,this.end,b),e=this.start,f=this.end+1;return Pe.s?Pe.s(c,d,e,f,null):Pe.call(null,c,d,e,f,null)}; 223 | k.call=function(){var a=null,a=function(a,c,d){switch(arguments.length){case 2:return this.O(null,c);case 3:return this.Z(null,c,d)}throw Error("Invalid arity: "+arguments.length);};a.a=function(a,c){return this.O(null,c)};a.c=function(a,c,d){return this.Z(null,c,d)};return a}();k.apply=function(a,b){return this.call.apply(this,[this].concat(xa(b)))};k.b=function(a){return this.O(null,a)};k.a=function(a,b){return this.Z(null,a,b)}; 224 | function Pe(a,b,c,d,e){for(;;)if(b instanceof Oe)c=b.start+c,d=b.start+d,b=b.Ca;else{var f=Q(b);if(0>c||0>d||c>f||d>f)throw Error("Index out of bounds");return new Oe(a,b,c,d,e)}}var Ne=function(){function a(a,b,c){return Pe(null,a,b,c,null)}function b(a,b){return c.c(a,b,Q(a))}var c=null,c=function(c,e,f){switch(arguments.length){case 2:return b.call(this,c,e);case 3:return a.call(this,c,e,f)}throw Error("Invalid arity: "+arguments.length);};c.a=b;c.c=a;return c}(); 225 | function Qe(a,b){return a===b.v?b:new ve(a,xa(b.f))}function Ie(a){return new ve({},xa(a.f))}function Je(a){var b=[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null];Kc(a,0,b,0,a.length);return b}var Se=function Re(b,c,d,e){d=Qe(b.root.v,d);var f=b.k-1>>>c&31;if(5===c)b=e;else{var g=d.f[f];b=null!=g?Re(b,c-5,g,e):ze(b.root.v,c-5,e)}d.f[f]=b;return d}; 226 | function He(a,b,c,d){this.k=a;this.shift=b;this.root=c;this.X=d;this.i=275;this.p=88}k=He.prototype;k.call=function(){var a=null,a=function(a,c,d){switch(arguments.length){case 2:return this.C(null,c);case 3:return this.D(null,c,d)}throw Error("Invalid arity: "+arguments.length);};a.a=function(a,c){return this.C(null,c)};a.c=function(a,c,d){return this.D(null,c,d)};return a}();k.apply=function(a,b){return this.call.apply(this,[this].concat(xa(b)))};k.b=function(a){return this.C(null,a)}; 227 | k.a=function(a,b){return this.D(null,a,b)};k.C=function(a,b){return Oa.c(this,b,null)};k.D=function(a,b,c){return"number"===typeof b?A.c(this,b,c):c};k.O=function(a,b){if(this.root.v)return Ee(this,b)[b&31];throw Error("nth after persistent!");};k.Z=function(a,b,c){return 0<=b&&b>>a&31,n=f(a-5,l.f[m]);l.f[m]=n}return l}}(this).call(null,d.shift,d.root),d.root=a),this;if(b===d.k)return wb(this,c);throw Error("Index "+x.b(b)+" out of bounds for TransientVector of length"+x.b(d.k));}throw Error("assoc! after persistent!");}; 229 | k.Za=function(a,b,c){if("number"===typeof b)return zb(this,b,c);throw Error("TransientVector's key for assoc! must be a number.");}; 230 | k.Ma=function(a,b){if(this.root.v){if(32>this.k-ye(this))this.X[this.k&31]=b;else{var c=new ve(this.root.v,this.X),d=[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null];d[0]=b;this.X=d;if(this.k>>>5>1<=c)return new ma(this.l,this.k-1,d,null);C.a(b,this.f[e])||(d[f]=this.f[e],d[f+1]=this.f[e+1],f+=2);e+=2}}else return this}; 241 | k.Da=function(a,b,c){a=Ze(this,b);if(-1===a){if(this.kb?4:2*(b+1));Kc(this.f,0,c,0,2*b);return new mf(a,this.B,c)};k.$a=function(){var a=this.f;return nf.b?nf.b(a):nf.call(null,a)};k.Ea=function(a,b,c,d){var e=1<<(b>>>a&31);if(0===(this.B&e))return d;var f=od(this.B&e-1),e=this.f[2*f],f=this.f[2*f+1];return null==e?f.Ea(a+5,b,c,d):hf(c,e)?f:d}; 250 | k.ia=function(a,b,c,d,e,f){var g=1<<(c>>>b&31),h=od(this.B&g-1);if(0===(this.B&g)){var l=od(this.B);if(2*l>>b&31]=of.ia(a,b+5,c,d,e,f);for(m=h=0;;)if(32>h)0!==(this.B>>>h&1)&&(g[h]=null!=this.f[m]?of.ia(a,b+5,Sb(this.f[m]), 251 | this.f[m],this.f[m+1],f):this.f[m+1],m+=2),h+=1;else break;return new pf(a,l+1,g)}n=Array(2*(l+4));Kc(this.f,0,n,0,2*h);n[2*h]=d;n[2*h+1]=e;Kc(this.f,2*h,n,2*(h+1),2*(l-h));f.da=!0;m=this.Va(a);m.f=n;m.B|=g;return m}var q=this.f[2*h],r=this.f[2*h+1];if(null==q)return l=r.ia(a,b+5,c,d,e,f),l===r?this:lf.j(this,a,2*h+1,l);if(hf(d,q))return e===r?this:lf.j(this,a,2*h+1,e);f.da=!0;return lf.U(this,a,2*h,null,2*h+1,function(){var f=b+5;return qf.ca?qf.ca(a,f,q,r,c,d,e):qf.call(null,a,f,q,r,c,d,e)}())}; 252 | k.ha=function(a,b,c,d,e){var f=1<<(b>>>a&31),g=od(this.B&f-1);if(0===(this.B&f)){var h=od(this.B);if(16<=h){f=[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null];f[b>>>a&31]=of.ha(a+5,b,c,d,e);for(var l=g=0;;)if(32>g)0!==(this.B>>>g&1)&&(f[g]=null!=this.f[l]?of.ha(a+5,Sb(this.f[l]),this.f[l],this.f[l+1],e):this.f[l+1],l+=2),g+=1;else break;return new pf(null,h+1,f)}l=Array(2*(h+1));Kc(this.f, 253 | 0,l,0,2*g);l[2*g]=c;l[2*g+1]=d;Kc(this.f,2*g,l,2*(g+1),2*(h-g));e.da=!0;return new mf(null,this.B|f,l)}var m=this.f[2*g],n=this.f[2*g+1];if(null==m)return h=n.ha(a+5,b,c,d,e),h===n?this:new mf(null,this.B,jf.c(this.f,2*g+1,h));if(hf(c,m))return d===n?this:new mf(null,this.B,jf.c(this.f,2*g+1,d));e.da=!0;return new mf(null,this.B,jf.s(this.f,2*g,null,2*g+1,function(){var e=a+5;return qf.U?qf.U(e,m,n,b,c,d):qf.call(null,e,m,n,b,c,d)}()))}; 254 | k.ab=function(a,b,c){var d=1<<(b>>>a&31);if(0===(this.B&d))return this;var e=od(this.B&d-1),f=this.f[2*e],g=this.f[2*e+1];return null==f?(a=g.ab(a+5,b,c),a===g?this:null!=a?new mf(null,this.B,jf.c(this.f,2*e+1,a)):this.B===d?null:new mf(null,this.B^d,kf(this.f,e))):hf(c,f)?new mf(null,this.B^d,kf(this.f,e)):this};var of=new mf(null,0,[]);function pf(a,b,c){this.v=a;this.k=b;this.f=c}k=pf.prototype;k.Va=function(a){return a===this.v?this:new pf(a,this.k,xa(this.f))}; 255 | k.$a=function(){var a=this.f;return rf.b?rf.b(a):rf.call(null,a)};k.Ea=function(a,b,c,d){var e=this.f[b>>>a&31];return null!=e?e.Ea(a+5,b,c,d):d};k.ia=function(a,b,c,d,e,f){var g=c>>>b&31,h=this.f[g];if(null==h)return a=lf.j(this,a,g,of.ia(a,b+5,c,d,e,f)),a.k+=1,a;b=h.ia(a,b+5,c,d,e,f);return b===h?this:lf.j(this,a,g,b)}; 256 | k.ha=function(a,b,c,d,e){var f=b>>>a&31,g=this.f[f];if(null==g)return new pf(null,this.k+1,jf.c(this.f,f,of.ha(a+5,b,c,d,e)));a=g.ha(a+5,b,c,d,e);return a===g?this:new pf(null,this.k,jf.c(this.f,f,a))}; 257 | k.ab=function(a,b,c){var d=b>>>a&31,e=this.f[d];if(null!=e){a=e.ab(a+5,b,c);if(a===e)d=this;else if(null==a)if(8>=this.k)a:{e=this.f;a=e.length;b=Array(2*(this.k-1));c=0;for(var f=1,g=0;;)if(ca?d:hf(c,this.f[a])?this.f[a+1]:d}; 259 | k.ia=function(a,b,c,d,e,f){if(c===this.ya){b=sf(this.f,this.k,d);if(-1===b){if(this.f.length>2*this.k)return a=lf.U(this,a,2*this.k,d,2*this.k+1,e),f.da=!0,a.k+=1,a;c=this.f.length;b=Array(c+2);Kc(this.f,0,b,0,c);b[c]=d;b[c+1]=e;f.da=!0;f=this.k+1;a===this.v?(this.f=b,this.k=f,a=this):a=new tf(this.v,this.ya,f,b);return a}return this.f[b+1]===e?this:lf.j(this,a,b+1,e)}return(new mf(a,1<<(this.ya>>>b&31),[null,this,null,null])).ia(a,b,c,d,e,f)}; 260 | k.ha=function(a,b,c,d,e){return b===this.ya?(a=sf(this.f,this.k,c),-1===a?(a=2*this.k,b=Array(a+2),Kc(this.f,0,b,0,a),b[a]=c,b[a+1]=d,e.da=!0,new tf(null,this.ya,this.k+1,b)):C.a(this.f[a],d)?this:new tf(null,this.ya,this.k,jf.c(this.f,a+1,d))):(new mf(null,1<<(this.ya>>>a&31),[null,this])).ha(a,b,c,d,e)};k.ab=function(a,b,c){a=sf(this.f,this.k,c);return-1===a?this:1===this.k?null:new tf(null,this.ya,this.k-1,kf(this.f,ld(a,2)))}; 261 | var qf=function(){function a(a,b,c,g,h,l,m){var n=Sb(c);if(n===h)return new tf(null,n,2,[c,g,l,m]);var q=new gf;return of.ia(a,b,n,c,g,q).ia(a,b,h,l,m,q)}function b(a,b,c,g,h,l){var m=Sb(b);if(m===g)return new tf(null,m,2,[b,c,h,l]);var n=new gf;return of.ha(a,m,b,c,n).ha(a,g,h,l,n)}var c=null,c=function(c,e,f,g,h,l,m){switch(arguments.length){case 6:return b.call(this,c,e,f,g,h,l);case 7:return a.call(this,c,e,f,g,h,l,m)}throw Error("Invalid arity: "+arguments.length);};c.U=b;c.ca=a;return c}(); 262 | function uf(a,b,c,d,e){this.l=a;this.Ga=b;this.o=c;this.J=d;this.n=e;this.p=0;this.i=32374860}k=uf.prototype;k.toString=function(){return Ib(this)};k.F=function(){return this.l};k.w=function(){var a=this.n;return null!=a?a:this.n=a=$b(this)};k.t=function(a,b){return lc(this,b)};k.N=function(){return nc(J,this.l)};k.S=function(a,b){return oc.a(b,this)};k.T=function(a,b,c){return oc.c(b,c,this)};k.P=function(){return null==this.J?new Z(null,2,5,$,[this.Ga[this.o],this.Ga[this.o+1]],null):H(this.J)}; 263 | k.V=function(){if(null==this.J){var a=this.Ga,b=this.o+2;return nf.c?nf.c(a,b,null):nf.call(null,a,b,null)}var a=this.Ga,b=this.o,c=L(this.J);return nf.c?nf.c(a,b,c):nf.call(null,a,b,c)};k.H=function(){return this};k.I=function(a,b){return new uf(b,this.Ga,this.o,this.J,this.n)};k.G=function(a,b){return O(b,this)}; 264 | var nf=function(){function a(a,b,c){if(null==c)for(c=a.length;;)if(b(a.b?a.b(c):a.call(null,c))?b:c}var b=null,c=function(){function a(b,d,h,l){var m=null;3this.end&&0===this.step)return this.start;throw Error("Index out of bounds");};k.Z=function(a,b,c){return bthis.end&&0===this.step?this.start:c};k.F=function(){return this.l}; 291 | k.Y=function(){return 0this.end?new Of(this.l,this.start+this.step,this.end,this.step,null):null};k.L=function(){if(ua(lb(this)))return 0;var a=(this.end-this.start)/this.step;return Math.ceil.b?Math.ceil.b(a):Math.ceil.call(null,a)};k.w=function(){var a=this.n;return null!=a?a:this.n=a=$b(this)};k.t=function(a,b){return lc(this,b)};k.N=function(){return nc(J,this.l)}; 292 | k.S=function(a,b){return gc.a(this,b)};k.T=function(a,b,c){return gc.c(this,b,c)};k.P=function(){return null==lb(this)?null:this.start};k.V=function(){return null!=lb(this)?new Of(this.l,this.start+this.step,this.end,this.step,null):J};k.H=function(){return 0this.end?this:null};k.I=function(a,b){return new Of(b,this.start,this.end,this.step,this.n)};k.G=function(a,b){return O(b,this)}; 293 | var Pf=function(){function a(a,b,c){return new Of(null,a,b,c,null)}function b(a,b){return e.c(a,b,1)}function c(a){return e.c(0,a,1)}function d(){return e.c(0,Number.MAX_VALUE,1)}var e=null,e=function(e,g,h){switch(arguments.length){case 0:return d.call(this);case 1:return c.call(this,e);case 2:return b.call(this,e,g);case 3:return a.call(this,e,g,h)}throw Error("Invalid arity: "+arguments.length);};e.m=d;e.b=c;e.a=b;e.c=a;return e}(); 294 | function Qf(a,b){return new Z(null,2,5,$,[Nf.a(a,b),ne.a(a,b)],null)} 295 | var Rf=function(){function a(a,b){for(;;)if(D(b)&&0ka)return rb(a,"#");rb(a,c);if(D(g)){var l=H(g);b.c?b.c(l,a,f):b.call(null,l,a,f)}for(var m=L(g),n=ra.b(f)-1;;)if(!m||null!=n&&0===n){D(m)&&0===n&&(rb(a,d),rb(a,"..."));break}else{rb(a,d);var q=H(m);c=a;g=f;b.c?b.c(q,c,g):b.call(null,q,c,g);var r=L(m);c=n-1;m=r;n=c}return rb(a,e)}finally{ka=h}} 299 | var Wf=function(){function a(a,d){var e=null;1a.Ra.length)a=a.append("0");else{a=a.toString();break a}a=void 0}a=wh(a);return s(a)?a:0}(),m=(C.a(m,"-")?-1:1)*(60*function(){var a=wh(n);return s(a)?a:0}()+function(){var a=wh(q);return s(a)?a:0}());return new Z(null,8,5,$,[r,xh(1,v,12,"timestamp month field must be in range 1..12"),xh(1,a,function(){var a;if(a=0===kd(r,4))a=0!==kd(r,100)||0===kd(r,400);return uh.a?uh.a(v,a):uh.call(null, 342 | v,a)}(),"timestamp day field must be in range 1..last day in month"),xh(0,b,23,"timestamp hour field must be in range 0..23"),xh(0,c,59,"timestamp minute field must be in range 0..59"),xh(0,w,C.a(c,59)?60:59,"timestamp second field must be in range 0..60"),xh(0,y,999,"timestamp millisecond field must be in range 0..999"),m],null)} 343 | var zh,Ah=new ma(null,4,["inst",function(a){var b;if("string"===typeof a)if(b=yh(a),s(b)){a=R.c(b,0,null);var c=R.c(b,1,null),d=R.c(b,2,null),e=R.c(b,3,null),f=R.c(b,4,null),g=R.c(b,5,null),h=R.c(b,6,null);b=R.c(b,7,null);b=new Date(Date.UTC(a,c-1,d,e,f,g,h)-6E4*b)}else b=Jg.d(null,M(["Unrecognized date/time syntax: "+x.b(a)],0));else b=Jg.d(null,M(["Instance literal expects a string for its timestamp."],0));return b},"uuid",function(a){return"string"===typeof a?new eg(a):Jg.d(null,M(["UUID literal expects a string as its representation."], 344 | 0))},"queue",function(a){return Hc(a)?re.a(Ve,a):Jg.d(null,M(["Queue literal expects a vector for its elements."],0))},"js",function(a){if(Hc(a)){var b=[];a=D(a);for(var c=null,d=0,e=0;;)if(ea}]),Rh=new ma(null,7,[new B(null,"distinct","distinct",-148347594,null),ce.a(Wc,function(a){return function c(a,e){return new Dd(null,function(){return function(a,d){for(;;){var e=a,l=R.c(e, 366 | 0,null);if(e=D(e))if(Pc(d,l))l=I(e),e=d,a=l,d=e;else return O(l,c(I(e),tc.a(d,l)));else return null}}.call(null,a,e)},null,null)}(a,If)}),new B(null,"min","min",2085523049,null),function(){function a(a,b){return Wc(z.c(function(b,c){return Q(b)H(b)?Uc.b(tc.a(L(b),c)):b},sc,b))}function b(a){return z.a(hd,a)}var c=null,c=function(c,e){switch(arguments.length){case 1:return b.call(this,c);case 2:return a.call(this,c,e)}throw Error("Invalid arity: "+arguments.length);};c.b=b;c.a=a;return c}(),new B(null,"sum","sum",1777518341,null),function(a){return z.c(Zc,0,a)},new B(null,"rand","rand",-1745930995, 368 | null),function(){function a(a,b){return Wc(oe.a(a,function(){return R.a(b,nd(Q(b)))}))}var b=null,b=function(b,d){switch(arguments.length){case 1:return R.a(b,nd(Q(b)));case 2:return a.call(this,b,d)}throw Error("Invalid arity: "+arguments.length);};b.b=function(a){return R.a(a,nd(Q(a)))};b.a=a;return b}(),new B(null,"sample","sample",1719555128,null),function(a,b){return Wc(le.a(a,Vc(b)))},new B(null,"count","count",-514511684,null),Q],null),Th=function Sh(b,c){if(s(function(){var c=new Z(null,2, 369 | 5,$,[new B(null,"_","_",-1201019570,null),new B(null,"...","...",-1926939749,null)],null);return Mh.a?Mh.a(c,b):Mh.call(null,c,b)}())||s(function(){var c=new Z(null,1,5,$,[new Z(null,1,5,$,[new B(null,"*","*",345799209,null)],null)],null);return Mh.a?Mh.a(c,b):Mh.call(null,c,b)}()))return z.a(Oh,Y.a(function(){return function(c){return Sh(H(b),c)}}(Mh,b),c));if(s(function(){var c=new Z(null,1,5,$,[new B(null,"*","*",345799209,null)],null);return Mh.a?Mh.a(c,b):Mh.call(null,c,b)}()))return z.a(Ph, 370 | Y.c(function(){return function(b,c){return Sh(b,c)}}(Mh,b),b,c));if(s(function(){var c=new B(null,"_","_",-1201019570,null);return Mh.a?Mh.a(c,b):Mh.call(null,c,b)}()))return new Fh(new ef([b,0]),new Z(null,1,5,$,[[c]],null));throw Error("No matching clause: "+x.b(b));};function Uh(a){a="string"===typeof a?ah(new Gg(a,[],-1),!1,null):a;return dg(qc,a)} 371 | function Vh(a,b){var c=R.c(b,0,null),d=R.c(b,1,null);return Jh(c)?ue.s(a,new Z(null,1,5,$,[kg],null),xc,c,d):C.a(new B(null,"%","%",-950237169,null),c)?xc.c(a,mg,Uh(d)):ue.j(a,new Z(null,1,5,$,[ng],null),tc,Th(c,d))}function Wh(a){return 1===Q(a)?function(a){return function(c){return c[a]}}(H(a)):function(a){return function(c){return Rd.b(a.map(function(){return function(a){return c[a]}}(a)))}}(Tc(a))} 372 | function Xh(a,b){for(var c=Wh(a),d=b,e=vb(bf);;){var f=H(d);if(s(f)){var g;g=f;g=c.b?c.b(g):c.call(null,g);d=L(d);e=Ud.c(e,g,tc.a(S.c(e,g,J),f))}else return xb(e)}} 373 | function Yh(a,b){var c=hg.b(a),d=hg.b(b),e=rg.b(a),f=rg.b(b),g=Wc(Hh(rg.b(a),rg.b(b))),h=Y.a(e,g),l=Y.a(f,g),m=Cf(e),n=Wc(Cg.a(Jf(Cf(f)),Jf(Cf(e)))),q=Tc(Y.a(e,m)),r=Tc(Y.a(f,n)),v=Xh(h,c),w=Wh(l),c=Sd(z.c(function(a,b,c,d,e,f,g,h,l,m,n,q,r){return function(v,w){var ia=r.b?r.b(w):r.call(null,w),Ga=S.a(q,ia);return s(Ga)?z.c(function(a,b,c,d,e,f,g,h,l,m,n,q,r,v){return function(a,b){return Td.a(a,Nh(b,r,w,v))}}(Ga,Ga,ia,a,b,c,d,e,f,g,h,l,m,n,q,r),v,Ga):v}}(c,d,e,f,g,h,l,m,n,q,r,v,w),vb(sc),d));return new Fh(Lf(Qd.a(m, 374 | n),Pf.m()),c)}function Zh(a,b,c){var d=se.a(function(a){return a instanceof B?null:a},b);b=re.a(bf,pe.a(function(){return function(a){var b=R.c(a,0,null);R.c(a,1,null);return Kh(b)}}(d),Y.c(Le,b,Pf.m())));return wg(a,d,function(a,b){return function(a){a=new Fh(b,a);return c.b?c.b(a):c.call(null,a)}}(d,b))} 375 | function $h(a,b,c){a=pe.a(function(a){a:{for(var c=b;;){var d=a;if(s(s(d)?c:d)){var d=H(a),h=H(c);if(h instanceof B||C.a(d,h))a=L(a),c=L(c);else{a=!1;break a}}else{a=!0;break a}}a=void 0}return a},a);var d=re.a(bf,pe.a(function(){return function(a){var b=R.c(a,0,null);R.c(a,1,null);return Kh(b)}}(a),Y.c(Le,b,Pf.m())));a=new Fh(d,Y.a(Tc,a));return c.b?c.b(a):c.call(null,a)} 376 | function ai(a,b,c){var d=Jh(H(b))?new Z(null,2,5,$,[H(b),L(b)],null):new Z(null,2,5,$,[new B(null,"$","$",-1580747756,null),b],null);b=R.c(d,0,null);d=R.c(d,1,null);a=S.a(kg.b(a),b);return a instanceof xg?Zh(a,d,c):$h(a,d,c)}function bi(a,b){for(var c=a,d=b,e=sc;;){var f=H(c);if(s(f))s(Xd(Hh(rg.b(d),rg.b(f))))?(c=L(c),d=Yh(f,d)):(c=L(c),e=tc.a(e,f));else return tc.a(e,d)}}function ci(a,b){var c=H(pe.a(function(a){return Pc(rg.b(a),b)},ng.b(a)));return s(c)?H(hg.b(c))[rg.b(c).call(null,b)]:null} 377 | function di(a,b){var c=pe.a(function(a){return!Dc(zg.a(Jf(b),Jf(Cf(rg.b(a)))))},ng.b(a)),d=z.a(Ph,c);return new Z(null,2,5,$,[ue.c(a,new Z(null,1,5,$,[ng],null),function(a){return function(b){return qe.a(Jf(a),b)}}(c,d)),d],null)}function ei(a,b,c,d){return function(e){var f=Y.a(function(c){if(c instanceof B){var d=S.a(kg.b(a),c);return s(d)?d:e[S.a(rg.b(b),c)]}return c},d);return T.a(c,f)}} 378 | function fi(a,b){var c=R.c(b,0,null),d=R.c(c,0,null),e=qd(c),f=function(){var b=S.a(Qh,d);return s(b)?b:ci(a,d)}(),g=di(a,pe.a(Ub,e)),h=R.c(g,0,null),l=R.c(g,1,null),c=ue.c(l,new Z(null,1,5,$,[hg],null),function(a,b,c,d,e,f,g,h,l){return function(a){return pe.a(l,a)}}(b,c,d,e,f,g,h,l,ei(h,l,f,e)));return ue.j(h,new Z(null,1,5,$,[ng],null),tc,c)} 379 | function gi(a,b){var c=R.c(b,0,null),d=R.c(c,0,null),e=qd(c),f=R.c(b,1,null),g=function(){var b=S.a(Qh,d);return s(b)?b:ci(a,d)}(),h=di(a,pe.a(Ub,e)),l=R.c(h,0,null),m=R.c(h,1,null),c=z.a(Oh,Y.a(function(a,b,c,d,e,f,g,h,l,m){return function(a){var b=m.b?m.b(a):m.call(null,a),b=Th(e,b);return Ph(new Fh(rg.b(l),new Z(null,1,5,$,[a],null)),b)}}(b,c,d,e,f,g,h,l,m,ei(l,m,g,e)),hg.b(m)));return ue.j(l,new Z(null,1,5,$,[ng],null),tc,c)}function hi(a,b){return Fc(b)&&Pc(mg.b(a),Jh(H(b))?pc(b):H(b))} 380 | var ii=ie.b?ie.b(0):ie.call(null,0); 381 | function ji(a,b){var c=R.c(a,0,null),d=qd(a),e=ke.a(ii,dc),f=S.a(mg.b(b),c);return function(a,b,c,d,e){return function r(f){return new Dd(null,function(a,b,c,d,e){return function(){for(;;){var g=D(f);if(g){var h=g;if(Ic(h)){var l=Bb(h),m=Q(l),n=Hd(m);return function(){for(var f=0;;)if(f> (for [tuple %1] 26 | (into-array tuple)) 27 | (into-array) 28 | (callback)) sources)))) -------------------------------------------------------------------------------- /src/datascript/preamble.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dataquery 3 | * Copyright 2014 David Alan Hjelle & Icon Systems, Inc. 4 | * 5 | * Query Engine originally from Datascript v0.4.0 6 | * which is Copyright 2014 Nikita Prokopov 7 | * 8 | * Licensed under Eclipse Public License; 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://github.com/dahjelle/dataquery/blob/master/LICENSE 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | -------------------------------------------------------------------------------- /src/datascript/query.cljs: -------------------------------------------------------------------------------- 1 | (ns datascript.query 2 | (:require 3 | [clojure.set :as set] 4 | [clojure.walk :as walk] 5 | [datascript.core :as dc] 6 | [cljs.reader])) 7 | 8 | ;; Records 9 | 10 | (defrecord Context [rels sources rules]) 11 | ;; attrs: 12 | ;; {?e 0, ?v 1} or {?e2 "a", ?age "v"} 13 | ;; tuples: 14 | ;; [ #js [1 "Ivan" 5 14] ... ] 15 | ;; or [ (Datom. 2 "Oleg" 1 55) ... ] 16 | (defrecord Relation [attrs tuples]) 17 | 18 | 19 | ;; Utilities 20 | (enable-console-print!) 21 | 22 | (defn async-reduce [reducer-function initial-value collection callback] 23 | (let [accumulator (atom initial-value) 24 | items-processed (atom 0)] 25 | (doseq [item collection] 26 | (reducer-function accumulator item (fn [result] 27 | (swap! accumulator (fn [] result)) ; cheating a bit in the update function 28 | (swap! items-processed inc) 29 | (if (= @items-processed (count collection)) 30 | (callback @accumulator))))))) 31 | 32 | (defn intersect-keys [attrs1 attrs2] 33 | (set/intersection (set (keys attrs1)) 34 | (set (keys attrs2)))) 35 | (defn concatv [& xs] 36 | (vec (apply concat xs))) 37 | 38 | (defn source? [sym] 39 | (and (symbol? sym) 40 | (= \$ (first (name sym))))) 41 | 42 | (defn free-var? [sym] 43 | (and (symbol? sym) 44 | (= \? (first (name sym))) 45 | (not= '_ sym))) 46 | 47 | (defn- looks-like? [pattern form] 48 | (cond 49 | (= '_ pattern) 50 | true 51 | (= '[*] pattern) 52 | (sequential? form) 53 | (sequential? pattern) 54 | (and (sequential? form) 55 | (= (count form) (count pattern)) 56 | (every? (fn [[pattern-el form-el]] (looks-like? pattern-el form-el)) 57 | (map vector pattern form))) 58 | (symbol? pattern) 59 | (= form pattern) 60 | :else ;; (predicate? pattern) 61 | (pattern form))) 62 | 63 | ;; Relation algebra 64 | 65 | (defn join-tuples [t1 idxs1 t2 idxs2] 66 | (let [l1 (alength idxs1) 67 | l2 (alength idxs2) 68 | res (js/Array. (+ l1 l2))] 69 | (dotimes [i l1] 70 | (aset res i (aget t1 (aget idxs1 i)))) ;; FIXME aget 71 | (dotimes [i l2] 72 | (aset res (+ l1 i) (aget t2 (aget idxs2 i)))) ;; FIXME aget 73 | res)) 74 | 75 | (defn sum-rel [a b] 76 | (Relation. (:attrs a) (concat (:tuples a) (:tuples b)))) 77 | 78 | (defn prod-rel [rel1 rel2] 79 | (let [attrs1 (keys (:attrs rel1)) 80 | attrs2 (keys (:attrs rel2)) 81 | idxs1 (to-array (map (:attrs rel1) attrs1)) 82 | idxs2 (to-array (map (:attrs rel2) attrs2))] 83 | (Relation. 84 | (zipmap (concat attrs1 attrs2) (range)) 85 | (for [t1 (:tuples rel1) 86 | t2 (:tuples rel2)] 87 | (join-tuples t1 idxs1 t2 idxs2))))) 88 | 89 | ;; built-ins 90 | 91 | (defn- -differ? [& xs] 92 | (let [l (count xs)] 93 | (not= (take (/ l 2) xs) (drop (/ l 2) xs)))) 94 | 95 | (def built-ins { 96 | '= =, '== ==, 'not= not=, '!= not=, '< <, '> >, '<= <=, '>= >=, '+ +, '- -, 97 | '* *, '/ /, 'quot quot, 'rem rem, 'mod mod, 'inc inc, 'dec dec, 'max max, 'min min, 98 | 'zero? zero?, 'pos? pos?, 'neg? neg?, 'even? even?, 'odd? odd?, 'true? true?, 99 | 'false? false?, 'nil? nil?, 'str str, 'identity identity, 'vector vector, 100 | '-differ? -differ?}) 101 | 102 | (def built-in-aggregates { 103 | 'distinct (comp vec distinct) 104 | 'min (fn 105 | ([coll] (reduce min coll)) 106 | ([n coll] 107 | (vec 108 | (reduce (fn [acc x] 109 | (cond 110 | (< (count acc) n) (sort (conj acc x)) 111 | (< x (last acc)) (sort (conj (butlast acc) x)) 112 | :else acc)) 113 | [] coll)))) 114 | 'max (fn 115 | ([coll] (reduce max coll)) 116 | ([n coll] 117 | (vec 118 | (reduce (fn [acc x] 119 | (cond 120 | (< (count acc) n) (sort (conj acc x)) 121 | (> x (first acc)) (sort (conj (next acc) x)) 122 | :else acc)) 123 | [] coll)))) 124 | 'sum #(reduce + 0 %) 125 | 'rand (fn 126 | ([coll] (rand-nth coll)) 127 | ([n coll] (vec (repeatedly n #(rand-nth coll))))) 128 | 'sample (fn [n coll] 129 | (vec (take n (shuffle coll)))) 130 | 'count count}) 131 | 132 | 133 | ;; 134 | 135 | (defn in->rel [form value] 136 | (condp looks-like? form 137 | '[_ ...] ;; collection binding [?x ...] 138 | (reduce sum-rel 139 | (map #(in->rel (first form) %) value)) 140 | '[[*]] ;; relation binding [[?a ?b]] 141 | (reduce sum-rel 142 | (map #(in->rel (first form) %) value)) 143 | '[*] ;; tuple binding [?a ?b] 144 | (reduce prod-rel 145 | (map #(in->rel %1 %2) form value)) 146 | '_ ;; regular binding ?x 147 | (Relation. {form 0} [#js [value]]))) 148 | 149 | (defn parse-rules [rules] 150 | (let [rules (if (string? rules) (cljs.reader/read-string rules) rules)] ;; for datascript.js interop 151 | (group-by ffirst rules))) 152 | 153 | (defn parse-in [context [in value]] 154 | (cond 155 | (source? in) 156 | (update-in context [:sources] assoc in value) 157 | (= '% in) 158 | (assoc context :rules (parse-rules value)) 159 | :else 160 | (update-in context [:rels] conj (in->rel in value)))) 161 | 162 | (defn parse-ins [context ins values] 163 | (reduce parse-in context (map vector ins values))) 164 | 165 | ;; 166 | 167 | (defn tuple-key-fn [idxs] 168 | (if (== (count idxs) 1) 169 | (let [idx (first idxs)] 170 | (fn [tuple] 171 | (aget tuple idx))) 172 | (let [idxs (to-array idxs)] 173 | (fn [tuple] 174 | (list* (.map idxs #(aget tuple %))))))) ;; FIXME aget 175 | 176 | (defn hash-attrs [idxs tuples] 177 | (let [key-fn (tuple-key-fn idxs)] 178 | (loop [tuples tuples 179 | hash-table (transient {})] 180 | (if-let [tuple (first tuples)] 181 | (let [key (key-fn tuple)] 182 | (recur (next tuples) 183 | (assoc! hash-table key (conj (get hash-table key '()) tuple)))) 184 | (persistent! hash-table))))) 185 | 186 | (defn hash-join [rel1 rel2] 187 | (let [tuples1 (:tuples rel1) 188 | tuples2 (:tuples rel2) 189 | attrs1 (:attrs rel1) 190 | attrs2 (:attrs rel2) 191 | common-attrs (vec (intersect-keys (:attrs rel1) (:attrs rel2))) 192 | common-idxs1 (map attrs1 common-attrs) 193 | common-idxs2 (map attrs2 common-attrs) 194 | keep-attrs1 (keys attrs1) 195 | keep-attrs2 (vec (set/difference (set (keys attrs2)) (set (keys attrs1)))) 196 | keep-idxs1 (to-array (map attrs1 keep-attrs1)) 197 | keep-idxs2 (to-array (map attrs2 keep-attrs2)) 198 | hash (hash-attrs common-idxs1 tuples1) 199 | key-fn (tuple-key-fn common-idxs2) 200 | new-tuples (->> 201 | (reduce (fn [acc tuple2] 202 | (let [key (key-fn tuple2)] 203 | (if-let [tuples1 (get hash key)] 204 | (reduce (fn [acc tuple1] 205 | (conj! acc (join-tuples tuple1 keep-idxs1 tuple2 keep-idxs2))) 206 | acc tuples1) 207 | acc))) 208 | (transient []) tuples2) 209 | (persistent!))] 210 | (Relation. (zipmap (concat keep-attrs1 keep-attrs2) (range)) 211 | new-tuples))) 212 | 213 | (defn lookup-pattern-db [db pattern callback] 214 | ;; TODO optimize with bound attrs min/max values here 215 | (let [search-pattern (mapv #(if (symbol? %) nil %) pattern) 216 | attr->idx (->> (map vector pattern (range)) 217 | (filter (fn [[s _]] (free-var? s))) 218 | (into {}))] 219 | (dc/-search db search-pattern (fn [datoms] (callback (Relation. attr->idx datoms)))))) 220 | 221 | (defn matches-pattern? [pattern tuple] 222 | (loop [tuple tuple 223 | pattern pattern] 224 | (if (and tuple pattern) 225 | (let [t (first tuple) 226 | p (first pattern)] 227 | (if (or (symbol? p) (= t p)) 228 | (recur (next tuple) (next pattern)) 229 | false)) 230 | true))) 231 | 232 | (defn lookup-pattern-coll [coll pattern callback] 233 | (let [data (filter #(matches-pattern? pattern %) coll) 234 | attr->idx (->> (map vector pattern (range)) 235 | (filter (fn [[s _]] (free-var? s))) 236 | (into {}))] 237 | (callback (Relation. attr->idx (map to-array data))))) ;; FIXME to-array 238 | 239 | (defn lookup-pattern [context clause callback] 240 | (let [[source-sym pattern] (if (source? (first clause)) 241 | [(first clause) (next clause)] 242 | ['$ clause]) 243 | source (get (:sources context) source-sym)] 244 | (cond 245 | (instance? dc/DB source) 246 | (lookup-pattern-db source pattern callback) 247 | :else 248 | (lookup-pattern-coll source pattern callback)))) 249 | 250 | (defn collapse-rels [rels new-rel] 251 | (loop [rels rels 252 | new-rel new-rel 253 | acc []] 254 | (if-let [rel (first rels)] 255 | (if (not-empty (intersect-keys (:attrs new-rel) (:attrs rel))) 256 | (recur (next rels) (hash-join rel new-rel) acc) 257 | (recur (next rels) new-rel (conj acc rel))) 258 | (conj acc new-rel)))) 259 | 260 | 261 | (defn- context-resolve-val [context sym] 262 | ;; TODO raise if more than one tuple bound 263 | (when-let [rel (first (filter #(contains? (:attrs %) sym) (:rels context)))] 264 | (aget (first (:tuples rel)) ((:attrs rel) sym)))) 265 | 266 | (defn- rel-contains-attrs? [rel attrs] 267 | (not (empty? (set/intersection (set attrs) (set (keys (:attrs rel))))))) 268 | 269 | (defn- rel-prod-by-attrs [context attrs] 270 | (let [rels (filter #(rel-contains-attrs? % attrs) (:rels context)) 271 | production (reduce prod-rel rels)] 272 | [(update-in context [:rels] #(remove (set rels) %)) production])) 273 | 274 | (defn -call-fn [context rel f args] 275 | (fn [tuple] 276 | ;; TODO raise if not all args are bound 277 | (let [resolved-args (map #(if (symbol? %) 278 | (or 279 | (get (:sources context) %) 280 | (aget tuple (get (:attrs rel) %))) 281 | %) 282 | args)] 283 | (apply f resolved-args)))) 284 | 285 | (defn filter-by-pred [context clause] 286 | (let [[[f & args]] clause 287 | pred (or (get built-ins f) 288 | (context-resolve-val context f)) 289 | [context production] (rel-prod-by-attrs context (filter symbol? args)) 290 | tuple-pred (-call-fn context production pred args) 291 | new-rel (update-in production [:tuples] #(filter tuple-pred %))] 292 | (update-in context [:rels] conj new-rel))) 293 | 294 | (defn bind-by-fn [context clause] 295 | (let [[[f & args] out] clause 296 | fun (or (get built-ins f) 297 | (context-resolve-val context f)) 298 | [context production] (rel-prod-by-attrs context (filter symbol? args)) 299 | tuple-fn (-call-fn context production fun args) 300 | new-rel (->> (:tuples production) 301 | (map #(let [val (tuple-fn %) 302 | rel (in->rel out val)] 303 | (prod-rel (Relation. (:attrs production) [%]) rel))) 304 | (reduce sum-rel))] 305 | (update-in context [:rels] conj new-rel))) 306 | 307 | 308 | 309 | ;;; RULES 310 | 311 | (defn rule? [context clause] 312 | (and (sequential? clause) 313 | (contains? (:rules context) 314 | (if (source? (first clause)) 315 | (second clause) 316 | (first clause))))) 317 | 318 | (declare -collect) 319 | (declare -resolve-clause) 320 | 321 | (def rule-seqid (atom 0)) 322 | 323 | (defn expand-rule [clause context used-args] 324 | (let [[rule & call-args] clause 325 | seqid (swap! rule-seqid inc) 326 | branches (get (:rules context) rule)] 327 | (for [branch branches 328 | :let [[[_ & rule-args] & clauses] branch 329 | replacements (zipmap rule-args call-args)]] 330 | (walk/postwalk 331 | #(if (free-var? %) 332 | (or (replacements %) 333 | (symbol (str (name %) "__auto__" seqid))) 334 | %) 335 | clauses)))) 336 | 337 | (defn remove-pairs [xs ys] 338 | (let [pairs (->> (map vector xs ys) 339 | (remove (fn [[x y]] (= x y))))] 340 | [(map first pairs) 341 | (map second pairs)])) 342 | 343 | (defn rule-gen-guards [rule-clause used-args] 344 | (let [[rule & call-args] rule-clause 345 | prev-call-args (get used-args rule)] 346 | (for [prev-args prev-call-args 347 | :let [[call-args prev-args] (remove-pairs call-args prev-args)]] 348 | [(concat ['-differ?] call-args prev-args)]))) 349 | 350 | (defn walk-collect [form pred] 351 | (let [res (atom [])] 352 | (walk/postwalk #(do (when (pred %) (swap! res conj %)) %) form) 353 | @res)) 354 | 355 | (defn split-guards [clauses guards] 356 | (let [bound-vars (set (walk-collect clauses free-var?)) 357 | pred (fn [[[_ & vars]]] (every? bound-vars vars))] 358 | [(filter pred guards) 359 | (remove pred guards)])) 360 | 361 | (defn solve-rule [context clause callback] 362 | (let [final-attrs (filter free-var? clause) 363 | final-attrs-map (zipmap final-attrs (range)) 364 | solve (fn [prefix-context clauses callback] 365 | (async-reduce -resolve-clause prefix-context clauses callback)) 366 | empty-rels? (fn [context] 367 | (some #(empty? (:tuples %)) (:rels context)))] 368 | ((defn looper [stack rel callback] ; looper is a terrible name, but replacing the use of recur so can use callbacks 369 | (if-let [frame (first stack)] 370 | (let [[clauses [rule-clause & next-clauses]] (split-with #(not (rule? context %)) (:clauses frame))] 371 | (if (nil? rule-clause) 372 | 373 | ;; no rules --> expand, collect, sum 374 | (solve (:prefix-context frame) clauses (fn [context] 375 | (let [tuples (-collect context final-attrs) 376 | new-rel (Relation. final-attrs-map tuples)] 377 | (looper (next stack) (sum-rel rel new-rel) callback)))) 378 | 379 | ;; has rule --> add guards --> check if dead --> expand rule --> push to stack, recur 380 | (let [[rule & call-args] rule-clause 381 | guards (rule-gen-guards rule-clause (:used-args frame)) 382 | [active-gs pending-gs] (split-guards (concat (:prefix-clauses frame) clauses) 383 | (concat guards (:pending-guards frame)))] 384 | (if (some #(= % '[(-differ?)]) active-gs) ;; trivial always false case like [(not= [?a ?b] [?a ?b])] 385 | 386 | ;; this branch has no data, just drop it from stack 387 | (looper (next stack) rel callback) 388 | 389 | (let [prefix-clauses (concat clauses active-gs)] 390 | (solve (:prefix-context frame) prefix-clauses (fn [prefix-context] 391 | (if (empty-rels? prefix-context) 392 | 393 | ;; this branch has no data, just drop it from stack 394 | (looper (next stack) rel callback) 395 | 396 | ;; need to expand rule to branches 397 | (let [used-args (assoc (:used-args frame) rule 398 | (conj (get (:used-args frame) rule []) call-args)) 399 | branches (expand-rule rule-clause context used-args)] 400 | (looper (concat 401 | (for [branch branches] 402 | {:prefix-clauses prefix-clauses 403 | :prefix-context prefix-context 404 | :clauses (concatv branch next-clauses) 405 | :used-args used-args 406 | :pending-guards pending-gs}) 407 | (next stack)) 408 | rel callback)))))))))) 409 | (callback rel))) 410 | (list {:prefix-clauses [] 411 | :prefix-context (assoc context :rels []) 412 | :clauses [clause] 413 | :used-args {} 414 | :pending-guards {}}) 415 | (Relation. final-attrs-map []) 416 | callback))) 417 | 418 | (defn -resolve-clause [context clause callback] 419 | (condp looks-like? clause 420 | '[[*]] ;; predicate [(pred ?a ?b ?c)] 421 | (callback (filter-by-pred @context clause)) 422 | 423 | '[[*] _] ;; function [(fn ?a ?b) ?res] 424 | (callback (bind-by-fn @context clause)) 425 | 426 | '[*] ;; pattern 427 | (lookup-pattern @context clause (fn [datoms] 428 | (callback (update-in @context [:rels] collapse-rels datoms)))))) 429 | 430 | ;TODO not really sure it is valid to replace all these context's with @context's, but it seems to work 431 | (defn resolve-clause [context clause callback] 432 | (if (rule? @context clause) 433 | (let [[source rule] (if (source? (first clause)) 434 | [(first clause) (next clause)] 435 | ['$ clause]) 436 | source (get-in @context [:sources source])] 437 | (solve-rule (assoc @context :sources {'$ source}) rule (fn [rel] 438 | (callback (update-in @context [:rels] collapse-rels rel))))) 439 | (-resolve-clause context clause callback))) 440 | 441 | (defn -q [context clauses callback] 442 | (async-reduce resolve-clause context clauses callback)) 443 | 444 | (defn -collect 445 | ([context symbols] 446 | (let [rels (:rels context)] 447 | (-collect [(make-array (count symbols))] rels symbols))) 448 | ([acc rels symbols] 449 | (if-let [rel (first rels)] 450 | (let [keep-attrs (select-keys (:attrs rel) symbols)] 451 | (if (empty? keep-attrs) 452 | (recur acc (next rels) symbols) 453 | (let [copy-map (to-array (map #(get keep-attrs %) symbols)) 454 | len (count symbols)] 455 | (recur (for [t1 acc 456 | t2 (:tuples rel)] 457 | (let [res (aclone t1)] 458 | (dotimes [i len] 459 | (when-let [idx (aget copy-map i)] 460 | (aset res i (nth t2 idx)))) 461 | res)) 462 | (next rels) 463 | symbols)))) 464 | acc))) 465 | 466 | (defn collect [context symbols] 467 | (->> (-collect context symbols) 468 | (map vec) 469 | set)) 470 | 471 | (defn find-attrs [q] 472 | (concat 473 | (map #(if (sequential? %) (last %) %) (:find q)) 474 | (:with q))) 475 | 476 | (defn -aggregate [q context tuples] 477 | (mapv (fn [form fixed-value i] 478 | (if (sequential? form) 479 | (let [[f & args] form 480 | vals (map #(nth % i) tuples) 481 | args (map #(if (symbol? %) (context-resolve-val context %) %) 482 | (butlast args)) 483 | f (or (built-in-aggregates f) 484 | (context-resolve-val context f))] 485 | (apply f (concat args [vals]))) 486 | fixed-value)) 487 | (:find q) 488 | (first tuples) 489 | (range))) 490 | 491 | (defn aggregate [q context resultset] 492 | (let [group-idxs (->> (map #(when-not (sequential? %1) %2) (:find q) (range)) 493 | (remove nil?)) 494 | group-fn (fn [tuple] 495 | (map #(nth tuple %) group-idxs)) 496 | grouped (group-by group-fn resultset)] 497 | (for [[_ tuples] grouped] 498 | (-aggregate q context tuples)))) 499 | 500 | (defn parse-query [query] 501 | (loop [parsed {}, key nil, qs query] 502 | (if-let [q (first qs)] 503 | (if (keyword? q) 504 | (recur parsed q (next qs)) 505 | (recur (update-in parsed [key] (fnil conj []) q) key (next qs))) 506 | parsed))) 507 | 508 | (defn q [q callback & inputs] 509 | (let [q (if (sequential? q) (parse-query q) q) 510 | find (find-attrs q) 511 | ins (:in q '[$]) 512 | wheres (:where q) 513 | context (-> (Context. [] {} {}) 514 | (parse-ins ins inputs))] 515 | (-q context wheres (fn [data] 516 | (callback 517 | (let [resultset (collect data find)] 518 | (cond->> resultset 519 | (:with q) 520 | (mapv #(subvec % 0 (count (:find q)))) 521 | (not-empty (filter sequential? (:find q))) 522 | (aggregate q context)))))))) 523 | -------------------------------------------------------------------------------- /test/js/index.js: -------------------------------------------------------------------------------- 1 | var d = require( "../../web/datascript.min.js" ); 2 | var PouchDB = require( "pouchdb" ); 3 | var db = new PouchDB( "http://test:test@localhost:5984/large/" ); 4 | 5 | var searchPouchIndex = function( db, index ) { 6 | return function( search, callback ) { 7 | var view = index + "/" + index; 8 | var endkey = search.map( function( el ) { 9 | if ( el === null ) { 10 | return {}; 11 | } 12 | return el; 13 | }); 14 | db.query( view, { 15 | startkey: search, 16 | endkey: endkey 17 | }, function( error, data ) { 18 | callback( data.rows.map( function( el ) { 19 | return el.value; 20 | }) ); 21 | }) 22 | } 23 | }; 24 | 25 | var initPouchDB = function() { 26 | return d.db( db, searchPouchIndex( db, "eav" ), searchPouchIndex( db, "ave" ) ); 27 | }; 28 | 29 | var datalog = initPouchDB(); 30 | d.q( '[:find ?id :in :where [?id "last_name" "benson"]]', function( data ) { 31 | if ( JSON.stringify( data ) === JSON.stringify( [["h-J3zsqvJU"],["h-KNsH97zd"],["h-KYgWG520"],["h-okf5vK7R"],["h-PMx7u50O"],["h-QHXBZ9vr"]] ) ) { 32 | console.log( "Passed!" ); 33 | } else { 34 | console.log( "Failed.", data ); 35 | } 36 | }, datalog ); 37 | -------------------------------------------------------------------------------- /test/test/datascript.cljs: -------------------------------------------------------------------------------- 1 | (ns test.datascript 2 | (:require-macros 3 | [cemerick.cljs.test :refer (is deftest with-test run-tests testing test-var done)]) 4 | (:require 5 | [datascript.core :as dc] 6 | [datascript :as d] 7 | [cljs.reader] 8 | [cemerick.cljs.test :as t])) 9 | 10 | (enable-console-print!) 11 | 12 | (defn search-index [index] 13 | (fn [search callback] 14 | (callback (let [search-start search 15 | search-stop (mapv #(if (nil? %) "\uffff" %) search)] 16 | (vec (vals (subseq index >= search-start <= search-stop))))))) 17 | 18 | (defn add-record [db e a v] 19 | (let [record (to-array [e a v]) 20 | indexes (:db db) 21 | ave (assoc (:ave indexes) (mapv str [a v e]) record) 22 | eav (assoc (:eav indexes) (mapv str [e a v]) record)] 23 | (dc/DB. (hash-map :eav eav :ave ave) 24 | (search-index eav) 25 | (search-index ave)))) 26 | 27 | (defn init-db [] 28 | (let [ave (sorted-map) 29 | eav (sorted-map)] 30 | (dc/DB. (hash-map :eav eav :ave ave) 31 | (search-index eav) 32 | (search-index ave)))) 33 | 34 | (deftest test-joins 35 | (let [db (-> (init-db) 36 | (add-record 1 "name" "Ivan") 37 | (add-record 2 "name" "Petr") 38 | (add-record 3 "name" "Ivan") 39 | (add-record 1 "age" 15) 40 | (add-record 2 "age" 37) 41 | (add-record 3 "age" 37) 42 | (add-record 4 "age" 15))] 43 | (d/q '[:find ?w 44 | :where [?w "name"]] (fn [result] 45 | (is (= result #{[1] [2] [3]}))) db) 46 | (d/q '[:find ?e ?v 47 | :where [?e "name" "Ivan"] 48 | [?e "age" ?v]] (fn [result] 49 | (is (= result #{[1 15] [3 37]}))) db) 50 | (d/q '[:find ?e1 ?e2 51 | :where [?e1 "name" ?n] 52 | [?e2 "name" ?n]] (fn [result] 53 | (is (= result #{[1 1] [2 2] [3 3] [1 3] [3 1]}))) db) 54 | (d/q '[:find ?e ?e2 ?n 55 | :where [?e "name" "Ivan"] 56 | [?e "age" ?a] 57 | [?e2 "age" ?a] 58 | [?e2 "name" ?n]] (fn [result] 59 | (is (= result #{[1 1 "Ivan"] 60 | [3 3 "Ivan"] 61 | [3 2 "Petr"]}))) db) 62 | )) 63 | 64 | (deftest test-q-many 65 | (let [db (-> (init-db) 66 | (add-record 1 "name" "Ivan") 67 | (add-record 1 "aka" "ivolga") 68 | (add-record 1 "aka" "pi") 69 | (add-record 2 "name" "Petr") 70 | (add-record 2 "aka" "porosenok") 71 | (add-record 2 "aka" "pi") 72 | )] 73 | (d/q '[:find ?n1 ?n2 74 | :where [?e1 "aka" ?x] 75 | [?e2 "aka" ?x] 76 | [?e1 "name" ?n1] 77 | [?e2 "name" ?n2]] (fn [result] 78 | (is (= result #{["Ivan" "Ivan"] 79 | ["Petr" "Petr"] 80 | ["Ivan" "Petr"] 81 | ["Petr" "Ivan"]}))) db) 82 | )) 83 | 84 | (deftest test-q-coll 85 | (let [db (-> (init-db) 86 | (add-record 1 "name" "Ivan") 87 | (add-record 1 "age" 19) 88 | (add-record 1 "aka" "dragon_killer_94") 89 | (add-record 1 "aka" "-=autobot=-") 90 | )] 91 | (d/q '[:find ?n ?a 92 | :where [?e "aka" "dragon_killer_94"] 93 | [?e "name" ?n] 94 | [?e "age" ?a]] (fn [result] 95 | (is (= result #{["Ivan" 19]}))) db) 96 | )) 97 | 98 | ;(deftest test-q-in 99 | ; (let [db (-> (d/empty-db) 100 | ; (d/db-with [ { :db/id 1, :name "Ivan", :age 15 } 101 | ; { :db/id 2, :name "Petr", :age 37 } 102 | ; { :db/id 3, :name "Ivan", :age 37 }])) 103 | ; query '{:find [?e] 104 | ; :in [$ ?attr ?value] 105 | ; :where [[?e ?attr ?value]]}] 106 | ; (is (= (d/q query db :name "Ivan") 107 | ; #{[1] [3]})) 108 | ; (is (= (d/q query db :age 37) 109 | ; #{[2] [3]})) 110 | ; 111 | ; (testing "Named DB" 112 | ; (is (= (d/q '[:find ?a ?v 113 | ; :in $db ?e 114 | ; :where [$db ?e ?a ?v]] db 1) 115 | ; #{[:name "Ivan"] 116 | ; [:age 15]}))) 117 | ; 118 | ; (testing "DB join with collection" 119 | ; (is (= (d/q '[:find ?e ?email 120 | ; :in $ $b 121 | ; :where [?e :name ?n] 122 | ; [$b ?n ?email]] 123 | ; db 124 | ; [["Ivan" "ivan@mail.ru"] 125 | ; ["Petr" "petr@gmail.com"]]) 126 | ; #{[1 "ivan@mail.ru"] 127 | ; [2 "petr@gmail.com"] 128 | ; [3 "ivan@mail.ru"]}))) 129 | ; 130 | ; (testing "Relation binding" 131 | ; (is (= (d/q '[:find ?e ?email 132 | ; :in $ [[?n ?email]] 133 | ; :where [?e :name ?n]] 134 | ; db 135 | ; [["Ivan" "ivan@mail.ru"] 136 | ; ["Petr" "petr@gmail.com"]]) 137 | ; #{[1 "ivan@mail.ru"] 138 | ; [2 "petr@gmail.com"] 139 | ; [3 "ivan@mail.ru"]}))) 140 | ; 141 | ; (testing "Tuple binding" 142 | ; (is (= (d/q '[:find ?e 143 | ; :in $ [?name ?age] 144 | ; :where [?e :name ?name] 145 | ; [?e :age ?age]] 146 | ; db ["Ivan" 37]) 147 | ; #{[3]}))) 148 | ; 149 | ; (testing "Collection binding" 150 | ; (is (= (d/q '[:find ?attr ?value 151 | ; :in $ ?e [?attr ...] 152 | ; :where [?e ?attr ?value]] 153 | ; db 1 [:name :age]) 154 | ; #{[:name "Ivan"] [:age 15]})))) 155 | ; 156 | ; (testing "Query without DB" 157 | ; (is (= (d/q '[:find ?a ?b 158 | ; :in ?a ?b] 159 | ; 10 20) 160 | ; #{[10 20]})))) 161 | ; 162 | ; 163 | ;(deftest test-nested-bindings 164 | ; (is (= (d/q '[:find ?k ?v 165 | ; :in [[?k ?v] ...] 166 | ; :where [(> ?v 1)]] 167 | ; {:a 1, :b 2, :c 3}) 168 | ; #{[:b 2] [:c 3]})) 169 | ; 170 | ; (is (= (d/q '[:find ?k ?min ?max 171 | ; :in [[?k ?v] ...] ?minmax 172 | ; :where [(?minmax ?v) [?min ?max]] 173 | ; [(> ?max ?min)]] 174 | ; {:a [1 2 3 4] 175 | ; :b [5 6 7] 176 | ; :c [3]} 177 | ; #(vector (reduce min %) (reduce max %))) 178 | ; #{[:a 1 4] [:b 5 7]})) 179 | ; 180 | ; (is (= (d/q '[:find ?k ?x 181 | ; :in [[?k [?min ?max]] ...] ?range 182 | ; :where [(?range ?min ?max) [?x ...]] 183 | ; [(even? ?x)]] 184 | ; {:a [1 7] 185 | ; :b [2 4]} 186 | ; range) 187 | ; #{[:a 2] [:a 4] [:a 6] 188 | ; [:b 2]}))) 189 | ; 190 | ; 191 | ;(deftest test-user-funs 192 | ; (let [db (-> (d/empty-db {:parent {:parent {:db/valueType :db.valueType/ref}}}) 193 | ; (d/db-with [ { :db/id 1, :name "Ivan", :age 15 } 194 | ; { :db/id 2, :name "Petr", :age 22, :height 240 :parent 1} 195 | ; { :db/id 3, :name "Slava", :age 37 :parent 2}]))] 196 | ; (testing "get-else" 197 | ; (is (= (d/q '[:find ?e ?age ?height 198 | ; :in $ 199 | ; :where [?e :age ?age] 200 | ; [(get-else $ ?e :height 300) ?height]] db) 201 | ; #{[1 15 300] [2 22 240] [3 37 300]}))) 202 | ; 203 | ; (testing "get-some" 204 | ; (is (= (d/q '[:find ?e ?v 205 | ; :in $ 206 | ; :where [?e :name ?name] 207 | ; [(get-some $ ?e :height :age) ?v]] db) 208 | ; #{[1 15] [2 240] [3 37]}))) 209 | ; 210 | ; (testing "missing?" 211 | ; (is (= (d/q '[:find ?e ?age 212 | ; :in $ 213 | ; :where [?e :age ?age] 214 | ; [(missing? $ ?e :height)]] db) 215 | ; #{[1 15] [3 37]}))) 216 | ; 217 | ; (testing "missing? back-ref" 218 | ; (is (= (d/q '[:find ?e 219 | ; :in $ 220 | ; :where [?e :age ?age] 221 | ; [(missing? $ ?e :_parent)]] db) 222 | ; #{[3]}))) 223 | ; 224 | ; (testing "Built-in predicate" 225 | ; (is (= (d/q '[:find ?e1 ?e2 226 | ; :where [?e1 :age ?a1] 227 | ; [?e2 :age ?a2] 228 | ; [(< ?a1 18 ?a2)]] db) 229 | ; #{[1 2] [1 3]}))) 230 | ; 231 | ; (testing "Passing predicate as source" 232 | ; (is (= (d/q '[:find ?e 233 | ; :in $ ?adult 234 | ; :where [?e :age ?a] 235 | ; [(?adult ?a)]] 236 | ; db 237 | ; #(> % 18)) 238 | ; #{[2] [3]}))) 239 | ; 240 | ; (testing "Calling a function" 241 | ; (is (= (d/q '[:find ?e1 ?e2 ?e3 242 | ; :where [?e1 :age ?a1] 243 | ; [?e2 :age ?a2] 244 | ; [?e3 :age ?a3] 245 | ; [(+ ?a1 ?a2) ?a12] 246 | ; [(= ?a12 ?a3)]] 247 | ; db) 248 | ; #{[1 2 3] [2 1 3]}))))) 249 | ; 250 | ; 251 | ;(deftest test-rules 252 | ; (let [db [ [5 :follow 3] 253 | ; [1 :follow 2] [2 :follow 3] [3 :follow 4] [4 :follow 6] 254 | ; [2 :follow 4]]] 255 | ; (is (= (d/q '[:find ?e1 ?e2 256 | ; :in $ % 257 | ; :where (follow ?e1 ?e2)] 258 | ; db 259 | ; '[[(follow ?x ?y) 260 | ; [?x :follow ?y]]]) 261 | ; #{[1 2] [2 3] [3 4] [2 4] [5 3] [4 6]})) 262 | ; 263 | ; (testing "Rule with branches" 264 | ; (is (= (d/q '[:find ?e2 265 | ; :in $ ?e1 % 266 | ; :where (follow ?e1 ?e2)] 267 | ; db 268 | ; 1 269 | ; '[[(follow ?e2 ?e1) 270 | ; [?e2 :follow ?e1]] 271 | ; [(follow ?e2 ?e1) 272 | ; [?e2 :follow ?t] 273 | ; [?t :follow ?e1]]]) 274 | ; #{[2] [3] [4]}))) 275 | ; 276 | ; (testing "Recursive rules" 277 | ; (is (= (d/q '[:find ?e2 278 | ; :in $ ?e1 % 279 | ; :where (follow ?e1 ?e2)] 280 | ; db 281 | ; 1 282 | ; '[[(follow ?e1 ?e2) 283 | ; [?e1 :follow ?e2]] 284 | ; [(follow ?e1 ?e2) 285 | ; [?e1 :follow ?t] 286 | ; (follow ?t ?e2)]]) 287 | ; #{[2] [3] [4] [6]})) 288 | ; 289 | ; (is (= (d/q '[:find ?e1 ?e2 290 | ; :in $ % 291 | ; :where (follow ?e1 ?e2)] 292 | ; [[1 :follow 2] [2 :follow 3]] 293 | ; '[[(follow ?e1 ?e2) 294 | ; [?e1 :follow ?e2]] 295 | ; [(follow ?e1 ?e2) 296 | ; (follow ?e2 ?e1)]]) 297 | ; #{[1 2] [2 3] [2 1] [3 2]})) 298 | ; 299 | ; (is (= (d/q '[:find ?e1 ?e2 300 | ; :in $ % 301 | ; :where (follow ?e1 ?e2)] 302 | ; [[1 :follow 2] [2 :follow 3] [3 :follow 1]] 303 | ; '[[(follow ?e1 ?e2) 304 | ; [?e1 :follow ?e2]] 305 | ; [(follow ?e1 ?e2) 306 | ; (follow ?e2 ?e1)]]) 307 | ; #{[1 2] [2 3] [3 1] [2 1] [3 2] [1 3]}))) 308 | ; 309 | ; (testing "Mutually recursive rules" 310 | ; (is (= (d/q '[:find ?e1 ?e2 311 | ; :in $ % 312 | ; :where (f1 ?e1 ?e2)] 313 | ; [[0 :f1 1] 314 | ; [1 :f2 2] 315 | ; [2 :f1 3] 316 | ; [3 :f2 4] 317 | ; [4 :f1 5] 318 | ; [5 :f2 6]] 319 | ; '[[(f1 ?e1 ?e2) 320 | ; [?e1 :f1 ?e2]] 321 | ; [(f1 ?e1 ?e2) 322 | ; [?t :f1 ?e2] 323 | ; (f2 ?e1 ?t)] 324 | ; [(f2 ?e1 ?e2) 325 | ; [?e1 :f2 ?e2]] 326 | ; [(f2 ?e1 ?e2) 327 | ; [?t :f2 ?e2] 328 | ; (f1 ?e1 ?t)]]) 329 | ; #{[0 1] [0 3] [0 5] 330 | ; [1 3] [1 5] 331 | ; [2 3] [2 5] 332 | ; [3 5] 333 | ; [4 5]})))) 334 | ; 335 | ; (testing "Specifying db to rule" 336 | ; (is (= (d/q '[ :find ?n 337 | ; :in $sexes $ages % 338 | ; :where ($sexes male ?n) 339 | ; ($ages adult ?n) ] 340 | ; [["Ivan" :male] ["Darya" :female] ["Oleg" :male] ["Igor" :male]] 341 | ; [["Ivan" 15] ["Oleg" 66] ["Darya" 32]] 342 | ; '[[(male ?x) 343 | ; [?x :male]] 344 | ; [(adult ?y) 345 | ; [?y ?a] 346 | ; [(>= ?a 18)]]]) 347 | ; #{["Oleg"]})))) 348 | ; 349 | ;(deftest test-aggregates 350 | ; (let [monsters [ ["Cerberus" 3] 351 | ; ["Medusa" 1] 352 | ; ["Cyclops" 1] 353 | ; ["Chimera" 1] ]] 354 | ; (testing "with" 355 | ; (is (= (d/q '[ :find ?heads 356 | ; :with ?monster 357 | ; :in [[?monster ?heads]] ] 358 | ; [ ["Medusa" 1] 359 | ; ["Cyclops" 1] 360 | ; ["Chimera" 1] ]) 361 | ; [[1] [1] [1]]))) 362 | ; 363 | ; (testing "Wrong grouping without :with" 364 | ; (is (= (d/q '[ :find (sum ?heads) 365 | ; :in [[?monster ?heads]] ] 366 | ; monsters) 367 | ; [[4]]))) 368 | ; 369 | ; (testing "Multiple aggregates, correct grouping with :with" 370 | ; (is (= (d/q '[ :find (sum ?heads) (min ?heads) (max ?heads) (count ?heads) 371 | ; :with ?monster 372 | ; :in [[?monster ?heads]] ] 373 | ; monsters) 374 | ; [[6 1 3 4]]))) 375 | ; 376 | ; (testing "Grouping and parameter passing" 377 | ; (is (= (set (d/q '[ :find ?color (max ?amount ?x) (min ?amount ?x) 378 | ; :in [[?color ?x]] ?amount ] 379 | ; [[:red 1] [:red 2] [:red 3] [:red 4] [:red 5] 380 | ; [:blue 7] [:blue 8]] 381 | ; 3)) 382 | ; #{[:red [3 4 5] [1 2 3]] 383 | ; [:blue [7 8] [7 8]]}))) 384 | ; 385 | ; (testing "Custom aggregates" 386 | ; (is (= (set (d/q '[ :find ?color (?agg ?x) 387 | ; :in [[?color ?x]] ?agg ] 388 | ; [[:red 1] [:red 2] [:red 3] [:red 4] [:red 5] 389 | ; [:blue 7] [:blue 8]] 390 | ; #(reverse (sort %)))) 391 | ; #{[:red [5 4 3 2 1]] [:blue [8 7]]}))))) --------------------------------------------------------------------------------