├── .gitignore ├── README.md ├── composer.json ├── docs ├── api │ ├── .htaccess │ ├── classes │ │ ├── IBT.JsonDB.Client.html │ │ ├── IBT.JsonDB.Collection.html │ │ ├── IBT.JsonDB.Exception.html │ │ ├── IBT.JsonDB.Filter.html │ │ ├── IBT.JsonDB.Indexer.Exception.html │ │ ├── IBT.JsonDB.Indexer.Index.html │ │ └── IBT.JsonDB.Indexer.Scanner.html │ ├── css │ │ ├── bootstrap-combined.no-icons.min.css │ │ ├── font-awesome.min.css │ │ ├── jquery.iviewer.css │ │ ├── phpdocumentor-clean-icons │ │ │ ├── Read Me.txt │ │ │ ├── fonts │ │ │ │ ├── phpdocumentor-clean-icons.dev.svg │ │ │ │ ├── phpdocumentor-clean-icons.eot │ │ │ │ ├── phpdocumentor-clean-icons.svg │ │ │ │ ├── phpdocumentor-clean-icons.ttf │ │ │ │ └── phpdocumentor-clean-icons.woff │ │ │ ├── lte-ie7.js │ │ │ └── style.css │ │ ├── prism.css │ │ └── template.css │ ├── files │ │ ├── Client.html │ │ ├── Client.php.txt │ │ ├── Collection.html │ │ ├── Collection.php.txt │ │ ├── Exception.html │ │ ├── Exception.php.txt │ │ ├── Filter.html │ │ ├── Filter.php.txt │ │ ├── Indexer.Exception.html │ │ ├── Indexer.Index.html │ │ ├── Indexer.Scanner.html │ │ ├── Indexer │ │ │ ├── Exception.php.txt │ │ │ ├── Index.php.txt │ │ │ └── Scanner.php.txt │ │ ├── helpers.html │ │ └── helpers.php.txt │ ├── font │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── graphs │ │ ├── class.html │ │ └── classes.svg │ ├── images │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon.png │ │ ├── custom-icons.svg │ │ ├── favicon.ico │ │ ├── hierarchy-item.png │ │ ├── icon-class-13x13.png │ │ ├── icon-class.svg │ │ ├── icon-interface-13x13.png │ │ ├── icon-interface.svg │ │ ├── icon-trait-13x13.png │ │ ├── icon-trait.svg │ │ └── iviewer │ │ │ ├── grab.cur │ │ │ ├── hand.cur │ │ │ ├── iviewer.rotate_left.png │ │ │ ├── iviewer.rotate_right.png │ │ │ ├── iviewer.zoom_fit.png │ │ │ ├── iviewer.zoom_in.png │ │ │ ├── iviewer.zoom_out.png │ │ │ └── iviewer.zoom_zero.png │ ├── index.html │ ├── js │ │ ├── bootstrap.min.js │ │ ├── html5.js │ │ ├── jquery-1.11.0.min.js │ │ ├── jquery.dotdotdot-1.5.9.js │ │ ├── jquery.dotdotdot-1.5.9.min.js │ │ ├── jquery.iviewer.js │ │ ├── jquery.iviewer.min.js │ │ ├── jquery.mousewheel.js │ │ ├── jquery.smooth-scroll.js │ │ ├── prism.min.js │ │ └── ui │ │ │ └── 1.10.4 │ │ │ └── jquery-ui.min.js │ ├── namespaces │ │ ├── IBT.JsonDB.Indexer.html │ │ ├── IBT.JsonDB.html │ │ ├── IBT.html │ │ └── default.html │ └── reports │ │ ├── deprecated.html │ │ ├── errors.html │ │ └── markers.html ├── getting_started.md ├── index.md └── tutorial.md ├── jsondb.png ├── phpunit.xml.dist ├── src ├── Client.php ├── Collection.php ├── Exception.php ├── Filter.php ├── Indexer │ ├── Exception.php │ ├── Index.php │ └── Scanner.php └── helpers.php └── tests ├── ClientTest.php ├── CollectionTest.php ├── IndexTest.php ├── ScannerTest.php └── test.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac files 2 | .DS_Store 3 | .Trashes 4 | 5 | # editor files 6 | *.sublime-project 7 | *.sublime-workspace 8 | .idea 9 | 10 | # other 11 | vendor/ 12 | composer.lock 13 | composer.phar 14 | tmp/ 15 | *.sqlite 16 | *.db 17 | *.ini 18 | phpunit.xml 19 | docs/api/phpdoc-cache* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JsonDB Library 2 | 3 | [![JsonDB](https://github.com/johnwilson/jsondb/raw/master/jsondb.png)](#JsonDB) 4 | 5 | ## About 6 | 7 | JSONDb is a PHP database abstraction library for MySQL which allows you to easily store and query JSON data. Because it doesn't require the Json Data type available in Mysql version 5.7, this means JSONDb can be used in hosted environments that offer older versions of Mysql for example. 8 | 9 | **JSONDb is primarily designed to be used for rapid application prototyping and is not advisable for use in production.** 10 | 11 | ## Quick Start 12 | 13 | ```PHP 14 | 15 | // Autoload 16 | require_once __DIR__ . '/vendor/autoload.php'; 17 | 18 | // import namespace 19 | use IBT\JsonDB\Client; 20 | use IBT\JsonDB\Collection; 21 | 22 | // create client and initialize database 23 | $c = new Client([ 24 | 'database' => 'database', 25 | 'username' => 'username', 26 | 'password' => 'password' 27 | ]); 28 | $c->setup(); 29 | 30 | // create collection 31 | $col = $c->newCollection("users"); 32 | 33 | // insert json 34 | $col->insert('{"name":"jason bourne", "category":"agent"}'); 35 | $col->insert('{"name":"james bond", "category":"agent"}'); 36 | $col->insert('{"name":"mathew murdock", "category":"superhero"}'); 37 | 38 | // search data 39 | $f = $col->newFilter(); 40 | $r = $f->whereIn('category', ['agent'])->run(); 41 | var_dump($r) 42 | ``` 43 | 44 | ## Learn More 45 | 46 | * View [documentation](https://github.com/johnwilson/jsondb/blob/master/docs/index.md) for additional information and tips. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "johnwilson/jsondb", 3 | "type": "library", 4 | "description": "JSON document db abstraction layer over MySQL", 5 | "keywords": ["json", "database", "nosql", "persistence", "schemaless"], 6 | "license": "MIT", 7 | "authors": [ 8 | {"name": "John Wilson", "email": "wilsonfiifi@gmail.com"} 9 | ], 10 | "require": { 11 | "illuminate/database": "5.2", 12 | "php": ">=5.4" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "IBT\\JsonDB\\": "src" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/api/.htaccess: -------------------------------------------------------------------------------- 1 | # Fixes a vulnerability in CentOS: http://stackoverflow.com/questions/20533279/prevent-php-from-parsing-non-php-files-such-as-somefile-php-txt 2 | 3 | RemoveHandler .php 4 | ForceType text/plain 5 | -------------------------------------------------------------------------------- /docs/api/css/jquery.iviewer.css: -------------------------------------------------------------------------------- 1 | .viewer { 2 | -ms-touch-action: none; 3 | } 4 | 5 | .iviewer_common { 6 | position:absolute; 7 | bottom:10px; 8 | border: 1px solid #000; 9 | height: 28px; 10 | z-index: 5000; 11 | } 12 | 13 | .iviewer_cursor { 14 | cursor: url(../images/iviewer/hand.cur) 6 8, pointer; 15 | } 16 | 17 | .iviewer_drag_cursor { 18 | cursor: url(../images/iviewer/grab.cur) 6 8, pointer; 19 | } 20 | 21 | .iviewer_button { 22 | width: 28px; 23 | cursor: pointer; 24 | background-position: center center; 25 | background-repeat: no-repeat; 26 | } 27 | 28 | .iviewer_zoom_in { 29 | left: 20px; 30 | background: url(../images/iviewer/iviewer.zoom_in.png); 31 | } 32 | 33 | .iviewer_zoom_out { 34 | left: 55px; 35 | background: url(../images/iviewer/iviewer.zoom_out.png); 36 | } 37 | 38 | .iviewer_zoom_zero { 39 | left: 90px; 40 | background: url(../images/iviewer/iviewer.zoom_zero.png); 41 | } 42 | 43 | .iviewer_zoom_fit { 44 | left: 125px; 45 | background: url(../images/iviewer/iviewer.zoom_fit.png); 46 | } 47 | 48 | .iviewer_zoom_status { 49 | left: 160px; 50 | font: 1em/28px Sans; 51 | color: #000; 52 | background-color: #fff; 53 | text-align: center; 54 | width: 60px; 55 | } 56 | 57 | .iviewer_rotate_left { 58 | left: 227px; 59 | background: #fff url(../images/iviewer/iviewer.rotate_left.png) center center no-repeat; 60 | } 61 | 62 | .iviewer_rotate_right { 63 | left: 262px; 64 | background: #fff url(../images/iviewer/iviewer.rotate_right.png) center center no-repeat; 65 | } 66 | -------------------------------------------------------------------------------- /docs/api/css/phpdocumentor-clean-icons/Read Me.txt: -------------------------------------------------------------------------------- 1 | To modify your generated font, use the *dev.svg* file, located in the *fonts* folder in this package. You can import this dev.svg file to the IcoMoon app. All the tags (class names) and the Unicode points of your glyphs are saved in this file. 2 | 3 | See the documentation for more info on how to use this package: http://icomoon.io/#docs/font-face -------------------------------------------------------------------------------- /docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.dev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG font generated by IcoMoon. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.eot -------------------------------------------------------------------------------- /docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG font generated by IcoMoon. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.ttf -------------------------------------------------------------------------------- /docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.woff -------------------------------------------------------------------------------- /docs/api/css/phpdocumentor-clean-icons/lte-ie7.js: -------------------------------------------------------------------------------- 1 | /* Load this script using conditional IE comments if you need to support IE 7 and IE 6. */ 2 | 3 | window.onload = function() { 4 | function addIcon(el, entity) { 5 | var html = el.innerHTML; 6 | el.innerHTML = '' + entity + '' + html; 7 | } 8 | var icons = { 9 | 'icon-trait' : '', 10 | 'icon-interface' : '', 11 | 'icon-class' : '' 12 | }, 13 | els = document.getElementsByTagName('*'), 14 | i, attr, html, c, el; 15 | for (i = 0; ; i += 1) { 16 | el = els[i]; 17 | if(!el) { 18 | break; 19 | } 20 | attr = el.getAttribute('data-icon'); 21 | if (attr) { 22 | addIcon(el, attr); 23 | } 24 | c = el.className; 25 | c = c.match(/icon-[^\s'"]+/); 26 | if (c && icons[c[0]]) { 27 | addIcon(el, icons[c[0]]); 28 | } 29 | } 30 | }; -------------------------------------------------------------------------------- /docs/api/css/phpdocumentor-clean-icons/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'phpdocumentor-clean-icons'; 3 | src:url('fonts/phpdocumentor-clean-icons.eot'); 4 | src:url('fonts/phpdocumentor-clean-icons.eot?#iefix') format('embedded-opentype'), 5 | url('fonts/phpdocumentor-clean-icons.woff') format('woff'), 6 | url('fonts/phpdocumentor-clean-icons.ttf') format('truetype'), 7 | url('fonts/phpdocumentor-clean-icons.svg#phpdocumentor-clean-icons') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | /* Use the following CSS code if you want to use data attributes for inserting your icons */ 13 | [data-icon]:before { 14 | font-family: 'phpdocumentor-clean-icons'; 15 | content: attr(data-icon); 16 | speak: none; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | -webkit-font-smoothing: antialiased; 22 | } 23 | 24 | /* Use the following CSS code if you want to have a class per icon */ 25 | /* 26 | Instead of a list of all class selectors, 27 | you can use the generic selector below, but it's slower: 28 | [class*="icon-"] { 29 | */ 30 | .icon-trait, .icon-interface, .icon-class { 31 | font-family: 'phpdocumentor-clean-icons'; 32 | speak: none; 33 | font-style: normal; 34 | font-weight: normal; 35 | font-variant: normal; 36 | text-transform: none; 37 | line-height: 1; 38 | -webkit-font-smoothing: antialiased; 39 | } 40 | .icon-trait:before { 41 | content: "\e000"; 42 | } 43 | .icon-interface:before { 44 | content: "\e001"; 45 | } 46 | .icon-class:before { 47 | content: "\e002"; 48 | } 49 | -------------------------------------------------------------------------------- /docs/api/css/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | text-shadow: 0 1px white; 11 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 12 | direction: ltr; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | 17 | -moz-tab-size: 4; 18 | -o-tab-size: 4; 19 | tab-size: 4; 20 | 21 | -webkit-hyphens: none; 22 | -moz-hyphens: none; 23 | -ms-hyphens: none; 24 | hyphens: none; 25 | } 26 | 27 | ::-moz-selection { 28 | text-shadow: none; 29 | background: #b3d4fc; 30 | } 31 | 32 | ::selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | @media print { 38 | code[class*="language-"], 39 | pre[class*="language-"] { 40 | text-shadow: none; 41 | } 42 | } 43 | 44 | /* Code blocks */ 45 | pre[class*="language-"] { 46 | padding: 1em; 47 | margin: .5em 0; 48 | overflow: auto; 49 | } 50 | 51 | :not(pre) > code[class*="language-"], 52 | pre[class*="language-"] { 53 | background: #f5f2f0; 54 | } 55 | 56 | /* Inline code */ 57 | :not(pre) > code[class*="language-"] { 58 | padding: .1em; 59 | border-radius: .3em; 60 | } 61 | 62 | .token.comment, 63 | .token.prolog, 64 | .token.doctype, 65 | .token.cdata { 66 | color: slategray; 67 | } 68 | 69 | .token.punctuation { 70 | color: #999; 71 | } 72 | 73 | .namespace { 74 | opacity: .7; 75 | } 76 | 77 | .token.property, 78 | .token.tag, 79 | .token.boolean, 80 | .token.number { 81 | color: #905; 82 | } 83 | 84 | .token.selector, 85 | .token.attr-name, 86 | .token.string { 87 | color: #690; 88 | } 89 | 90 | .token.operator, 91 | .token.entity, 92 | .token.url, 93 | .language-css .token.string, 94 | .style .token.string { 95 | color: #a67f59; 96 | background: hsla(0,0%,100%,.5); 97 | } 98 | 99 | .token.atrule, 100 | .token.attr-value, 101 | .token.keyword { 102 | color: #07a; 103 | } 104 | 105 | 106 | .token.regex, 107 | .token.important { 108 | color: #e90; 109 | } 110 | 111 | .token.important { 112 | font-weight: bold; 113 | } 114 | 115 | .token.entity { 116 | cursor: help; 117 | } 118 | pre[data-line] { 119 | position: relative; 120 | padding: 1em 0 1em 3em; 121 | } 122 | 123 | .line-highlight { 124 | position: absolute; 125 | left: 0; 126 | right: 0; 127 | padding: inherit 0; 128 | margin-top: 1em; /* Same as .prism’s padding-top */ 129 | 130 | background: hsla(24, 20%, 50%,.08); 131 | background: -moz-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 132 | background: -webkit-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 133 | background: -o-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 134 | background: linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 135 | 136 | pointer-events: none; 137 | 138 | line-height: inherit; 139 | white-space: pre; 140 | } 141 | 142 | .line-highlight:before, 143 | .line-highlight[data-end]:after { 144 | content: attr(data-start); 145 | position: absolute; 146 | top: .4em; 147 | left: .6em; 148 | min-width: 1em; 149 | padding: 0 .5em; 150 | background-color: hsla(24, 20%, 50%,.4); 151 | color: hsl(24, 20%, 95%); 152 | font: bold 65%/1.5 sans-serif; 153 | text-align: center; 154 | vertical-align: .3em; 155 | border-radius: 999px; 156 | text-shadow: none; 157 | box-shadow: 0 1px white; 158 | } 159 | 160 | .line-highlight[data-end]:after { 161 | content: attr(data-end); 162 | top: auto; 163 | bottom: .4em; 164 | } 165 | pre.line-numbers { 166 | position: relative; 167 | padding-left: 3.8em; 168 | counter-reset: linenumber; 169 | } 170 | 171 | pre.line-numbers > code { 172 | position: relative; 173 | } 174 | 175 | .line-numbers .line-numbers-rows { 176 | position: absolute; 177 | pointer-events: none; 178 | top: 0; 179 | font-size: 100%; 180 | left: -3.8em; 181 | width: 3em; /* works for line-numbers below 1000 lines */ 182 | letter-spacing: -1px; 183 | border-right: 1px solid #999; 184 | 185 | -webkit-user-select: none; 186 | -moz-user-select: none; 187 | -ms-user-select: none; 188 | user-select: none; 189 | 190 | } 191 | 192 | .line-numbers-rows > span { 193 | pointer-events: none; 194 | display: block; 195 | counter-increment: linenumber; 196 | } 197 | 198 | .line-numbers-rows > span:before { 199 | content: counter(linenumber); 200 | color: #999; 201 | display: block; 202 | padding-right: 0.8em; 203 | text-align: right; 204 | } 205 | -------------------------------------------------------------------------------- /docs/api/css/template.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro); 2 | @import url('phpdocumentor-clean-icons/style.css'); 3 | 4 | body { 5 | padding-top: 40px; 6 | background-color: #333333; 7 | } 8 | 9 | a { 10 | color: #6495ed; 11 | } 12 | a.anchor { 13 | height: 40px; 14 | margin-top: -40px; 15 | display: block; 16 | } 17 | 18 | h1, h2, h3, h4, h5, h6, .brand { 19 | font-family: 'Source Sans Pro', sans-serif; 20 | font-weight: normal; 21 | letter-spacing: 0.05em; 22 | } 23 | 24 | h2, h3, .detailsbar h1 { 25 | overflow: hidden; 26 | white-space: nowrap; 27 | margin: 30px 0 20px 0; 28 | } 29 | 30 | h2:after, h3:after, .detailsbar h1:after { 31 | content: ''; 32 | display: inline-block; 33 | vertical-align: middle; 34 | width: 100%; 35 | height: 2px; 36 | margin-left: 1em; 37 | background: silver; 38 | } 39 | 40 | h3 { 41 | margin: 10px 0 20px 0; 42 | } 43 | 44 | h4 { 45 | margin: 20px 0 10px 0; 46 | color: gray; 47 | font-size: 18.5px; 48 | } 49 | 50 | h3.public, h3.protected, h3.private { 51 | padding-left: 10px; 52 | text-overflow: ellipsis; 53 | } 54 | 55 | .table tr:first-of-type th, .table tr:first-of-type td { 56 | border-top: none; 57 | } 58 | .detailsbar { 59 | color: #eeeeee; 60 | background-color: #333333; 61 | font-size: 0.9em; 62 | overflow: hidden; 63 | border-left: 2px solid gray; 64 | } 65 | 66 | .detailsbar h1 { 67 | font-size: 1.5em; 68 | margin-bottom: 20px; 69 | margin-top: 0; 70 | } 71 | 72 | .detailsbar h2 { 73 | font-size: 1.2em; 74 | margin: 0; 75 | padding: 0; 76 | } 77 | 78 | .detailsbar h1:after { 79 | background: gray; 80 | } 81 | .detailsbar h2:after, .detailsbar h3:after { 82 | background: transparent; 83 | } 84 | 85 | .detailsbar dt { 86 | font-variant: small-caps; 87 | text-transform: lowercase; 88 | font-size: 1.1em; 89 | letter-spacing: 0.1em; 90 | color: silver; 91 | } 92 | 93 | .hierarchy div:nth-of-type(2) { margin-left: 11px; } 94 | .hierarchy div:nth-of-type(3) { margin-left: 22px; } 95 | .hierarchy div:nth-of-type(4) { margin-left: 33px; } 96 | .hierarchy div:nth-of-type(5) { margin-left: 44px; } 97 | .hierarchy div:nth-of-type(6) { margin-left: 55px; } 98 | .hierarchy div:nth-of-type(7) { margin-left: 66px; } 99 | .hierarchy div:nth-of-type(8) { margin-left: 77px; } 100 | .hierarchy div:nth-of-type(9) { margin-left: 88px; } 101 | .hierarchy div:before { 102 | content: "\f0da"; 103 | font-family: FontAwesome; 104 | margin-right: 5px; 105 | } 106 | 107 | .row-fluid { 108 | background-color: white; 109 | overflow: hidden; 110 | } 111 | 112 | footer.row-fluid, footer.row-fluid * { 113 | background-color: #333333; 114 | color: white; 115 | } 116 | 117 | footer.row-fluid { 118 | border-top: 2px dashed #555; 119 | margin-top: 2px; 120 | } 121 | 122 | .footer-sections .span4 { 123 | border: 2px solid #555; 124 | text-align: center; 125 | border-radius: 10px; 126 | margin-top: 70px; 127 | margin-bottom: 20px; 128 | background: #373737; 129 | } 130 | 131 | .footer-sections .span4 h1 { 132 | background: transparent; 133 | margin-top: -30px; 134 | margin-bottom: 20px; 135 | font-size: 5em; 136 | } 137 | 138 | .footer-sections .span4 h1 * { 139 | background: transparent; 140 | } 141 | 142 | .footer-sections .span4 div { 143 | border-bottom-right-radius: 6px; 144 | border-bottom-left-radius: 6px; 145 | padding: 10px; 146 | min-height: 40px; 147 | } 148 | .footer-sections .span4 div, .footer-sections .span4 div * { 149 | background-color: #555; 150 | } 151 | .footer-sections .span4 ul { 152 | text-align: left; 153 | list-style: none; 154 | margin: 0; 155 | padding: 0; 156 | } 157 | 158 | .content { 159 | background-color: white; 160 | padding-right: 20px; 161 | } 162 | 163 | .content nav { 164 | text-align: center; 165 | border-bottom: 1px solid silver; 166 | margin: 5px 0 20px 0; 167 | padding-bottom: 5px; 168 | } 169 | 170 | .content > h1 { 171 | padding-bottom: 15px; 172 | } 173 | 174 | .content > h1 small { 175 | display: block; 176 | padding-bottom: 8px; 177 | font-size: 0.6em; 178 | } 179 | 180 | .deprecated { 181 | text-decoration: line-through; 182 | } 183 | 184 | .method { 185 | margin-bottom: 20px; 186 | } 187 | 188 | .method .signature .argument { 189 | color: maroon; 190 | font-weight: bold; 191 | } 192 | 193 | .class #summary section.row-fluid { 194 | overflow: hidden 195 | } 196 | 197 | .class #summary .heading { 198 | font-weight: bold; 199 | text-align: center; 200 | } 201 | 202 | .class #summary section .span4 { 203 | padding: 3px; 204 | overflow: hidden; 205 | margin-bottom: -9999px; 206 | padding-bottom: 9999px; 207 | white-space: nowrap; 208 | text-overflow: ellipsis; 209 | border-left: 5px solid transparent; 210 | } 211 | 212 | .class #summary section.public .span4:first-of-type:before, 213 | .class #summary section.public .span6:first-of-type:before, 214 | h3.public:before { 215 | font-family: FontAwesome; 216 | content: "\f046"; 217 | color: green; 218 | display: inline-block; 219 | width: 1.2em; 220 | } 221 | 222 | .class #summary section .span4:first-of-type, 223 | .class #summary section .span6:first-of-type { 224 | padding-left: 21px; 225 | } 226 | .class #summary section .span4:first-of-type:before, 227 | .class #summary section .span6:first-of-type:before { 228 | margin-left: -21px; 229 | } 230 | .class #summary section.protected .span4:first-of-type:before, 231 | .class #summary section.protected .span6:first-of-type:before, 232 | h3.protected:before { 233 | font-family: FontAwesome; 234 | content: "\f132"; 235 | color: orange; 236 | display: inline-block; 237 | width: 1.2em; 238 | } 239 | 240 | .class #summary section.private .span4:first-of-type:before, 241 | .class #summary section.private .span6:first-of-type:before, 242 | h3.private:before { 243 | font-family: FontAwesome; 244 | content: "\f023"; 245 | color: red; 246 | display: inline-block; 247 | width: 1.2em; 248 | } 249 | 250 | .class #summary section em { 251 | font-size: 0.9em; 252 | color: silver; 253 | } 254 | .class #summary .inherited { 255 | color: gray; 256 | font-style: italic; 257 | } 258 | 259 | .accordion-group { 260 | border: none; 261 | } 262 | 263 | .accordion { 264 | margin-bottom: 0; 265 | } 266 | 267 | .accordion a:hover { 268 | text-decoration: none; 269 | background: #333333; 270 | color: #eeeeee; 271 | } 272 | 273 | .accordion-heading .accordion-toggle:before { 274 | content: "\f078"; 275 | font-family: FontAwesome; 276 | margin-right: 5px; 277 | } 278 | 279 | .accordion-heading .accordion-toggle.collapsed:before { 280 | content: "\f054"; 281 | } 282 | .accordion-heading .accordion-toggle { 283 | float: left; 284 | width: 16px; 285 | height: 16px; 286 | padding: 4px 2px 4px 12px; 287 | } 288 | .accordion-heading a { 289 | display: block; 290 | padding: 4px 12px; 291 | } 292 | 293 | .accordion-inner a { 294 | display: block; 295 | padding: 4px 12px; 296 | } 297 | 298 | .accordion-inner > ul a:before { 299 | font-family: 'phpdocumentor-clean-icons'; 300 | content: "\e001"; 301 | margin-right: 5px; 302 | } 303 | 304 | .accordion-inner li.class a:before { 305 | content: "\e002"; 306 | } 307 | 308 | .accordion-inner li.interface a:before { 309 | content: "\e001"; 310 | } 311 | 312 | .accordion-inner li.trait a:before { 313 | content: "\e000"; 314 | } 315 | 316 | .accordion-inner { 317 | padding: 4px 0 4px 12px; 318 | } 319 | .accordion-inner ul { 320 | list-style: none; 321 | padding: 0; 322 | margin: 0; 323 | } 324 | 325 | .row-fluid .span2 { 326 | width: 16.5%; 327 | } 328 | 329 | body .modal { 330 | width: 90%; /* desired relative width */ 331 | left: 5%; /* (100%-width)/2 */ 332 | /* place center */ 333 | margin-left:auto; 334 | margin-right:auto; 335 | } 336 | 337 | .side-nav.nav-list li a { 338 | overflow: hidden; 339 | white-space: nowrap; 340 | text-overflow: ellipsis; 341 | } 342 | 343 | @media (min-width: 767px) { 344 | .sidebar { 345 | position: fixed; 346 | top: 40px; 347 | bottom: 0; 348 | background-color: #f3f3f3; 349 | left: 0; 350 | border-right: 1px solid #e9e9e9; 351 | overflow-y: scroll; 352 | overflow-x: hidden; 353 | padding-top: 10px; 354 | } 355 | 356 | .sidebar::-webkit-scrollbar { 357 | width: 10px; 358 | } 359 | 360 | .sidebar::-webkit-scrollbar-thumb { 361 | background: #cccccc; 362 | background-clip: padding-box; 363 | border: 3px solid #f3f3f3; 364 | border-radius: 5px; 365 | } 366 | 367 | .sidebar::-webkit-scrollbar-button { 368 | display: none; 369 | } 370 | 371 | .sidebar::-webkit-scrollbar-track { 372 | background: #f3f3f3; 373 | } 374 | } 375 | 376 | @media (max-width: 979px) { 377 | body { 378 | padding-top: 0; 379 | } 380 | } 381 | 382 | @media (max-width: 767px) { 383 | .class #summary .heading { 384 | display: none; 385 | } 386 | 387 | .detailsbar h1 { 388 | display: none; 389 | } 390 | 391 | body { 392 | background-color: white; 393 | } 394 | 395 | footer.row-fluid, footer.row-fluid * { 396 | background-color: white; 397 | } 398 | 399 | .footer-sections .span4 h1 { 400 | color: #ccccd9; 401 | margin-top: 0; 402 | } 403 | 404 | .detailsbar { 405 | background-color: white; 406 | color: #333; 407 | border: none; 408 | } 409 | 410 | .row-fluid .span2 { 411 | width: 100%; 412 | } 413 | } 414 | 415 | @media (min-width: 767px) { 416 | .detailsbar { 417 | min-height: 100%; 418 | margin-bottom: -99999px; 419 | padding-bottom: 99999px; 420 | padding-left: 20px; 421 | padding-top: 10px; 422 | } 423 | } 424 | 425 | @media (min-width: 1200px) { 426 | .row-fluid .span2 { 427 | width: 16.5%; 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /docs/api/files/Client.php.txt: -------------------------------------------------------------------------------- 1 | setup(); 24 | * ``` 25 | */ 26 | class Client { 27 | 28 | /** 29 | * Collection Table name 30 | */ 31 | const T_COLLECTIONS = 'jsondb_collection'; 32 | 33 | /** 34 | * JSON data Table name 35 | * 36 | */ 37 | const T_JSON = 'jsondb_json'; 38 | 39 | /** 40 | * JSON indices Table name 41 | * 42 | */ 43 | const T_INDEX = 'jsondb_index'; 44 | 45 | /** 46 | * Configuration manager for Illuminate framework 47 | * 48 | * @var \Illuminate\Database\Capsule\Manager 49 | * @see https://laravel.com/api/5.2/Illuminate/Database/Capsule/Manager.html. 50 | */ 51 | private $capsule; 52 | 53 | /** 54 | * Constructor, creates a new Client 55 | * 56 | * @param array $config 57 | */ 58 | public function __construct($config = array()) { 59 | // setup db connection 60 | $capsule = new Capsule(); 61 | 62 | // default configuration 63 | $default = [ 64 | 'driver' => env('DB_DRIVER', 'mysql'), 65 | 'host' => env('DB_HOST', 'localhost'), 66 | 'port' => env('DB_PORT', 3306), 67 | 'database' => env('DB_DATABASE', ''), 68 | 'username' => env('DB_USERNAME', ''), 69 | 'password' => env('DB_PASSWORD', ''), 70 | 'charset' => env('DB_CHARSET', 'utf8'), 71 | 'collation' => env('DB_COLLATION', 'utf8_unicode_ci') 72 | ]; 73 | 74 | // create final config 75 | $config = array_merge($default, $config); 76 | 77 | $capsule->addConnection($config); 78 | $this->capsule = $capsule; 79 | } 80 | 81 | /** 82 | * Creates/Recreates required tables in database 83 | * 84 | */ 85 | public function setup() { 86 | $sb = $this->connection()->getSchemaBuilder(); 87 | 88 | // drop existing tables 89 | $sb->dropIfExists(self::T_INDEX); 90 | $sb->dropIfExists(self::T_JSON); 91 | $sb->dropIfExists(self::T_COLLECTIONS); 92 | 93 | // collections table 94 | $sb->create(self::T_COLLECTIONS, function(Blueprint $table){ 95 | $table->increments('id'); 96 | $table->string("name"); 97 | $table->unique("name"); 98 | }); 99 | 100 | // json document table 101 | $sb->create(self::T_JSON, function(Blueprint $table){ 102 | $table->increments('id'); 103 | 104 | // document id 105 | $table->string("doc_id", 36); 106 | $table->unique("doc_id"); 107 | 108 | // collection id 109 | $table->integer('cid')->unsigned(); 110 | $table->foreign('cid')->references('id') 111 | ->on(self::T_COLLECTIONS) 112 | ->onDelete('cascade'); 113 | 114 | $table->text('data'); 115 | }); 116 | 117 | // index table 118 | $sb->create(self::T_INDEX, function(Blueprint $table){ 119 | $table->increments('id'); 120 | 121 | $table->integer('cid')->unsigned(); 122 | $table->foreign('cid')->references('id') 123 | ->on(self::T_COLLECTIONS) 124 | ->onDelete('cascade'); 125 | 126 | // object id (primary key) 127 | $table->integer('oid')->unsigned(); 128 | $table->foreign('oid')->references('id') 129 | ->on(self::T_JSON) 130 | ->onDelete('cascade'); 131 | 132 | $table->integer('depth'); 133 | $table->string('typ'); 134 | 135 | $table->string('path'); 136 | $table->index('path'); 137 | 138 | // values 139 | $table->text('vjson')->nullable(); 140 | $table->boolean('vboolean')->nullable(); 141 | $table->text('vstring')->nullable(); 142 | $table->float('vfloat')->nullable(); 143 | $table->boolean('vnull')->nullable(); 144 | }); 145 | } 146 | 147 | /** 148 | * Current database connection 149 | * @return \Illuminate\Database\Connection 150 | */ 151 | public function connection() { 152 | return $this->capsule->getConnection(); 153 | } 154 | 155 | /** 156 | * Create a new Collection 157 | * @param string $name Collection name 158 | * @return \IBT\JsonDB\Collection 159 | * @throws Exception if the collection name already exists/is invalid 160 | */ 161 | public function newCollection($name) { 162 | $name = strtolower($name); 163 | 164 | if(!Collection::isValidName($name)) { 165 | throw new Exception("The collection name $name isn't valid"); 166 | } 167 | 168 | if(in_array($name, $this->listCollection())) { 169 | throw new Exception("The collection name $name already exists"); 170 | } 171 | 172 | $conn = $this->connection(); 173 | $id = $conn->table(self::T_COLLECTIONS)->insertGetId( 174 | ['name' => $name] 175 | ); 176 | 177 | return new Collection($id, $name, $this); 178 | } 179 | 180 | /** 181 | * Return existing Collection 182 | * @param string $name Collection name 183 | * @return \IBT\JsonDB\Collection 184 | * @throws Exception if the collection name doesn't exist 185 | */ 186 | public function getCollection($name) { 187 | $name = strtolower($name); 188 | $conn = $this->connection(); 189 | 190 | // check for existence 191 | $row = $conn->table(self::T_COLLECTIONS)->select("id", "name")->where("name", $name)->first(); 192 | if(!$row) { 193 | throw new Exception("The collection name $name doesn't exist"); 194 | } 195 | 196 | return new Collection($row->id, $row->name, $this); 197 | } 198 | 199 | /** 200 | * List all collections 201 | * @return array 202 | */ 203 | public function listCollection() { 204 | $conn = $this->connection(); 205 | $name = $conn->table(self::T_COLLECTIONS)->pluck("name"); 206 | return $name; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /docs/api/files/Collection.php.txt: -------------------------------------------------------------------------------- 1 | setup(); 21 | * $col = $c->newCollection("users"); 22 | * $col->insert('{"name":"jason bourne", "category":"agent"}'); 23 | * ``` 24 | */ 25 | class Collection { 26 | 27 | /** 28 | * @var string Name of the Collection 29 | */ 30 | private $name; 31 | 32 | /** 33 | * @var int Collection database primary key 34 | */ 35 | private $id; 36 | 37 | /** 38 | * @var Client JsonDB Client 39 | */ 40 | private $client; 41 | 42 | /** 43 | * Constructor, creates a new Collection 44 | * 45 | * @param int $id Database primary key 46 | * @param string $name Collection name 47 | * @param Client $client 48 | */ 49 | function __construct($id, $name, Client $client) { 50 | $this->id = $id; 51 | $this->name = $name; 52 | $this->client = $client; 53 | } 54 | 55 | /** 56 | * Validate Collection name 57 | * 58 | * @param string $name Collection name 59 | * @return boolean 60 | */ 61 | static function isValidName($name) { 62 | // only accept alpha characters 63 | $p = '/^[a-zA-Z]+$/'; 64 | $m = preg_match($p, $name); 65 | if($m) { 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | /** 73 | * Collection name 74 | * 75 | * @return string 76 | */ 77 | function getName() { 78 | return $this->name; 79 | } 80 | 81 | /** 82 | * Collection id 83 | * 84 | * @return int 85 | */ 86 | function getId() { 87 | return $this->id; 88 | } 89 | 90 | /** 91 | * Column name from JSON type 92 | * 93 | * @param string $typ 94 | * @return string 95 | * @throws Exception if the JSON type isn't supported 96 | */ 97 | public static function getColumn($typ) { 98 | switch ($typ) { 99 | case 'float': 100 | return 'vfloat'; 101 | case 'boolean': 102 | return 'vboolean'; 103 | case 'string': 104 | return 'vstring'; 105 | case 'array': 106 | case 'object': 107 | return 'vjson'; 108 | case 'null': 109 | return 'vnull'; 110 | default: 111 | throw new Exception('Unknown JSON type'); 112 | break; 113 | } 114 | } 115 | 116 | /** 117 | * Save JSON string (create/update) 118 | * 119 | * @param string $doc JSON string 120 | * @return string 121 | */ 122 | private function save($doc) { 123 | // create index 124 | $scr = new Scanner(); 125 | $index_list = $scr->scan($doc); 126 | 127 | $conn = $this->client->connection(); 128 | $cid = $this->id; 129 | 130 | $conn->transaction(function() use ($index_list, $conn, $cid, $doc) { 131 | // add data 132 | 133 | $json = json_encode($doc); 134 | $id = $conn->table(Client::T_JSON)->insertGetId( 135 | ['doc_id' => $doc['_id'], 'cid' => $cid, 'data' => $json] 136 | ); 137 | 138 | // add index 139 | $rows = array(); 140 | foreach ($index_list as $item) { 141 | $row = [ 142 | 'cid' => $cid, 143 | 'oid' => $id, 144 | 'depth' => $item->depth, 145 | 'typ' => $item->typ, 146 | 'path' => $item->path, 147 | 'vfloat' => NULL, 148 | 'vboolean' => NULL, 149 | 'vstring' => NULL, 150 | 'vjson' => NULL, 151 | 'vnull' => NULL 152 | ]; 153 | 154 | // insert value in appropriate column 155 | $c = Collection::getColumn($item->typ); 156 | $row[$c] = $item->value; 157 | $rows[] = $row; 158 | } 159 | $conn->table(Client::T_INDEX)->insert($rows); 160 | }); 161 | 162 | return $doc['_id']; 163 | } 164 | 165 | /** 166 | * Check if document exists 167 | * 168 | * @param string $id document id 169 | * @return boolean 170 | */ 171 | private function exists($id) { 172 | $conn = $this->client->connection(); 173 | $row = $conn->table(Client::T_JSON)->select("doc_id")->where("doc_id", $id)->first(); 174 | if(!$row) { 175 | return false; 176 | } 177 | 178 | return true; 179 | } 180 | 181 | /** 182 | * Database manager 183 | * 184 | * @return Client 185 | */ 186 | function getClient() { 187 | return $this->client; 188 | } 189 | 190 | /** 191 | * Inserts a json document into db. 192 | * 193 | * @param string $json JSON string to insert 194 | * @return string Returns document id 195 | * @throws Exception if the document id isn't valid 196 | */ 197 | function insert($json) { 198 | $doc = json_decode($json, true); 199 | 200 | // check if json has id 201 | if(!array_key_exists('_id', $doc)) { 202 | $doc['_id'] = uniqid('oid'); 203 | } else { 204 | // only accept alpha numeric characters 205 | $p = '/^[a-z0-9]+$/'; 206 | $m = preg_match($p, $doc['_id']); 207 | if(!$m) { 208 | throw new Exception('Invalid document id.'); 209 | } 210 | } 211 | 212 | return $this->save($doc); 213 | } 214 | 215 | /** 216 | * Retrieve JSON document 217 | * 218 | * @param string $id document id 219 | * @return mixed 220 | */ 221 | function get($id) { 222 | $conn = $this->client->connection(); 223 | $doc = $conn->table(Client::T_JSON) 224 | ->where('doc_id', $id) 225 | ->where('cid', $this->id) 226 | ->first(); 227 | 228 | if(is_null($doc)) { 229 | return NULL; 230 | } 231 | 232 | return json_decode($doc->data); 233 | } 234 | 235 | /** 236 | * Delete JSON document 237 | * 238 | * @param string $oid document id 239 | * @return boolean 240 | */ 241 | function delete($oid) { 242 | $conn = $this->client->connection(); 243 | return $conn->table(Client::T_JSON)->where('doc_id', $oid)->delete() > 0; 244 | } 245 | 246 | /** 247 | * Updates a JSON document in db. 248 | * 249 | * @param string $json JSON string to insert 250 | * @return string Returns document id 251 | * @throws Exception if the original document id isn't present 252 | */ 253 | function update($json) { 254 | $doc = json_decode($json, true); 255 | 256 | // check if json has id 257 | if(!array_key_exists('_id', $doc)) { 258 | throw new Exception('Missing \'_id\' in JSON document'); 259 | } 260 | 261 | $oid = $doc['_id']; 262 | 263 | if($this->exists($oid)) { 264 | $this->delete($oid); 265 | $this->save($doc); 266 | return true; 267 | } 268 | 269 | return false; 270 | } 271 | 272 | /** 273 | * Total number of JSON documents 274 | * 275 | * @return int 276 | */ 277 | function count() { 278 | $conn = $this->client->connection(); 279 | $tbl = $conn->table(Client::T_JSON); 280 | return $tbl->select($conn->raw('distinct(doc_id)'))->count(); 281 | } 282 | 283 | /** 284 | * New Filter to query JSON document content 285 | * 286 | * @return \IBT\JsonDB\Filter 287 | */ 288 | function newFilter() { 289 | return new Filter($this); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /docs/api/files/Exception.php.txt: -------------------------------------------------------------------------------- 1 | setup(); 23 | * $col = $c->newCollection("users"); 24 | * $col->insert('{"name":"jason bourne", "category":"agent"}'); 25 | * $f = $col->newFilter(); 26 | * $f->where('category', 'in', ['agent'])->run(); 27 | * ``` 28 | */ 29 | class Filter { 30 | 31 | /** 32 | * @var Collection Collection the filter is based on 33 | */ 34 | private $col; 35 | 36 | /** 37 | * @var \Illuminate\Database\Query\Builder Query builder instance 38 | */ 39 | private $query; 40 | 41 | /** 42 | * @var string Name of the database View 43 | */ 44 | private $view; 45 | 46 | /** 47 | * @var array Fields(or JSON paths) that will be used in the query 48 | */ 49 | private $fields; 50 | 51 | /** 52 | * Constructor, creates a new Filter 53 | * 54 | * @param Collection $col Collection the filter is based on 55 | */ 56 | public function __construct(Collection $col) { 57 | $this->fields = array(); 58 | $this->col = $col; 59 | 60 | $this->view = uniqid('view_'); 61 | 62 | $conn = $this->col->getClient()->connection(); 63 | $this->query = $conn->table($this->view) 64 | ->join(Client::T_JSON, 'oid', '=', 'id') 65 | ->select('doc_id'); 66 | 67 | } 68 | 69 | /** 70 | * Converts JSON value path into valid column name 71 | * 72 | * @param string $path JSON path 73 | * @return string 74 | */ 75 | private function sanitizeColName($path) { 76 | return str_replace('.', '_', $path); 77 | } 78 | 79 | /** 80 | * SQL Where statement. 81 | * 82 | * @param string $path JSON path 83 | * @param string $op Operand such as =, <=, > 84 | * @param mixed $value Value 85 | * @return $this 86 | */ 87 | public function where($path, $op, $value) { 88 | $this->fields[$path] = 1; 89 | $this->query->where($this->sanitizeColName($path), $op, $value); 90 | return $this; 91 | } 92 | 93 | /** 94 | * SQL Or Where statement. 95 | * 96 | * @param string $path JSON path 97 | * @param string $op Operand such as =, <=, > 98 | * @param mixed $value Value 99 | * @return $this 100 | */ 101 | public function orWhere($path, $op, $value) { 102 | $this->fields[$path] = 1; 103 | $this->query->orWhere($this->sanitizeColName($path), $op, $value); 104 | return $this; 105 | } 106 | 107 | /** 108 | * SQL Where Between statement. 109 | * 110 | * @param string $path JSON path 111 | * @param array $value Values 112 | * @return $this 113 | */ 114 | public function whereBetween($path, $value) { 115 | $this->fields[$path] = 1; 116 | $this->query->whereBetween($this->sanitizeColName($path), $value); 117 | return $this; 118 | } 119 | 120 | /** 121 | * SQL Where Not Between statement. 122 | * 123 | * @param string $path JSON path 124 | * @param array $value Values 125 | * @return $this 126 | */ 127 | public function whereNotBetween($path, $value) { 128 | $this->fields[$path] = 1; 129 | $this->query->whereNotBetween($this->sanitizeColName($path), $value); 130 | return $this; 131 | } 132 | 133 | /** 134 | * SQL Where In statement. 135 | * 136 | * @param string $path JSON path 137 | * @param mixed $value Value(s) 138 | * @return $this 139 | */ 140 | public function whereIn($path, $value) { 141 | $this->fields[$path] = 1; 142 | $this->query->whereIn($this->sanitizeColName($path), $value); 143 | return $this; 144 | } 145 | 146 | /** 147 | * SQL Where Not In statement. 148 | * 149 | * @param string $path JSON path 150 | * @param mixed $value Value(s) 151 | * @return $this 152 | */ 153 | public function whereNotIn($path, $value) { 154 | $this->fields[$path] = 1; 155 | $this->query->whereNotIn($this->sanitizeColName($path), $value); 156 | return $this; 157 | } 158 | 159 | /** 160 | * SQL Order By statement. 161 | * 162 | * @param string $path JSON path 163 | * @param string $direction Direction i.e 'asc' or 'desc' 164 | * @return $this 165 | */ 166 | public function orderBy($path, $direction) { 167 | $this->fields[$path] = 1; 168 | $this->query->orderBy($this->sanitizeColName($path), $direction); 169 | return $this; 170 | } 171 | 172 | /** 173 | * Return View name. 174 | * 175 | * @return string 176 | */ 177 | public function viewName() { 178 | return $this->view; 179 | } 180 | 181 | /** 182 | * Make sure JSON paths specified in filter statements exist. 183 | * Prevent SQL injection. 184 | * 185 | * @return array 186 | * @throws Exception if the requested JSON path is invalid 187 | */ 188 | private function validateFields() { 189 | // get paths and their data types 190 | $conn = $this->col->getClient()->connection(); 191 | $rows = $conn->table(Client::T_INDEX) 192 | ->select($conn->raw('distinct(path)'), 'typ') 193 | ->whereIn('path', array_keys($this->fields)) 194 | ->get(); 195 | 196 | // quick comparison. can be changed for a more descriptive 197 | // error by comparing values in both lists 198 | if(sizeof($rows) != sizeof($this->fields)) { 199 | throw new Exception("Error: One or more JSON paths don't exist"); 200 | } 201 | 202 | return $rows; 203 | } 204 | 205 | /** 206 | * View query string. This can be used for debugging or caching 207 | * 208 | * @return string 209 | */ 210 | public function viewSQL() { 211 | $idx_table = Client::T_INDEX; 212 | 213 | // validate fields 214 | $fields = $this->validateFields(); 215 | 216 | // oid table 217 | $main = "(select distinct(oid) from ${idx_table}) as a"; 218 | 219 | $select = ['a.oid']; 220 | 221 | $join = [$main]; 222 | 223 | $tc = 0; // table counter 224 | 225 | // add fields 226 | foreach ($fields as $item) { 227 | // get column name 228 | $col = Collection::getColumn($item->typ); 229 | $field = $item->path; 230 | 231 | $cn = $this->sanitizeColName($field); // column name 232 | 233 | $t = "select oid, case when path='${field}' then ${col} end as ${cn} "; 234 | $t .= "from ${idx_table} having ${cn} is not null"; 235 | 236 | $tc++; 237 | $tn = "jn_tbl_" . $tc; // join table alias 238 | 239 | $select[] = "${tn}.${cn}"; 240 | $join[] = '(' . $t . ") as $tn on a.oid = $tn.oid"; 241 | } 242 | 243 | $sb = 'create view ' . $this->view .' as '; 244 | $sb .= 'select ' . join(",", $select) . ' from '; 245 | $sb .= join(" left join ", $join); 246 | 247 | return $sb; 248 | } 249 | 250 | /** 251 | * View select query string. complements the view function 252 | * 253 | * @return string 254 | */ 255 | public function selectSQL() { 256 | return $this->query->toSql(); 257 | } 258 | 259 | /** 260 | * Execute the filter 261 | * 262 | * @return array 263 | */ 264 | public function run() { 265 | $res = []; 266 | $conn = $this->col->getClient()->connection(); 267 | $view = $this->view; 268 | $viewSQL = $this->viewSQL(); 269 | $query = $this->query; 270 | 271 | $conn->transaction(function() use (&$res, $conn, $view, $viewSQL, $query) { 272 | // create view 273 | $conn->statement($viewSQL); 274 | 275 | // select data 276 | $res = $query->pluck('doc_id'); 277 | 278 | // drop view 279 | $conn->statement('drop view if exists ' . $view); 280 | }); 281 | 282 | return $res; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /docs/api/files/Indexer/Exception.php.txt: -------------------------------------------------------------------------------- 1 | typ = $typ; 48 | $this->value = $value; 49 | $this->path = $path; 50 | $this->depth = $depth; 51 | } 52 | 53 | /** 54 | * Retrieves a property if it exists. 55 | * 56 | * @see http://php.net/manual/en/language.oop5.overloading.php#object.get 57 | * @param string $property Name of property to retrieve 58 | * @return mixed 59 | * @throws Exception if the property isn't found 60 | */ 61 | public function __get($property) { 62 | if(property_exists($this, $property)) { 63 | return $this->$property; 64 | } 65 | throw new Exception('Property not found'); 66 | } 67 | 68 | /** 69 | * Return internal properties for debugging purposes. 70 | * 71 | * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo 72 | * @return mixed 73 | */ 74 | public function __debugInfo() 75 | { 76 | return array( 77 | "depth" => $this->depth, 78 | "path" => $this->path, 79 | "value" => $this->value, 80 | "type" => $this->typ 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /docs/api/files/Indexer/Scanner.php.txt: -------------------------------------------------------------------------------- 1 | scan(json_decode($j)); 19 | * ``` 20 | */ 21 | class Scanner { 22 | 23 | /** 24 | * Indexes a JSON string. 25 | * 26 | * @param array $json JSON value 27 | * @return array Path 28 | */ 29 | public function scan($json) { 30 | $it = new \RecursiveArrayIterator($json); // call global namespace class 31 | $path = ""; // set object root path 32 | $depth = 0; // set path depth 33 | $result = array(); // array for indexes 34 | $fn = array($this, "walk"); // iterator callback function 35 | 36 | iterator_apply($it, $fn, array($it, $path, $depth, &$result)); 37 | 38 | return $result; 39 | } 40 | 41 | /** 42 | * Indexes a JSON string. 43 | * 44 | * @param \RecursiveArrayIterator $it Iterator 45 | * @param string $path current JSON path 46 | * @param int $depth current JSON depth 47 | * @param array $result Reference to array containing scanned indexes 48 | */ 49 | private function walk($it, $path, $depth, &$result) { 50 | while ($it->valid()) { 51 | 52 | $key = $path . $it->key(); 53 | $value = $it->current(); 54 | $typ = Scanner::jsonType(gettype($value)); 55 | if($typ == "array" || $typ == "object") { 56 | $value = json_encode($value); 57 | } 58 | 59 | $result[] = new Index($typ, $value, $key, $depth); 60 | 61 | if($it->hasChildren()) { 62 | $c = $it->getChildren(); 63 | $sub_path = $key . "."; 64 | $depth++; 65 | $this->walk($c, $sub_path, $depth, $result); 66 | } 67 | $it->next(); 68 | } 69 | } 70 | 71 | /** 72 | * Returns the equivalent JSON type. 73 | * 74 | * @param string $typ 75 | * @return string 76 | * @throws Exception if the JSON type isn't supported 77 | */ 78 | public static function jsonType($typ) { 79 | switch ($typ) { 80 | case 'integer': 81 | case 'double': 82 | return 'float'; 83 | case 'NULL': 84 | return 'null'; 85 | case 'boolean': 86 | case 'string': 87 | case 'array': 88 | case 'object': 89 | return $typ; 90 | default: 91 | throw new Exception("Unknown JSON type ${typ}"); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /docs/api/files/helpers.php.txt: -------------------------------------------------------------------------------- 1 | 1 && Str::startsWith($value, '"') && Str::endsWith($value, '"')) { 41 | return substr($value, 1, -1); 42 | } 43 | return $value; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/api/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/font/FontAwesome.otf -------------------------------------------------------------------------------- /docs/api/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/api/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/api/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/api/graphs/class.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | API Documentation 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 117 | 118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | 127 | 169 |
170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /docs/api/graphs/classes.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | G 11 | 12 | cluster_Global 13 | 14 | Global 15 | 16 | cluster_\IBT 17 | 18 | IBT 19 | 20 | cluster_\IBT\JsonDB 21 | 22 | JsonDB 23 | 24 | cluster_\IBT\JsonDB\Indexer 25 | 26 | Indexer 27 | 28 | 29 | \\IBT\\JsonDB\\Indexer\\Scanner 30 | 31 | Scanner 32 | 33 | 34 | \\IBT\\JsonDB\\Indexer\\Index 35 | 36 | Index 37 | 38 | 39 | \\IBT\\JsonDB\\Indexer\\Exception 40 | 41 | Exception 42 | 43 | 44 | \\Exception 45 | 46 | \Exception 47 | 48 | 49 | \\IBT\\JsonDB\\Indexer\\Exception->\\Exception 50 | 51 | 52 | 53 | 54 | \\IBT\\JsonDB\\Collection 55 | 56 | Collection 57 | 58 | 59 | \\IBT\\JsonDB\\Filter 60 | 61 | Filter 62 | 63 | 64 | \\IBT\\JsonDB\\Exception 65 | 66 | Exception 67 | 68 | 69 | \\IBT\\JsonDB\\Exception->\\Exception 70 | 71 | 72 | 73 | 74 | \\IBT\\JsonDB\\Client 75 | 76 | Client 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/api/images/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /docs/api/images/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /docs/api/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/api/images/custom-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 61 | 67 | 68 | 84 | 100 | 106 | 109 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/api/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/favicon.ico -------------------------------------------------------------------------------- /docs/api/images/hierarchy-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/hierarchy-item.png -------------------------------------------------------------------------------- /docs/api/images/icon-class-13x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/icon-class-13x13.png -------------------------------------------------------------------------------- /docs/api/images/icon-class.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 31 | 36 | 37 | 40 | 43 | 47 | 48 | 49 | 63 | 77 | 78 | -------------------------------------------------------------------------------- /docs/api/images/icon-interface-13x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/icon-interface-13x13.png -------------------------------------------------------------------------------- /docs/api/images/icon-interface.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 31 | 36 | 37 | 49 | 52 | 55 | 59 | 60 | 61 | 73 | 74 | -------------------------------------------------------------------------------- /docs/api/images/icon-trait-13x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/icon-trait-13x13.png -------------------------------------------------------------------------------- /docs/api/images/icon-trait.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 31 | 36 | 37 | 49 | 61 | 64 | 67 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/api/images/iviewer/grab.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/iviewer/grab.cur -------------------------------------------------------------------------------- /docs/api/images/iviewer/hand.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/iviewer/hand.cur -------------------------------------------------------------------------------- /docs/api/images/iviewer/iviewer.rotate_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/iviewer/iviewer.rotate_left.png -------------------------------------------------------------------------------- /docs/api/images/iviewer/iviewer.rotate_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/iviewer/iviewer.rotate_right.png -------------------------------------------------------------------------------- /docs/api/images/iviewer/iviewer.zoom_fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/iviewer/iviewer.zoom_fit.png -------------------------------------------------------------------------------- /docs/api/images/iviewer/iviewer.zoom_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/iviewer/iviewer.zoom_in.png -------------------------------------------------------------------------------- /docs/api/images/iviewer/iviewer.zoom_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/iviewer/iviewer.zoom_out.png -------------------------------------------------------------------------------- /docs/api/images/iviewer/iviewer.zoom_zero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/docs/api/images/iviewer/iviewer.zoom_zero.png -------------------------------------------------------------------------------- /docs/api/js/html5.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f); 8 | if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d 1 ) 33 | { 34 | return this.each( 35 | function() 36 | { 37 | $(this).dotdotdot( o ); 38 | } 39 | ); 40 | } 41 | 42 | 43 | var $dot = this; 44 | 45 | if ( $dot.data( 'dotdotdot' ) ) 46 | { 47 | $dot.trigger( 'destroy.dot' ); 48 | } 49 | 50 | $dot.data( 'dotdotdot-style', $dot.attr( 'style' ) ); 51 | $dot.css( 'word-wrap', 'break-word' ); 52 | 53 | $dot.bind_events = function() 54 | { 55 | $dot.bind( 56 | 'update.dot', 57 | function( e, c ) 58 | { 59 | e.preventDefault(); 60 | e.stopPropagation(); 61 | 62 | opts.maxHeight = ( typeof opts.height == 'number' ) 63 | ? opts.height 64 | : getTrueInnerHeight( $dot ); 65 | 66 | opts.maxHeight += opts.tolerance; 67 | 68 | if ( typeof c != 'undefined' ) 69 | { 70 | if ( typeof c == 'string' || c instanceof HTMLElement ) 71 | { 72 | c = $('
').append( c ).contents(); 73 | } 74 | if ( c instanceof $ ) 75 | { 76 | orgContent = c; 77 | } 78 | } 79 | 80 | $inr = $dot.wrapInner( '
' ).children(); 81 | $inr.empty() 82 | .append( orgContent.clone( true ) ) 83 | .css({ 84 | 'height' : 'auto', 85 | 'width' : 'auto', 86 | 'border' : 'none', 87 | 'padding' : 0, 88 | 'margin' : 0 89 | }); 90 | 91 | var after = false, 92 | trunc = false; 93 | 94 | if ( conf.afterElement ) 95 | { 96 | after = conf.afterElement.clone( true ); 97 | conf.afterElement.remove(); 98 | } 99 | if ( test( $inr, opts ) ) 100 | { 101 | if ( opts.wrap == 'children' ) 102 | { 103 | trunc = children( $inr, opts, after ); 104 | } 105 | else 106 | { 107 | trunc = ellipsis( $inr, $dot, $inr, opts, after ); 108 | } 109 | } 110 | $inr.replaceWith( $inr.contents() ); 111 | $inr = null; 112 | 113 | if ( $.isFunction( opts.callback ) ) 114 | { 115 | opts.callback.call( $dot[ 0 ], trunc, orgContent ); 116 | } 117 | 118 | conf.isTruncated = trunc; 119 | return trunc; 120 | } 121 | 122 | ).bind( 123 | 'isTruncated.dot', 124 | function( e, fn ) 125 | { 126 | e.preventDefault(); 127 | e.stopPropagation(); 128 | 129 | if ( typeof fn == 'function' ) 130 | { 131 | fn.call( $dot[ 0 ], conf.isTruncated ); 132 | } 133 | return conf.isTruncated; 134 | } 135 | 136 | ).bind( 137 | 'originalContent.dot', 138 | function( e, fn ) 139 | { 140 | e.preventDefault(); 141 | e.stopPropagation(); 142 | 143 | if ( typeof fn == 'function' ) 144 | { 145 | fn.call( $dot[ 0 ], orgContent ); 146 | } 147 | return orgContent; 148 | } 149 | 150 | ).bind( 151 | 'destroy.dot', 152 | function( e ) 153 | { 154 | e.preventDefault(); 155 | e.stopPropagation(); 156 | 157 | $dot.unwatch() 158 | .unbind_events() 159 | .empty() 160 | .append( orgContent ) 161 | .attr( 'style', $dot.data( 'dotdotdot-style' ) ) 162 | .data( 'dotdotdot', false ); 163 | } 164 | ); 165 | return $dot; 166 | }; // /bind_events 167 | 168 | $dot.unbind_events = function() 169 | { 170 | $dot.unbind('.dot'); 171 | return $dot; 172 | }; // /unbind_events 173 | 174 | $dot.watch = function() 175 | { 176 | $dot.unwatch(); 177 | if ( opts.watch == 'window' ) 178 | { 179 | var $window = $(window), 180 | _wWidth = $window.width(), 181 | _wHeight = $window.height(); 182 | 183 | $window.bind( 184 | 'resize.dot' + conf.dotId, 185 | function() 186 | { 187 | if ( _wWidth != $window.width() || _wHeight != $window.height() || !opts.windowResizeFix ) 188 | { 189 | _wWidth = $window.width(); 190 | _wHeight = $window.height(); 191 | 192 | if ( watchInt ) 193 | { 194 | clearInterval( watchInt ); 195 | } 196 | watchInt = setTimeout( 197 | function() 198 | { 199 | $dot.trigger( 'update.dot' ); 200 | }, 10 201 | ); 202 | } 203 | } 204 | ); 205 | } 206 | else 207 | { 208 | watchOrg = getSizes( $dot ); 209 | watchInt = setInterval( 210 | function() 211 | { 212 | var watchNew = getSizes( $dot ); 213 | if ( watchOrg.width != watchNew.width || 214 | watchOrg.height != watchNew.height ) 215 | { 216 | $dot.trigger( 'update.dot' ); 217 | watchOrg = getSizes( $dot ); 218 | } 219 | }, 100 220 | ); 221 | } 222 | return $dot; 223 | }; 224 | $dot.unwatch = function() 225 | { 226 | $(window).unbind( 'resize.dot' + conf.dotId ); 227 | if ( watchInt ) 228 | { 229 | clearInterval( watchInt ); 230 | } 231 | return $dot; 232 | }; 233 | 234 | var orgContent = $dot.contents(), 235 | opts = $.extend( true, {}, $.fn.dotdotdot.defaults, o ), 236 | conf = {}, 237 | watchOrg = {}, 238 | watchInt = null, 239 | $inr = null; 240 | 241 | conf.afterElement = getElement( opts.after, $dot ); 242 | conf.isTruncated = false; 243 | conf.dotId = dotId++; 244 | 245 | 246 | $dot.data( 'dotdotdot', true ) 247 | .bind_events() 248 | .trigger( 'update.dot' ); 249 | 250 | if ( opts.watch ) 251 | { 252 | $dot.watch(); 253 | } 254 | 255 | return $dot; 256 | }; 257 | 258 | 259 | // public 260 | $.fn.dotdotdot.defaults = { 261 | 'ellipsis' : '... ', 262 | 'wrap' : 'word', 263 | 'lastCharacter': { 264 | 'remove' : [ ' ', ',', ';', '.', '!', '?' ], 265 | 'noEllipsis' : [] 266 | }, 267 | 'tolerance' : 0, 268 | 'callback' : null, 269 | 'after' : null, 270 | 'height' : null, 271 | 'watch' : false, 272 | 'windowResizeFix': true, 273 | 'debug' : false 274 | }; 275 | 276 | 277 | // private 278 | var dotId = 1; 279 | 280 | function children( $elem, o, after ) 281 | { 282 | var $elements = $elem.children(), 283 | isTruncated = false; 284 | 285 | $elem.empty(); 286 | 287 | for ( var a = 0, l = $elements.length; a < l; a++ ) 288 | { 289 | var $e = $elements.eq( a ); 290 | $elem.append( $e ); 291 | if ( after ) 292 | { 293 | $elem.append( after ); 294 | } 295 | if ( test( $elem, o ) ) 296 | { 297 | $e.remove(); 298 | isTruncated = true; 299 | break; 300 | } 301 | else 302 | { 303 | if ( after ) 304 | { 305 | after.remove(); 306 | } 307 | } 308 | } 309 | return isTruncated; 310 | } 311 | function ellipsis( $elem, $d, $i, o, after ) 312 | { 313 | var $elements = $elem.contents(), 314 | isTruncated = false; 315 | 316 | $elem.empty(); 317 | 318 | var notx = 'table, thead, tbody, tfoot, tr, col, colgroup, object, embed, param, ol, ul, dl, select, optgroup, option, textarea, script, style'; 319 | for ( var a = 0, l = $elements.length; a < l; a++ ) 320 | { 321 | 322 | if ( isTruncated ) 323 | { 324 | break; 325 | } 326 | 327 | var e = $elements[ a ], 328 | $e = $(e); 329 | 330 | if ( typeof e == 'undefined' ) 331 | { 332 | continue; 333 | } 334 | 335 | $elem.append( $e ); 336 | if ( after ) 337 | { 338 | $elem[ ( $elem.is( notx ) ) ? 'after' : 'append' ]( after ); 339 | } 340 | if ( e.nodeType == 3 ) 341 | { 342 | if ( test( $i, o ) ) 343 | { 344 | isTruncated = ellipsisElement( $e, $d, $i, o, after ); 345 | } 346 | } 347 | else 348 | { 349 | isTruncated = ellipsis( $e, $d, $i, o, after ); 350 | } 351 | 352 | if ( !isTruncated ) 353 | { 354 | if ( after ) 355 | { 356 | after.remove(); 357 | } 358 | } 359 | } 360 | return isTruncated; 361 | } 362 | function ellipsisElement( $e, $d, $i, o, after ) 363 | { 364 | var isTruncated = false, 365 | e = $e[ 0 ]; 366 | 367 | if ( typeof e == 'undefined' ) 368 | { 369 | return false; 370 | } 371 | 372 | var seporator = ( o.wrap == 'letter' ) ? '' : ' ', 373 | textArr = getTextContent( e ).split( seporator ), 374 | position = -1, 375 | midPos = -1, 376 | startPos = 0, 377 | endPos = textArr.length - 1; 378 | 379 | while ( startPos <= endPos ) 380 | { 381 | var m = Math.floor( ( startPos + endPos ) / 2 ); 382 | if ( m == midPos ) 383 | { 384 | break; 385 | } 386 | midPos = m; 387 | 388 | setTextContent( e, textArr.slice( 0, midPos + 1 ).join( seporator ) + o.ellipsis ); 389 | 390 | if ( !test( $i, o ) ) 391 | { 392 | position = midPos; 393 | startPos = midPos; 394 | } 395 | else 396 | { 397 | endPos = midPos; 398 | } 399 | } 400 | 401 | if ( position != -1 && !( textArr.length == 1 && textArr[ 0 ].length == 0 ) ) 402 | { 403 | var txt = addEllipsis( textArr.slice( 0, position + 1 ).join( seporator ), o ); 404 | isTruncated = true; 405 | setTextContent( e, txt ); 406 | } 407 | else 408 | { 409 | var $w = $e.parent(); 410 | $e.remove(); 411 | 412 | var afterLength = ( after ) ? after.length : 0 ; 413 | 414 | if ( $w.contents().size() > afterLength ) 415 | { 416 | var $n = $w.contents().eq( -1 - afterLength ); 417 | isTruncated = ellipsisElement( $n, $d, $i, o, after ); 418 | } 419 | else 420 | { 421 | var $p = $w.prev() 422 | var e = $p.contents().eq( -1 )[ 0 ]; 423 | 424 | if ( typeof e != 'undefined' ) 425 | { 426 | var txt = addEllipsis( getTextContent( e ), o ); 427 | setTextContent( e, txt ); 428 | if ( after ) 429 | { 430 | $p.append( after ); 431 | } 432 | $w.remove(); 433 | isTruncated = true; 434 | } 435 | 436 | } 437 | } 438 | 439 | return isTruncated; 440 | } 441 | function test( $i, o ) 442 | { 443 | return $i.innerHeight() > o.maxHeight; 444 | } 445 | function addEllipsis( txt, o ) 446 | { 447 | while( $.inArray( txt.slice( -1 ), o.lastCharacter.remove ) > -1 ) 448 | { 449 | txt = txt.slice( 0, -1 ); 450 | } 451 | if ( $.inArray( txt.slice( -1 ), o.lastCharacter.noEllipsis ) < 0 ) 452 | { 453 | txt += o.ellipsis; 454 | } 455 | return txt; 456 | } 457 | function getSizes( $d ) 458 | { 459 | return { 460 | 'width' : $d.innerWidth(), 461 | 'height': $d.innerHeight() 462 | }; 463 | } 464 | function setTextContent( e, content ) 465 | { 466 | if ( e.innerText ) 467 | { 468 | e.innerText = content; 469 | } 470 | else if ( e.nodeValue ) 471 | { 472 | e.nodeValue = content; 473 | } 474 | else if (e.textContent) 475 | { 476 | e.textContent = content; 477 | } 478 | 479 | } 480 | function getTextContent( e ) 481 | { 482 | if ( e.innerText ) 483 | { 484 | return e.innerText; 485 | } 486 | else if ( e.nodeValue ) 487 | { 488 | return e.nodeValue; 489 | } 490 | else if ( e.textContent ) 491 | { 492 | return e.textContent; 493 | } 494 | else 495 | { 496 | return ""; 497 | } 498 | } 499 | function getElement( e, $i ) 500 | { 501 | if ( typeof e == 'undefined' ) 502 | { 503 | return false; 504 | } 505 | if ( !e ) 506 | { 507 | return false; 508 | } 509 | if ( typeof e == 'string' ) 510 | { 511 | e = $(e, $i); 512 | return ( e.length ) 513 | ? e 514 | : false; 515 | } 516 | if ( typeof e == 'object' ) 517 | { 518 | return ( typeof e.jquery == 'undefined' ) 519 | ? false 520 | : e; 521 | } 522 | return false; 523 | } 524 | function getTrueInnerHeight( $el ) 525 | { 526 | var h = $el.innerHeight(), 527 | a = [ 'paddingTop', 'paddingBottom' ]; 528 | 529 | for ( var z = 0, l = a.length; z < l; z++ ) { 530 | var m = parseInt( $el.css( a[ z ] ), 10 ); 531 | if ( isNaN( m ) ) 532 | { 533 | m = 0; 534 | } 535 | h -= m; 536 | } 537 | return h; 538 | } 539 | function debug( d, m ) 540 | { 541 | if ( !d ) 542 | { 543 | return false; 544 | } 545 | if ( typeof m == 'string' ) 546 | { 547 | m = 'dotdotdot: ' + m; 548 | } 549 | else 550 | { 551 | m = [ 'dotdotdot:', m ]; 552 | } 553 | 554 | if ( typeof window.console != 'undefined' ) 555 | { 556 | if ( typeof window.console.log != 'undefined' ) 557 | { 558 | window.console.log( m ); 559 | } 560 | } 561 | return false; 562 | } 563 | 564 | 565 | // override jQuery.html 566 | var _orgHtml = $.fn.html; 567 | $.fn.html = function( str ) { 568 | if ( typeof str != 'undefined' ) 569 | { 570 | if ( this.data( 'dotdotdot' ) ) 571 | { 572 | if ( typeof str != 'function' ) 573 | { 574 | return this.trigger( 'update', [ str ] ); 575 | } 576 | } 577 | return _orgHtml.call( this, str ); 578 | } 579 | return _orgHtml.call( this ); 580 | }; 581 | 582 | 583 | // override jQuery.text 584 | var _orgText = $.fn.text; 585 | $.fn.text = function( str ) { 586 | if ( typeof str != 'undefined' ) 587 | { 588 | if ( this.data( 'dotdotdot' ) ) 589 | { 590 | var temp = $( '
' ); 591 | temp.text( str ); 592 | str = temp.html(); 593 | temp.remove(); 594 | return this.trigger( 'update', [ str ] ); 595 | } 596 | return _orgText.call( this, str ); 597 | } 598 | return _orgText.call( this ); 599 | }; 600 | 601 | 602 | })( jQuery ); 603 | -------------------------------------------------------------------------------- /docs/api/js/jquery.dotdotdot-1.5.9.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery dotdotdot 1.5.9 3 | * 4 | * Copyright (c) 2013 Fred Heusschen 5 | * www.frebsite.nl 6 | * 7 | * Plugin website: 8 | * dotdotdot.frebsite.nl 9 | * 10 | * Dual licensed under the MIT and GPL licenses. 11 | * http://en.wikipedia.org/wiki/MIT_License 12 | * http://en.wikipedia.org/wiki/GNU_General_Public_License 13 | */ 14 | 15 | (function(a){function c(a,b,c){var d=a.children(),e=!1;a.empty();for(var g=0,h=d.length;h>g;g++){var i=d.eq(g);if(a.append(i),c&&a.append(c),f(a,b)){i.remove(),e=!0;break}c&&c.remove()}return e}function d(b,c,g,h,i){var j=b.contents(),k=!1;b.empty();for(var l="table, thead, tbody, tfoot, tr, col, colgroup, object, embed, param, ol, ul, dl, select, optgroup, option, textarea, script, style",m=0,n=j.length;n>m&&!k;m++){var o=j[m],p=a(o);void 0!==o&&(b.append(p),i&&b[b.is(l)?"after":"append"](i),3==o.nodeType?f(g,h)&&(k=e(p,c,g,h,i)):k=d(p,c,g,h,i),k||i&&i.remove())}return k}function e(a,b,c,d,h){var k=!1,l=a[0];if(l===void 0)return!1;for(var m="letter"==d.wrap?"":" ",n=j(l).split(m),o=-1,p=-1,q=0,r=n.length-1;r>=q;){var s=Math.floor((q+r)/2);if(s==p)break;p=s,i(l,n.slice(0,p+1).join(m)+d.ellipsis),f(c,d)?r=p:(o=p,q=p)}if(-1==o||1==n.length&&0==n[0].length){var u=a.parent();a.remove();var v=h?h.length:0;if(u.contents().size()>v){var w=u.contents().eq(-1-v);k=e(w,b,c,d,h)}else{var x=u.prev(),l=x.contents().eq(-1)[0];if(l!==void 0){var t=g(j(l),d);i(l,t),h&&x.append(h),u.remove(),k=!0}}}else{var t=g(n.slice(0,o+1).join(m),d);k=!0,i(l,t)}return k}function f(a,b){return a.innerHeight()>b.maxHeight}function g(b,c){for(;a.inArray(b.slice(-1),c.lastCharacter.remove)>-1;)b=b.slice(0,-1);return 0>a.inArray(b.slice(-1),c.lastCharacter.noEllipsis)&&(b+=c.ellipsis),b}function h(a){return{width:a.innerWidth(),height:a.innerHeight()}}function i(a,b){a.innerText?a.innerText=b:a.nodeValue?a.nodeValue=b:a.textContent&&(a.textContent=b)}function j(a){return a.innerText?a.innerText:a.nodeValue?a.nodeValue:a.textContent?a.textContent:""}function k(b,c){return b===void 0?!1:b?"string"==typeof b?(b=a(b,c),b.length?b:!1):"object"==typeof b?b.jquery===void 0?!1:b:!1:!1}function l(a){for(var b=a.innerHeight(),c=["paddingTop","paddingBottom"],d=0,e=c.length;e>d;d++){var f=parseInt(a.css(c[d]),10);isNaN(f)&&(f=0),b-=f}return b}function m(a,b){return a?(b="string"==typeof b?"dotdotdot: "+b:["dotdotdot:",b],window.console!==void 0&&window.console.log!==void 0&&window.console.log(b),!1):!1}if(!a.fn.dotdotdot){a.fn.dotdotdot=function(e){if(0==this.length)return e&&e.debug===!1||m(!0,'No element found for "'+this.selector+'".'),this;if(this.length>1)return this.each(function(){a(this).dotdotdot(e)});var g=this;g.data("dotdotdot")&&g.trigger("destroy.dot"),g.data("dotdotdot-style",g.attr("style")),g.css("word-wrap","break-word"),g.bind_events=function(){return g.bind("update.dot",function(b,e){b.preventDefault(),b.stopPropagation(),j.maxHeight="number"==typeof j.height?j.height:l(g),j.maxHeight+=j.tolerance,e!==void 0&&(("string"==typeof e||e instanceof HTMLElement)&&(e=a("
").append(e).contents()),e instanceof a&&(i=e)),q=g.wrapInner('
').children(),q.empty().append(i.clone(!0)).css({height:"auto",width:"auto",border:"none",padding:0,margin:0});var h=!1,k=!1;return n.afterElement&&(h=n.afterElement.clone(!0),n.afterElement.remove()),f(q,j)&&(k="children"==j.wrap?c(q,j,h):d(q,g,q,j,h)),q.replaceWith(q.contents()),q=null,a.isFunction(j.callback)&&j.callback.call(g[0],k,i),n.isTruncated=k,k}).bind("isTruncated.dot",function(a,b){return a.preventDefault(),a.stopPropagation(),"function"==typeof b&&b.call(g[0],n.isTruncated),n.isTruncated}).bind("originalContent.dot",function(a,b){return a.preventDefault(),a.stopPropagation(),"function"==typeof b&&b.call(g[0],i),i}).bind("destroy.dot",function(a){a.preventDefault(),a.stopPropagation(),g.unwatch().unbind_events().empty().append(i).attr("style",g.data("dotdotdot-style")).data("dotdotdot",!1)}),g},g.unbind_events=function(){return g.unbind(".dot"),g},g.watch=function(){if(g.unwatch(),"window"==j.watch){var b=a(window),c=b.width(),d=b.height();b.bind("resize.dot"+n.dotId,function(){c==b.width()&&d==b.height()&&j.windowResizeFix||(c=b.width(),d=b.height(),p&&clearInterval(p),p=setTimeout(function(){g.trigger("update.dot")},10))})}else o=h(g),p=setInterval(function(){var a=h(g);(o.width!=a.width||o.height!=a.height)&&(g.trigger("update.dot"),o=h(g))},100);return g},g.unwatch=function(){return a(window).unbind("resize.dot"+n.dotId),p&&clearInterval(p),g};var i=g.contents(),j=a.extend(!0,{},a.fn.dotdotdot.defaults,e),n={},o={},p=null,q=null;return n.afterElement=k(j.after,g),n.isTruncated=!1,n.dotId=b++,g.data("dotdotdot",!0).bind_events().trigger("update.dot"),j.watch&&g.watch(),g},a.fn.dotdotdot.defaults={ellipsis:"... ",wrap:"word",lastCharacter:{remove:[" ",",",";",".","!","?"],noEllipsis:[]},tolerance:0,callback:null,after:null,height:null,watch:!1,windowResizeFix:!0,debug:!1};var b=1,n=a.fn.html;a.fn.html=function(a){return a!==void 0?this.data("dotdotdot")&&"function"!=typeof a?this.trigger("update",[a]):n.call(this,a):n.call(this)};var o=a.fn.text;a.fn.text=function(b){if(b!==void 0){if(this.data("dotdotdot")){var c=a("
");return c.text(b),b=c.html(),c.remove(),this.trigger("update",[b])}return o.call(this,b)}return o.call(this)}}})(jQuery); -------------------------------------------------------------------------------- /docs/api/js/jquery.mousewheel.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh) 2 | * Licensed under the MIT License (LICENSE.txt). 3 | * 4 | * Version: 3.1.9 5 | * 6 | * Requires: jQuery 1.2.2+ 7 | */ 8 | 9 | (function (factory) { 10 | if ( typeof define === 'function' && define.amd ) { 11 | // AMD. Register as an anonymous module. 12 | define(['jquery'], factory); 13 | } else if (typeof exports === 'object') { 14 | // Node/CommonJS style for Browserify 15 | module.exports = factory; 16 | } else { 17 | // Browser globals 18 | factory(jQuery); 19 | } 20 | }(function ($) { 21 | 22 | var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'], 23 | toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? 24 | ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'], 25 | slice = Array.prototype.slice, 26 | nullLowestDeltaTimeout, lowestDelta; 27 | 28 | if ( $.event.fixHooks ) { 29 | for ( var i = toFix.length; i; ) { 30 | $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks; 31 | } 32 | } 33 | 34 | var special = $.event.special.mousewheel = { 35 | version: '3.1.9', 36 | 37 | setup: function() { 38 | if ( this.addEventListener ) { 39 | for ( var i = toBind.length; i; ) { 40 | this.addEventListener( toBind[--i], handler, false ); 41 | } 42 | } else { 43 | this.onmousewheel = handler; 44 | } 45 | // Store the line height and page height for this particular element 46 | $.data(this, 'mousewheel-line-height', special.getLineHeight(this)); 47 | $.data(this, 'mousewheel-page-height', special.getPageHeight(this)); 48 | }, 49 | 50 | teardown: function() { 51 | if ( this.removeEventListener ) { 52 | for ( var i = toBind.length; i; ) { 53 | this.removeEventListener( toBind[--i], handler, false ); 54 | } 55 | } else { 56 | this.onmousewheel = null; 57 | } 58 | }, 59 | 60 | getLineHeight: function(elem) { 61 | return parseInt($(elem)['offsetParent' in $.fn ? 'offsetParent' : 'parent']().css('fontSize'), 10); 62 | }, 63 | 64 | getPageHeight: function(elem) { 65 | return $(elem).height(); 66 | }, 67 | 68 | settings: { 69 | adjustOldDeltas: true 70 | } 71 | }; 72 | 73 | $.fn.extend({ 74 | mousewheel: function(fn) { 75 | return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel'); 76 | }, 77 | 78 | unmousewheel: function(fn) { 79 | return this.unbind('mousewheel', fn); 80 | } 81 | }); 82 | 83 | 84 | function handler(event) { 85 | var orgEvent = event || window.event, 86 | args = slice.call(arguments, 1), 87 | delta = 0, 88 | deltaX = 0, 89 | deltaY = 0, 90 | absDelta = 0; 91 | event = $.event.fix(orgEvent); 92 | event.type = 'mousewheel'; 93 | 94 | // Old school scrollwheel delta 95 | if ( 'detail' in orgEvent ) { deltaY = orgEvent.detail * -1; } 96 | if ( 'wheelDelta' in orgEvent ) { deltaY = orgEvent.wheelDelta; } 97 | if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY; } 98 | if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; } 99 | 100 | // Firefox < 17 horizontal scrolling related to DOMMouseScroll event 101 | if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { 102 | deltaX = deltaY * -1; 103 | deltaY = 0; 104 | } 105 | 106 | // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy 107 | delta = deltaY === 0 ? deltaX : deltaY; 108 | 109 | // New school wheel delta (wheel event) 110 | if ( 'deltaY' in orgEvent ) { 111 | deltaY = orgEvent.deltaY * -1; 112 | delta = deltaY; 113 | } 114 | if ( 'deltaX' in orgEvent ) { 115 | deltaX = orgEvent.deltaX; 116 | if ( deltaY === 0 ) { delta = deltaX * -1; } 117 | } 118 | 119 | // No change actually happened, no reason to go any further 120 | if ( deltaY === 0 && deltaX === 0 ) { return; } 121 | 122 | // Need to convert lines and pages to pixels if we aren't already in pixels 123 | // There are three delta modes: 124 | // * deltaMode 0 is by pixels, nothing to do 125 | // * deltaMode 1 is by lines 126 | // * deltaMode 2 is by pages 127 | if ( orgEvent.deltaMode === 1 ) { 128 | var lineHeight = $.data(this, 'mousewheel-line-height'); 129 | delta *= lineHeight; 130 | deltaY *= lineHeight; 131 | deltaX *= lineHeight; 132 | } else if ( orgEvent.deltaMode === 2 ) { 133 | var pageHeight = $.data(this, 'mousewheel-page-height'); 134 | delta *= pageHeight; 135 | deltaY *= pageHeight; 136 | deltaX *= pageHeight; 137 | } 138 | 139 | // Store lowest absolute delta to normalize the delta values 140 | absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) ); 141 | 142 | if ( !lowestDelta || absDelta < lowestDelta ) { 143 | lowestDelta = absDelta; 144 | 145 | // Adjust older deltas if necessary 146 | if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) { 147 | lowestDelta /= 40; 148 | } 149 | } 150 | 151 | // Adjust older deltas if necessary 152 | if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) { 153 | // Divide all the things by 40! 154 | delta /= 40; 155 | deltaX /= 40; 156 | deltaY /= 40; 157 | } 158 | 159 | // Get a whole, normalized value for the deltas 160 | delta = Math[ delta >= 1 ? 'floor' : 'ceil' ](delta / lowestDelta); 161 | deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta); 162 | deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta); 163 | 164 | // Add information to the event object 165 | event.deltaX = deltaX; 166 | event.deltaY = deltaY; 167 | event.deltaFactor = lowestDelta; 168 | // Go ahead and set deltaMode to 0 since we converted to pixels 169 | // Although this is a little odd since we overwrite the deltaX/Y 170 | // properties with normalized deltas. 171 | event.deltaMode = 0; 172 | 173 | // Add event and delta to the front of the arguments 174 | args.unshift(event, delta, deltaX, deltaY); 175 | 176 | // Clearout lowestDelta after sometime to better 177 | // handle multiple device types that give different 178 | // a different lowestDelta 179 | // Ex: trackpad = 3 and mouse wheel = 120 180 | if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); } 181 | nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200); 182 | 183 | return ($.event.dispatch || $.event.handle).apply(this, args); 184 | } 185 | 186 | function nullLowestDelta() { 187 | lowestDelta = null; 188 | } 189 | 190 | function shouldAdjustOldDeltas(orgEvent, absDelta) { 191 | // If this is an older event and the delta is divisable by 120, 192 | // then we are assuming that the browser is treating this as an 193 | // older mouse wheel event and that we should divide the deltas 194 | // by 40 to try and get a more usable deltaFactor. 195 | // Side note, this actually impacts the reported scroll distance 196 | // in older browsers and can cause scrolling to be slower than native. 197 | // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false. 198 | return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0; 199 | } 200 | 201 | })); 202 | -------------------------------------------------------------------------------- /docs/api/js/jquery.smooth-scroll.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | function filterPath(string) { 3 | return string 4 | .replace(/^\//,'') 5 | .replace(/(index|default).[a-zA-Z]{3,4}$/,'') 6 | .replace(/\/$/,''); 7 | } 8 | var locationPath = filterPath(location.pathname); 9 | 10 | $('a[href*=#]').each(function() { 11 | var thisPath = filterPath(this.pathname) || locationPath; 12 | if ( locationPath == thisPath 13 | && (location.hostname == this.hostname || !this.hostname) 14 | && this.hash.replace(/#/,'') ) { 15 | var $target = $(this.hash), target = this.hash; 16 | if (target) { 17 | $(this).click(function(event) { 18 | if (!$(this.hash).offset()) { 19 | return; 20 | } 21 | 22 | event.preventDefault(); 23 | position = $(this.hash).offset().top; 24 | 25 | $('html,body').animate({scrollTop: position}, 400, function() { 26 | location.hash = target; 27 | }); 28 | }); 29 | } 30 | } 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /docs/api/js/prism.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prism: Lightweight, robust, elegant syntax highlighting 3 | * MIT license http://www.opensource.org/licenses/mit-license.php/ 4 | * @author Lea Verou http://lea.verou.me 5 | */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();; 6 | Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});; 7 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//g,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*{))/gi,inside:{punctuation:/[;:]/g}},url:/url\((["']?).*?\1\)/gi,selector:/[^\{\}\s][^\{\};]*(?=\s*\{)/g,property:/(\b|\B)[\w-]+(?=\s*:)/ig,string:/("|')(\\?.)*?\1/g,important:/\B!important\b/gi,ignore:/&(lt|gt|amp);/gi,punctuation:/[\{\};:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<|<)style[\w\W]*?(>|>)[\w\W]*?(<|<)\/style(>|>)/ig,inside:{tag:{pattern:/(<|<)style[\w\W]*?(>|>)|(<|<)\/style(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css}}});; 8 | Prism.languages.css.selector={pattern:/[^\{\}\s][^\{\}]*(?=\s*\{)/g,inside:{"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/g,"pseudo-class":/:[-\w]+(?:\(.*\))?/g,"class":/\.[-:\.\w]+/g,id:/#[-:\.\w]+/g}};Prism.languages.insertBefore("css","ignore",{hexcode:/#[\da-f]{3,6}/gi,entity:/\\[\da-f]{1,8}/gi,number:/[\d%\.]+/g,"function":/(attr|calc|cross-fade|cycle|element|hsla?|image|lang|linear-gradient|matrix3d|matrix|perspective|radial-gradient|repeating-linear-gradient|repeating-radial-gradient|rgba?|rotatex|rotatey|rotatez|rotate3d|rotate|scalex|scaley|scalez|scale3d|scale|skewx|skewy|skew|steps|translatex|translatey|translatez|translate3d|translate|url|var)/ig});; 9 | Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};; 10 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; 11 | Prism.languages.php=Prism.languages.extend("clike",{keyword:/\b(and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|extends|private|protected|parent|static|throw|null|echo|print|trait|namespace|use|final|yield|goto|instanceof|finally|try|catch)\b/ig, constant:/\b[A-Z0-9_]{2,}\b/g});Prism.languages.insertBefore("php","keyword",{delimiter:/(\?>|<\?php|<\?)/ig,variable:/(\$\w+)\b/ig,"package":{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/g,lookbehind:!0,inside:{punctuation:/\\/}}});Prism.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/g,lookbehind:!0}}); Prism.languages.markup&&(Prism.hooks.add("before-highlight",function(a){"php"===a.language&&(a.tokenStack=[],a.code=a.code.replace(/(?:<\?php|<\?|<\?php|<\?)[\w\W]*?(?:\?>|\?>)/ig,function(b){a.tokenStack.push(b);return"{{{PHP"+a.tokenStack.length+"}}}"}))}),Prism.hooks.add("after-highlight",function(a){if("php"===a.language){for(var b=0,c;c=a.tokenStack[b];b++)a.highlightedCode=a.highlightedCode.replace("{{{PHP"+(b+1)+"}}}",Prism.highlight(c,a.grammar,"php"));a.element.innerHTML=a.highlightedCode}}), Prism.hooks.add("wrap",function(a){"php"===a.language&&"markup"===a.type&&(a.content=a.content.replace(/(\{\{\{PHP[0-9]+\}\}\})/g,'$1'))}),Prism.languages.insertBefore("php","comment",{markup:{pattern:/(<|<)[^?]\/?(.*?)(>|>)/g,inside:Prism.languages.markup},php:/\{\{\{PHP[0-9]+\}\}\}/g}));; 12 | Prism.languages.insertBefore("php","variable",{"this":/\$this/g,global:/\$_?(GLOBALS|SERVER|GET|POST|FILES|REQUEST|SESSION|ENV|COOKIE|HTTP_RAW_POST_DATA|argc|argv|php_errormsg|http_response_header)/g,scope:{pattern:/\b[\w\\]+::/g,inside:{keyword:/(static|self|parent)/,punctuation:/(::|\\)/}}});; 13 | (function(){function e(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function n(e,t,n){var r=t.replace(/\s+/g,"").split(","),i=+e.getAttribute("data-line-offset")||0,s=parseFloat(getComputedStyle(e).lineHeight);for(var o=0,u;u=r[o++];){u=u.split("-");var a=+u[0],f=+u[1]||a,l=document.createElement("div");l.textContent=Array(f-a+2).join(" \r\n");l.className=(n||"")+" line-highlight";l.setAttribute("data-start",a);f>a&&l.setAttribute("data-end",f);l.style.top=(a-i-1)*s+"px";(e.querySelector("code")||e).appendChild(l)}}function r(){var t=location.hash.slice(1);e(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var r=(t.match(/\.([\d,-]+)$/)||[,""])[1];if(!r||document.getElementById(t))return;var i=t.slice(0,t.lastIndexOf(".")),s=document.getElementById(i);if(!s)return;s.hasAttribute("data-line")||s.setAttribute("data-line","");n(s,r,"temporary ");document.querySelector(".temporary.line-highlight").scrollIntoView()}if(!window.Prism)return;var t=crlf=/\r?\n|\r/g,i=0;Prism.hooks.add("after-highlight",function(t){var s=t.element.parentNode,o=s&&s.getAttribute("data-line");if(!s||!o||!/pre/i.test(s.nodeName))return;clearTimeout(i);e(".line-highlight",s).forEach(function(e){e.parentNode.removeChild(e)});n(s,o);i=setTimeout(r,1)});addEventListener("hashchange",r)})();; 14 | Prism.hooks.add("after-highlight",function(e){var t=e.element.parentNode;if(!t||!/pre/i.test(t.nodeName)||t.className.indexOf("line-numbers")===-1){return}var n=1+e.code.split("\n").length;var r;lines=new Array(n);lines=lines.join("");r=document.createElement("span");r.className="line-numbers-rows";r.innerHTML=lines;if(t.hasAttribute("data-start")){t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)}e.element.appendChild(r)}) 15 | ; 16 | (function(){if(!self.Prism||!self.document||!document.querySelector)return;var e={js:"javascript",html:"markup",svg:"markup"};Array.prototype.slice.call(document.querySelectorAll("pre[data-src]")).forEach(function(t){var n=t.getAttribute("data-src"),r=(n.match(/\.(\w+)$/)||[,""])[1],i=e[r]||r,s=document.createElement("code");s.className="language-"+i;t.textContent="";s.textContent="Loading…";t.appendChild(s);var o=new XMLHttpRequest;o.open("GET",n,!0);o.onreadystatechange=function(){if(o.readyState==4)if(o.status<400&&o.responseText){s.textContent=o.responseText;Prism.highlightElement(s)}else o.status>=400?s.textContent="✖ Error "+o.status+" while fetching file: "+o.statusText:s.textContent="✖ Error: File does not exist or is empty"};o.send(null)})})();; 17 | -------------------------------------------------------------------------------- /docs/api/reports/deprecated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » Deprecated elements 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 93 | 94 |
95 |
96 | 97 | 102 | 103 |
104 | 108 | 109 |
110 |
No deprecated elements have been found in this project.
111 | 112 |
113 |
114 |
115 |
116 | 117 |
118 |
119 |
120 |
121 | 148 |
149 |
150 |
151 |
152 |
153 | Documentation is powered by phpDocumentor and authored 154 | on January 15th, 2017 at 01:12. 155 |
156 |
157 |
158 |
159 |
160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/api/reports/errors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » Compilation errors 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 93 | 94 |
95 |
96 | 104 | 105 |
106 | 110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 122 |

123 | 124 | Exception.php 125 | 1 126 |

127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
TypeLineDescription
error12No summary for class \IBT\JsonDB\Exception
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | 152 |

153 | 154 | Indexer/Exception.php 155 | 1 156 |

157 |
158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 |
TypeLineDescription
error12No summary for class \IBT\JsonDB\Indexer\Exception
174 |
175 |
176 |
177 |
178 | 179 |
180 |
181 |
182 |
183 | 210 |
211 |
212 |
213 |
214 |
215 | Documentation is powered by phpDocumentor and authored 216 | on January 15th, 2017 at 01:12. 217 |
218 |
219 |
220 |
221 |
222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /docs/api/reports/markers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » Markers 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 93 | 94 |
95 |
96 | 101 | 102 |
103 | 104 | 108 | 109 |
No markers have been found in this project.
110 | 111 |
112 |
113 |
114 |
115 | 116 |
117 |
118 |
119 |
120 | 147 |
148 |
149 |
150 |
151 |
152 | Documentation is powered by phpDocumentor and authored 153 | on January 15th, 2017 at 01:12. 154 |
155 |
156 |
157 |
158 |
159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /docs/getting_started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | ## Installation (new project) 4 | 5 | ``` 6 | composer init --require="johnwilson/jsondb" 7 | ``` 8 | 9 | ## Call autoloader 10 | 11 | ```PHP 12 | require_once __DIR__ . '/vendor/autoload.php'; 13 | ``` 14 | 15 | ## Import the relevant namespaces as required: 16 | 17 | ```PHP 18 | use IBT\JsonDB\Client; 19 | use IBT\JsonDB\Collection; 20 | ``` 21 | 22 | [Continue to Tutorial](https://github.com/johnwilson/jsondb/blob/master/docs/tutorial.md) or [Back to Table of Contents](https://github.com/johnwilson/jsondb/blob/master/docs/index.md) -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | JsonDB borrows a few concepts from Mongodb such as: 2 | 3 | * **Document**: which is a valid JSON object stored in the database 4 | * **Collection**: which is a group of documents (comparable to a table but schema-less) 5 | 6 | JsonDB also gives you the power to query your JSON data using familiar SQL statements (`Where`, `Or Where`, `Order By`). 7 | 8 | ## Contents 9 | 10 | * [Getting Started](https://github.com/johnwilson/jsondb/blob/master/docs/getting_started.md) 11 | * [Tutorial](https://github.com/johnwilson/jsondb/blob/master/docs/tutorial.md) -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | # Quick tutorial 2 | 3 | This tutorial will get you up and running with JsonDB by performing basic CRUD (Create, Read, Update and Delete) operations. For further usage guidelines you can have a look at the code in the `tests` directory. 4 | 5 | ## Preparation 6 | 7 | If you've setup your project according to the guidelines in the [getting started](https://github.com/johnwilson/jsondb/blob/master/docs/getting_started.md) section, you should be ready to follow this tutorial. 8 | 9 | The following code will get you ready to run your CRUD operations 10 | 11 | ```PHP 12 | 'database', 24 | 'username' => 'username', 25 | 'password' => 'password' 26 | ]); 27 | 28 | // setup 29 | $c->setup(); 30 | ``` 31 | 32 | ## Creating a document 33 | 34 | JSON documents reside in **Collections** which can be thought of as tables. Their main purpose is to keep your documents grouped in any logical manner you see fit. 35 | 36 | ```PHP 37 | // create collection 38 | $col = $c->newCollection("users"); 39 | ``` 40 | 41 | Each JSON document has a special `_id` field which you can add yourself or let the library create it for you. the `_id` is an alpha-numeric only string. 42 | 43 | ```PHP 44 | // insert json 45 | $j = '{"name":"jason bourne", "category":"agent", "_id": "1"}'; 46 | $oid = $col->insert($j); 47 | ``` 48 | 49 | The value returned on insert will be either the library generated `_id` which will be similar to `oid57b57b407fe93` or the value you supplied in the `_id` field of your JSON document. 50 | 51 | ## Reading a document 52 | 53 | Retrieving a document for which you have the `_id` is as simple as running the following which will return your document as a PHP `object`: 54 | 55 | ```PHP 56 | $obj = $col->get($oid); 57 | ``` 58 | 59 | ## Updating a document 60 | 61 | You can modify your JSON document and save it to the database just as simply as doing the following: 62 | 63 | ```PHP 64 | $obj->category = "ghost"; 65 | $col->update(json_encode($obj)); 66 | ``` 67 | 68 | A boolean is returned to indicate whether the operation was successful. 69 | 70 | Care should be taken not to change or remove the `_id` field or the library will assume the document is new and create a new entry. 71 | 72 | ## Deleting a document 73 | 74 | Finally to delete the document you'd do the following: 75 | 76 | ```PHP 77 | $col->delete($oid); 78 | ``` 79 | 80 | Again a boolean value will indicate the success or failure of the operation. 81 | 82 | [Back to Table of Contents](https://github.com/johnwilson/jsondb/blob/master/docs/index.md) 83 | 84 | -------------------------------------------------------------------------------- /jsondb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwilson/jsondb/7f72443c69128f24cb1cf7ec3adb31263729db3f/jsondb.png -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | setup(); 24 | * ``` 25 | */ 26 | class Client { 27 | 28 | /** 29 | * Collection Table name 30 | */ 31 | const T_COLLECTIONS = 'jsondb_collection'; 32 | 33 | /** 34 | * JSON data Table name 35 | * 36 | */ 37 | const T_JSON = 'jsondb_json'; 38 | 39 | /** 40 | * JSON indices Table name 41 | * 42 | */ 43 | const T_INDEX = 'jsondb_index'; 44 | 45 | /** 46 | * Configuration manager for Illuminate framework 47 | * 48 | * @var \Illuminate\Database\Capsule\Manager 49 | * @see https://laravel.com/api/5.2/Illuminate/Database/Capsule/Manager.html. 50 | */ 51 | private $capsule; 52 | 53 | /** 54 | * Constructor, creates a new Client 55 | * 56 | * @param array $config 57 | */ 58 | public function __construct($config = array()) { 59 | // setup db connection 60 | $capsule = new Capsule(); 61 | 62 | // default configuration 63 | $default = [ 64 | 'driver' => env('DB_DRIVER', 'mysql'), 65 | 'host' => env('DB_HOST', 'localhost'), 66 | 'port' => env('DB_PORT', 3306), 67 | 'database' => env('DB_DATABASE', ''), 68 | 'username' => env('DB_USERNAME', ''), 69 | 'password' => env('DB_PASSWORD', ''), 70 | 'charset' => env('DB_CHARSET', 'utf8'), 71 | 'collation' => env('DB_COLLATION', 'utf8_unicode_ci') 72 | ]; 73 | 74 | // create final config 75 | $config = array_merge($default, $config); 76 | 77 | $capsule->addConnection($config); 78 | $this->capsule = $capsule; 79 | } 80 | 81 | /** 82 | * Creates/Recreates required tables in database 83 | * 84 | */ 85 | public function setup() { 86 | $sb = $this->connection()->getSchemaBuilder(); 87 | 88 | // drop existing tables 89 | $sb->dropIfExists(self::T_INDEX); 90 | $sb->dropIfExists(self::T_JSON); 91 | $sb->dropIfExists(self::T_COLLECTIONS); 92 | 93 | // collections table 94 | $sb->create(self::T_COLLECTIONS, function(Blueprint $table){ 95 | $table->increments('id'); 96 | $table->string("name"); 97 | $table->unique("name"); 98 | }); 99 | 100 | // json document table 101 | $sb->create(self::T_JSON, function(Blueprint $table){ 102 | $table->increments('id'); 103 | 104 | // document id 105 | $table->string("doc_id", 36); 106 | $table->unique("doc_id"); 107 | 108 | // collection id 109 | $table->integer('cid')->unsigned(); 110 | $table->foreign('cid')->references('id') 111 | ->on(self::T_COLLECTIONS) 112 | ->onDelete('cascade'); 113 | 114 | $table->text('data'); 115 | }); 116 | 117 | // index table 118 | $sb->create(self::T_INDEX, function(Blueprint $table){ 119 | $table->increments('id'); 120 | 121 | $table->integer('cid')->unsigned(); 122 | $table->foreign('cid')->references('id') 123 | ->on(self::T_COLLECTIONS) 124 | ->onDelete('cascade'); 125 | 126 | // object id (primary key) 127 | $table->integer('oid')->unsigned(); 128 | $table->foreign('oid')->references('id') 129 | ->on(self::T_JSON) 130 | ->onDelete('cascade'); 131 | 132 | $table->integer('depth'); 133 | $table->string('typ'); 134 | 135 | $table->string('path'); 136 | $table->index('path'); 137 | 138 | // values 139 | $table->text('vjson')->nullable(); 140 | $table->boolean('vboolean')->nullable(); 141 | $table->text('vstring')->nullable(); 142 | $table->float('vfloat')->nullable(); 143 | $table->boolean('vnull')->nullable(); 144 | }); 145 | } 146 | 147 | /** 148 | * Current database connection 149 | * @return \Illuminate\Database\Connection 150 | */ 151 | public function connection() { 152 | return $this->capsule->getConnection(); 153 | } 154 | 155 | /** 156 | * Create a new Collection 157 | * @param string $name Collection name 158 | * @return \IBT\JsonDB\Collection 159 | * @throws Exception if the collection name already exists/is invalid 160 | */ 161 | public function newCollection($name) { 162 | $name = strtolower($name); 163 | 164 | if(!Collection::isValidName($name)) { 165 | throw new Exception("The collection name $name isn't valid"); 166 | } 167 | 168 | if(in_array($name, $this->listCollection())) { 169 | throw new Exception("The collection name $name already exists"); 170 | } 171 | 172 | $conn = $this->connection(); 173 | $id = $conn->table(self::T_COLLECTIONS)->insertGetId( 174 | ['name' => $name] 175 | ); 176 | 177 | return new Collection($id, $name, $this); 178 | } 179 | 180 | /** 181 | * Return existing Collection 182 | * @param string $name Collection name 183 | * @return \IBT\JsonDB\Collection 184 | * @throws Exception if the collection name doesn't exist 185 | */ 186 | public function getCollection($name) { 187 | $name = strtolower($name); 188 | $conn = $this->connection(); 189 | 190 | // check for existence 191 | $row = $conn->table(self::T_COLLECTIONS)->select("id", "name")->where("name", $name)->first(); 192 | if(!$row) { 193 | throw new Exception("The collection name $name doesn't exist"); 194 | } 195 | 196 | return new Collection($row->id, $row->name, $this); 197 | } 198 | 199 | /** 200 | * List all collections 201 | * @return array 202 | */ 203 | public function listCollection() { 204 | $conn = $this->connection(); 205 | $name = $conn->table(self::T_COLLECTIONS)->pluck("name"); 206 | return $name; 207 | } 208 | } -------------------------------------------------------------------------------- /src/Collection.php: -------------------------------------------------------------------------------- 1 | setup(); 21 | * $col = $c->newCollection("users"); 22 | * $col->insert('{"name":"jason bourne", "category":"agent"}'); 23 | * ``` 24 | */ 25 | class Collection { 26 | 27 | /** 28 | * @var string Name of the Collection 29 | */ 30 | private $name; 31 | 32 | /** 33 | * @var int Collection database primary key 34 | */ 35 | private $id; 36 | 37 | /** 38 | * @var Client JsonDB Client 39 | */ 40 | private $client; 41 | 42 | /** 43 | * Constructor, creates a new Collection 44 | * 45 | * @param int $id Database primary key 46 | * @param string $name Collection name 47 | * @param Client $client 48 | */ 49 | function __construct($id, $name, Client $client) { 50 | $this->id = $id; 51 | $this->name = $name; 52 | $this->client = $client; 53 | } 54 | 55 | /** 56 | * Validate Collection name 57 | * 58 | * @param string $name Collection name 59 | * @return boolean 60 | */ 61 | static function isValidName($name) { 62 | // only accept alpha characters 63 | $p = '/^[a-zA-Z]+$/'; 64 | $m = preg_match($p, $name); 65 | if($m) { 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | /** 73 | * Collection name 74 | * 75 | * @return string 76 | */ 77 | function getName() { 78 | return $this->name; 79 | } 80 | 81 | /** 82 | * Collection id 83 | * 84 | * @return int 85 | */ 86 | function getId() { 87 | return $this->id; 88 | } 89 | 90 | /** 91 | * Column name from JSON type 92 | * 93 | * @param string $typ 94 | * @return string 95 | * @throws Exception if the JSON type isn't supported 96 | */ 97 | public static function getColumn($typ) { 98 | switch ($typ) { 99 | case 'float': 100 | return 'vfloat'; 101 | case 'boolean': 102 | return 'vboolean'; 103 | case 'string': 104 | return 'vstring'; 105 | case 'array': 106 | case 'object': 107 | return 'vjson'; 108 | case 'null': 109 | return 'vnull'; 110 | default: 111 | throw new Exception('Unknown JSON type'); 112 | break; 113 | } 114 | } 115 | 116 | /** 117 | * Save JSON string (create/update) 118 | * 119 | * @param string $doc JSON string 120 | * @return string 121 | */ 122 | private function save($doc) { 123 | // create index 124 | $scr = new Scanner(); 125 | $index_list = $scr->scan($doc); 126 | 127 | $conn = $this->client->connection(); 128 | $cid = $this->id; 129 | 130 | $conn->transaction(function() use ($index_list, $conn, $cid, $doc) { 131 | // add data 132 | 133 | $json = json_encode($doc); 134 | $id = $conn->table(Client::T_JSON)->insertGetId( 135 | ['doc_id' => $doc['_id'], 'cid' => $cid, 'data' => $json] 136 | ); 137 | 138 | // add index 139 | $rows = array(); 140 | foreach ($index_list as $item) { 141 | $row = [ 142 | 'cid' => $cid, 143 | 'oid' => $id, 144 | 'depth' => $item->depth, 145 | 'typ' => $item->typ, 146 | 'path' => $item->path, 147 | 'vfloat' => NULL, 148 | 'vboolean' => NULL, 149 | 'vstring' => NULL, 150 | 'vjson' => NULL, 151 | 'vnull' => NULL 152 | ]; 153 | 154 | // insert value in appropriate column 155 | $c = Collection::getColumn($item->typ); 156 | $row[$c] = $item->value; 157 | $rows[] = $row; 158 | } 159 | $conn->table(Client::T_INDEX)->insert($rows); 160 | }); 161 | 162 | return $doc['_id']; 163 | } 164 | 165 | /** 166 | * Check if document exists 167 | * 168 | * @param string $id document id 169 | * @return boolean 170 | */ 171 | private function exists($id) { 172 | $conn = $this->client->connection(); 173 | $row = $conn->table(Client::T_JSON)->select("doc_id")->where("doc_id", $id)->first(); 174 | if(!$row) { 175 | return false; 176 | } 177 | 178 | return true; 179 | } 180 | 181 | /** 182 | * Database manager 183 | * 184 | * @return Client 185 | */ 186 | function getClient() { 187 | return $this->client; 188 | } 189 | 190 | /** 191 | * Inserts a json document into db. 192 | * 193 | * @param string $json JSON string to insert 194 | * @return string Returns document id 195 | * @throws Exception if the document id isn't valid 196 | */ 197 | function insert($json) { 198 | $doc = json_decode($json, true); 199 | 200 | // check if json has id 201 | if(!array_key_exists('_id', $doc)) { 202 | $doc['_id'] = uniqid('oid'); 203 | } else { 204 | // only accept alpha numeric characters 205 | $p = '/^[a-z0-9]+$/'; 206 | $m = preg_match($p, $doc['_id']); 207 | if(!$m) { 208 | throw new Exception('Invalid document id.'); 209 | } 210 | } 211 | 212 | return $this->save($doc); 213 | } 214 | 215 | /** 216 | * Retrieve JSON document 217 | * 218 | * @param string $id document id 219 | * @return mixed 220 | */ 221 | function get($id) { 222 | $conn = $this->client->connection(); 223 | $doc = $conn->table(Client::T_JSON) 224 | ->where('doc_id', $id) 225 | ->where('cid', $this->id) 226 | ->first(); 227 | 228 | if(is_null($doc)) { 229 | return NULL; 230 | } 231 | 232 | return json_decode($doc->data); 233 | } 234 | 235 | /** 236 | * Delete JSON document 237 | * 238 | * @param string $oid document id 239 | * @return boolean 240 | */ 241 | function delete($oid) { 242 | $conn = $this->client->connection(); 243 | return $conn->table(Client::T_JSON)->where('doc_id', $oid)->delete() > 0; 244 | } 245 | 246 | /** 247 | * Updates a JSON document in db. 248 | * 249 | * @param string $json JSON string to insert 250 | * @return string Returns document id 251 | * @throws Exception if the original document id isn't present 252 | */ 253 | function update($json) { 254 | $doc = json_decode($json, true); 255 | 256 | // check if json has id 257 | if(!array_key_exists('_id', $doc)) { 258 | throw new Exception('Missing \'_id\' in JSON document'); 259 | } 260 | 261 | $oid = $doc['_id']; 262 | 263 | if($this->exists($oid)) { 264 | $this->delete($oid); 265 | $this->save($doc); 266 | return true; 267 | } 268 | 269 | return false; 270 | } 271 | 272 | /** 273 | * Total number of JSON documents 274 | * 275 | * @return int 276 | */ 277 | function count() { 278 | $conn = $this->client->connection(); 279 | $tbl = $conn->table(Client::T_JSON); 280 | return $tbl->select($conn->raw('distinct(doc_id)'))->count(); 281 | } 282 | 283 | /** 284 | * New Filter to query JSON document content 285 | * 286 | * @return \IBT\JsonDB\Filter 287 | */ 288 | function newFilter() { 289 | return new Filter($this); 290 | } 291 | } -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | setup(); 23 | * $col = $c->newCollection("users"); 24 | * $col->insert('{"name":"jason bourne", "category":"agent"}'); 25 | * $f = $col->newFilter(); 26 | * $f->where('category', 'in', ['agent'])->run(); 27 | * ``` 28 | */ 29 | class Filter { 30 | 31 | /** 32 | * @var Collection Collection the filter is based on 33 | */ 34 | private $col; 35 | 36 | /** 37 | * @var \Illuminate\Database\Query\Builder Query builder instance 38 | */ 39 | private $query; 40 | 41 | /** 42 | * @var string Name of the database View 43 | */ 44 | private $view; 45 | 46 | /** 47 | * @var array Fields(or JSON paths) that will be used in the query 48 | */ 49 | private $fields; 50 | 51 | /** 52 | * Constructor, creates a new Filter 53 | * 54 | * @param Collection $col Collection the filter is based on 55 | */ 56 | public function __construct(Collection $col) { 57 | $this->fields = array(); 58 | $this->col = $col; 59 | 60 | $this->view = uniqid('view_'); 61 | 62 | $conn = $this->col->getClient()->connection(); 63 | $this->query = $conn->table($this->view) 64 | ->join(Client::T_JSON, 'oid', '=', 'id') 65 | ->select('doc_id'); 66 | 67 | } 68 | 69 | /** 70 | * Converts JSON value path into valid column name 71 | * 72 | * @param string $path JSON path 73 | * @return string 74 | */ 75 | private function sanitizeColName($path) { 76 | return str_replace('.', '_', $path); 77 | } 78 | 79 | /** 80 | * SQL Where statement. 81 | * 82 | * @param string $path JSON path 83 | * @param string $op Operand such as =, <=, > 84 | * @param mixed $value Value 85 | * @return $this 86 | */ 87 | public function where($path, $op, $value) { 88 | $this->fields[$path] = 1; 89 | $this->query->where($this->sanitizeColName($path), $op, $value); 90 | return $this; 91 | } 92 | 93 | /** 94 | * SQL Or Where statement. 95 | * 96 | * @param string $path JSON path 97 | * @param string $op Operand such as =, <=, > 98 | * @param mixed $value Value 99 | * @return $this 100 | */ 101 | public function orWhere($path, $op, $value) { 102 | $this->fields[$path] = 1; 103 | $this->query->orWhere($this->sanitizeColName($path), $op, $value); 104 | return $this; 105 | } 106 | 107 | /** 108 | * SQL Where Between statement. 109 | * 110 | * @param string $path JSON path 111 | * @param array $value Values 112 | * @return $this 113 | */ 114 | public function whereBetween($path, $value) { 115 | $this->fields[$path] = 1; 116 | $this->query->whereBetween($this->sanitizeColName($path), $value); 117 | return $this; 118 | } 119 | 120 | /** 121 | * SQL Where Not Between statement. 122 | * 123 | * @param string $path JSON path 124 | * @param array $value Values 125 | * @return $this 126 | */ 127 | public function whereNotBetween($path, $value) { 128 | $this->fields[$path] = 1; 129 | $this->query->whereNotBetween($this->sanitizeColName($path), $value); 130 | return $this; 131 | } 132 | 133 | /** 134 | * SQL Where In statement. 135 | * 136 | * @param string $path JSON path 137 | * @param mixed $value Value(s) 138 | * @return $this 139 | */ 140 | public function whereIn($path, $value) { 141 | $this->fields[$path] = 1; 142 | $this->query->whereIn($this->sanitizeColName($path), $value); 143 | return $this; 144 | } 145 | 146 | /** 147 | * SQL Where Not In statement. 148 | * 149 | * @param string $path JSON path 150 | * @param mixed $value Value(s) 151 | * @return $this 152 | */ 153 | public function whereNotIn($path, $value) { 154 | $this->fields[$path] = 1; 155 | $this->query->whereNotIn($this->sanitizeColName($path), $value); 156 | return $this; 157 | } 158 | 159 | /** 160 | * SQL Order By statement. 161 | * 162 | * @param string $path JSON path 163 | * @param string $direction Direction i.e 'asc' or 'desc' 164 | * @return $this 165 | */ 166 | public function orderBy($path, $direction) { 167 | $this->fields[$path] = 1; 168 | $this->query->orderBy($this->sanitizeColName($path), $direction); 169 | return $this; 170 | } 171 | 172 | /** 173 | * Return View name. 174 | * 175 | * @return string 176 | */ 177 | public function viewName() { 178 | return $this->view; 179 | } 180 | 181 | /** 182 | * Make sure JSON paths specified in filter statements exist. 183 | * Prevent SQL injection. 184 | * 185 | * @return array 186 | * @throws Exception if the requested JSON path is invalid 187 | */ 188 | private function validateFields() { 189 | // get paths and their data types 190 | $conn = $this->col->getClient()->connection(); 191 | $rows = $conn->table(Client::T_INDEX) 192 | ->select($conn->raw('distinct(path)'), 'typ') 193 | ->whereIn('path', array_keys($this->fields)) 194 | ->get(); 195 | 196 | // quick comparison. can be changed for a more descriptive 197 | // error by comparing values in both lists 198 | if(sizeof($rows) != sizeof($this->fields)) { 199 | throw new Exception("Error: One or more JSON paths don't exist"); 200 | } 201 | 202 | return $rows; 203 | } 204 | 205 | /** 206 | * View query string. This can be used for debugging or caching 207 | * 208 | * @return string 209 | */ 210 | public function viewSQL() { 211 | $idx_table = Client::T_INDEX; 212 | 213 | // validate fields 214 | $fields = $this->validateFields(); 215 | 216 | // oid table 217 | $main = "(select distinct(oid) from ${idx_table}) as a"; 218 | 219 | $select = ['a.oid']; 220 | 221 | $join = [$main]; 222 | 223 | $tc = 0; // table counter 224 | 225 | // add fields 226 | foreach ($fields as $item) { 227 | // get column name 228 | $col = Collection::getColumn($item->typ); 229 | $field = $item->path; 230 | 231 | $cn = $this->sanitizeColName($field); // column name 232 | 233 | $t = "select oid, case when path='${field}' then ${col} end as ${cn} "; 234 | $t .= "from ${idx_table} having ${cn} is not null"; 235 | 236 | $tc++; 237 | $tn = "jn_tbl_" . $tc; // join table alias 238 | 239 | $select[] = "${tn}.${cn}"; 240 | $join[] = '(' . $t . ") as $tn on a.oid = $tn.oid"; 241 | } 242 | 243 | $sb = 'create view ' . $this->view .' as '; 244 | $sb .= 'select ' . join(",", $select) . ' from '; 245 | $sb .= join(" left join ", $join); 246 | 247 | return $sb; 248 | } 249 | 250 | /** 251 | * View select query string. complements the view function 252 | * 253 | * @return string 254 | */ 255 | public function selectSQL() { 256 | return $this->query->toSql(); 257 | } 258 | 259 | /** 260 | * Execute the filter 261 | * 262 | * @return array 263 | */ 264 | public function run() { 265 | $res = []; 266 | $conn = $this->col->getClient()->connection(); 267 | $view = $this->view; 268 | $viewSQL = $this->viewSQL(); 269 | $query = $this->query; 270 | 271 | $conn->transaction(function() use (&$res, $conn, $view, $viewSQL, $query) { 272 | // create view 273 | $conn->statement($viewSQL); 274 | 275 | // select data 276 | $res = $query->pluck('doc_id'); 277 | 278 | // drop view 279 | $conn->statement('drop view if exists ' . $view); 280 | }); 281 | 282 | return $res; 283 | } 284 | } -------------------------------------------------------------------------------- /src/Indexer/Exception.php: -------------------------------------------------------------------------------- 1 | typ = $typ; 48 | $this->value = $value; 49 | $this->path = $path; 50 | $this->depth = $depth; 51 | } 52 | 53 | /** 54 | * Retrieves a property if it exists. 55 | * 56 | * @see http://php.net/manual/en/language.oop5.overloading.php#object.get 57 | * @param string $property Name of property to retrieve 58 | * @return mixed 59 | * @throws Exception if the property isn't found 60 | */ 61 | public function __get($property) { 62 | if(property_exists($this, $property)) { 63 | return $this->$property; 64 | } 65 | throw new Exception('Property not found'); 66 | } 67 | 68 | /** 69 | * Return internal properties for debugging purposes. 70 | * 71 | * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo 72 | * @return mixed 73 | */ 74 | public function __debugInfo() 75 | { 76 | return array( 77 | "depth" => $this->depth, 78 | "path" => $this->path, 79 | "value" => $this->value, 80 | "type" => $this->typ 81 | ); 82 | } 83 | } -------------------------------------------------------------------------------- /src/Indexer/Scanner.php: -------------------------------------------------------------------------------- 1 | scan(json_decode($j)); 19 | * ``` 20 | */ 21 | class Scanner { 22 | 23 | /** 24 | * Indexes a JSON string. 25 | * 26 | * @param array $json JSON value 27 | * @return array Path 28 | */ 29 | public function scan($json) { 30 | $it = new \RecursiveArrayIterator($json); // call global namespace class 31 | $path = ""; // set object root path 32 | $depth = 0; // set path depth 33 | $result = array(); // array for indexes 34 | $fn = array($this, "walk"); // iterator callback function 35 | 36 | iterator_apply($it, $fn, array($it, $path, $depth, &$result)); 37 | 38 | return $result; 39 | } 40 | 41 | /** 42 | * Indexes a JSON string. 43 | * 44 | * @param \RecursiveArrayIterator $it Iterator 45 | * @param string $path current JSON path 46 | * @param int $depth current JSON depth 47 | * @param array $result Reference to array containing scanned indexes 48 | */ 49 | private function walk($it, $path, $depth, &$result) { 50 | while ($it->valid()) { 51 | 52 | $key = $path . $it->key(); 53 | $value = $it->current(); 54 | $typ = Scanner::jsonType(gettype($value)); 55 | if($typ == "array" || $typ == "object") { 56 | $value = json_encode($value); 57 | } 58 | 59 | $result[] = new Index($typ, $value, $key, $depth); 60 | 61 | if($it->hasChildren()) { 62 | $c = $it->getChildren(); 63 | $sub_path = $key . "."; 64 | $depth++; 65 | $this->walk($c, $sub_path, $depth, $result); 66 | } 67 | $it->next(); 68 | } 69 | } 70 | 71 | /** 72 | * Returns the equivalent JSON type. 73 | * 74 | * @param string $typ 75 | * @return string 76 | * @throws Exception if the JSON type isn't supported 77 | */ 78 | public static function jsonType($typ) { 79 | switch ($typ) { 80 | case 'integer': 81 | case 'double': 82 | return 'float'; 83 | case 'NULL': 84 | return 'null'; 85 | case 'boolean': 86 | case 'string': 87 | case 'array': 88 | case 'object': 89 | return $typ; 90 | default: 91 | throw new Exception("Unknown JSON type ${typ}"); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | 1 && Str::startsWith($value, '"') && Str::endsWith($value, '"')) { 41 | return substr($value, 1, -1); 42 | } 43 | return $value; 44 | } 45 | } -------------------------------------------------------------------------------- /tests/ClientTest.php: -------------------------------------------------------------------------------- 1 | setup(); 17 | 18 | // make sure collection list is empty 19 | $this->assertEmpty($c->listCollection()); 20 | 21 | //create new collection 22 | $c->newCollection("users"); 23 | $l = $c->listCollection(); 24 | $this->assertContains("users", $l); 25 | $this->assertTrue(count($l) == 1); 26 | $col = $c->getCollection("users"); 27 | $this->assertTrue($col->getName() == "users"); 28 | } 29 | } -------------------------------------------------------------------------------- /tests/CollectionTest.php: -------------------------------------------------------------------------------- 1 | setup(); 18 | 19 | $col = $c->newCollection("users"); 20 | 21 | // insert json 22 | $j = '{"name":"jason bourne", "category":"agent"}'; 23 | $oid = $col->insert($j); 24 | 25 | // retrieve and check 26 | $obj = $col->get($oid); 27 | $this->assertEquals($obj->name, "jason bourne"); 28 | $this->assertEquals($obj->category, "agent"); 29 | 30 | // update 31 | $obj->category = "ghost"; 32 | $ok = $col->update(json_encode($obj)); 33 | $this->assertTrue($ok); 34 | 35 | // retrieve and check 36 | $obj = $col->get($oid); 37 | $this->assertEquals($obj->name, "jason bourne"); 38 | $this->assertEquals($obj->category, "ghost"); 39 | 40 | // delete 41 | $ok = $col->delete($oid); 42 | $this->assertTrue($ok); 43 | $obj = $col->get($oid); 44 | $this->assertNull($obj); 45 | } 46 | 47 | public function testCollectionFind() { 48 | // load test data 49 | $str = file_get_contents(__DIR__ . "/test.json"); 50 | $j = json_decode($str, true); 51 | 52 | $c = new Client(); 53 | $c->setup(); 54 | 55 | $c->newCollection("dummy"); 56 | $col = $c->newCollection("users"); 57 | 58 | // insert json 59 | for ($i=0; $i < count($j); $i++) { 60 | $tmp = json_encode($j[$i]); 61 | $col->insert($tmp); 62 | } 63 | 64 | // count 65 | $this->assertEquals($col->count(), 11); 66 | 67 | // implement filters 68 | $f = $col->newFilter(); 69 | $f->whereIn('gender', ['female']); 70 | $res = $f->run(); 71 | $this->assertEquals(count($res), 3); 72 | 73 | $f = $col->newFilter(); 74 | $f->where('gender', '=', 'male'); 75 | $res = $f->run(); 76 | $this->assertEquals(count($res), 8); 77 | 78 | $f = $col->newFilter(); 79 | $f->where('name', '=', 'Lowery Sheppard'); 80 | $res = $f->run(); 81 | $this->assertEquals(count($res), 1); 82 | $this->assertEquals($res[0], '575e76e84a60553b98f42544'); 83 | 84 | $f = $col->newFilter(); 85 | $f->where('gender', '=', 'female')->where('age', '>', 30); 86 | $res = $f->run(); 87 | $this->assertEquals(count($res), 2); 88 | } 89 | } -------------------------------------------------------------------------------- /tests/IndexTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(1, $idx->depth); 19 | $this->assertEquals("user.greeting", $idx->path); 20 | $this->assertEquals("hello", $idx->value); 21 | $this->assertEquals("string", $idx->typ); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/ScannerTest.php: -------------------------------------------------------------------------------- 1 | scan(json_decode($j)); 32 | 33 | $kv = array(); 34 | foreach ($r as $index) { 35 | $kv[$index->path] = $index; 36 | } 37 | 38 | $this->assertEquals($kv["fighter"]->value, "iron monkey"); 39 | $this->assertEquals($kv["points"]->value, 96); 40 | $this->assertEquals($kv["available"]->value, false); 41 | $this->assertEquals($kv["dob"]->value, null); 42 | $this->assertEquals($kv["styles.0.name"]->value, "功夫"); 43 | $this->assertEquals($kv["styles.0.name_eng"]->value, "kung-fu"); 44 | } 45 | } -------------------------------------------------------------------------------- /tests/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "575e76e88bd9a7df73545929", 4 | "active": true, 5 | "balance": 2481.47, 6 | "age": 33, 7 | "name": "Poole Curtis", 8 | "gender": "male", 9 | "tags": [ 10 | "laborum", 11 | "est", 12 | "ullamco", 13 | "dolore", 14 | "nostrud", 15 | "sint", 16 | "magna" 17 | ], 18 | "friends": [ 19 | { 20 | "id": 0, 21 | "name": "Miller Ballard" 22 | }, 23 | { 24 | "id": 1, 25 | "name": "Ashley Gross" 26 | }, 27 | { 28 | "id": 2, 29 | "name": "Savannah Mueller" 30 | } 31 | ] 32 | }, 33 | { 34 | "_id": "575e76e8cf735ff40b0b87cf", 35 | "active": true, 36 | "balance": 3649.24, 37 | "age": 40, 38 | "name": "Hoover Hudson", 39 | "gender": "male", 40 | "tags": [ 41 | "enim", 42 | "id", 43 | "magna", 44 | "aliquip", 45 | "Lorem", 46 | "et", 47 | "mollit" 48 | ], 49 | "friends": [ 50 | { 51 | "id": 0, 52 | "name": "Steele Hogan" 53 | }, 54 | { 55 | "id": 1, 56 | "name": "Ray Moody" 57 | }, 58 | { 59 | "id": 2, 60 | "name": "Ochoa Haynes" 61 | } 62 | ] 63 | }, 64 | { 65 | "_id": "575e76e81911bebed4002c82", 66 | "active": true, 67 | "balance": 1289.98, 68 | "age": 40, 69 | "name": "Jannie Coleman", 70 | "gender": "female", 71 | "tags": [ 72 | "tempor", 73 | "et", 74 | "duis", 75 | "ut", 76 | "pariatur", 77 | "consequat", 78 | "eu" 79 | ], 80 | "friends": [ 81 | { 82 | "id": 0, 83 | "name": "Wilkinson Rodriguez" 84 | }, 85 | { 86 | "id": 1, 87 | "name": "Delacruz Bonner" 88 | }, 89 | { 90 | "id": 2, 91 | "name": "Lang Dean" 92 | } 93 | ] 94 | }, 95 | { 96 | "_id": "575e76e865a5210bcfa8c0a7", 97 | "active": false, 98 | "balance": 2083.73, 99 | "age": 27, 100 | "name": "Walters Booker", 101 | "gender": "male", 102 | "tags": [ 103 | "dolor", 104 | "reprehenderit", 105 | "reprehenderit", 106 | "eiusmod", 107 | "ipsum", 108 | "ullamco", 109 | "quis" 110 | ], 111 | "friends": [ 112 | { 113 | "id": 0, 114 | "name": "Dorothy Simpson" 115 | }, 116 | { 117 | "id": 1, 118 | "name": "Krystal Mccray" 119 | }, 120 | { 121 | "id": 2, 122 | "name": "Sheila Beach" 123 | } 124 | ] 125 | }, 126 | { 127 | "_id": "575e76e8395e74230d46b354", 128 | "active": false, 129 | "balance": 2863.92, 130 | "age": 32, 131 | "name": "Dorthy Cohen", 132 | "gender": "female", 133 | "tags": [ 134 | "eu", 135 | "ipsum", 136 | "fugiat", 137 | "reprehenderit", 138 | "occaecat", 139 | "velit", 140 | "nostrud" 141 | ], 142 | "friends": [ 143 | { 144 | "id": 0, 145 | "name": "Valarie Burt" 146 | }, 147 | { 148 | "id": 1, 149 | "name": "Dana Shepherd" 150 | }, 151 | { 152 | "id": 2, 153 | "name": "Shepherd Mcneil" 154 | } 155 | ] 156 | }, 157 | { 158 | "_id": "575e76e84a60553b98f42544", 159 | "active": true, 160 | "balance": 1250.94, 161 | "age": 30, 162 | "name": "Lowery Sheppard", 163 | "gender": "male", 164 | "tags": [ 165 | "ea", 166 | "occaecat", 167 | "eiusmod", 168 | "elit", 169 | "minim", 170 | "non", 171 | "proident" 172 | ], 173 | "friends": [ 174 | { 175 | "id": 0, 176 | "name": "Holcomb Mcfarland" 177 | }, 178 | { 179 | "id": 1, 180 | "name": "Cora Marshall" 181 | }, 182 | { 183 | "id": 2, 184 | "name": "Stuart Powell" 185 | } 186 | ] 187 | }, 188 | { 189 | "_id": "575e76e8b91bdf438beaec9d", 190 | "active": false, 191 | "balance": 2981.59, 192 | "age": 24, 193 | "name": "Cortez Martinez", 194 | "gender": "male", 195 | "tags": [ 196 | "veniam", 197 | "qui", 198 | "pariatur", 199 | "sint", 200 | "pariatur", 201 | "ex", 202 | "et" 203 | ], 204 | "friends": [ 205 | { 206 | "id": 0, 207 | "name": "Orr Doyle" 208 | }, 209 | { 210 | "id": 1, 211 | "name": "Kristine York" 212 | }, 213 | { 214 | "id": 2, 215 | "name": "Cotton Perez" 216 | } 217 | ] 218 | }, 219 | { 220 | "_id": "575e76e8a70ce7bc09c4031a", 221 | "active": true, 222 | "balance": 3998.48, 223 | "age": 23, 224 | "name": "Yang Mcdaniel", 225 | "gender": "male", 226 | "tags": [ 227 | "exercitation", 228 | "adipisicing", 229 | "id", 230 | "cupidatat", 231 | "cillum", 232 | "quis", 233 | "sunt" 234 | ], 235 | "friends": [ 236 | { 237 | "id": 0, 238 | "name": "Suzanne Lindsay" 239 | }, 240 | { 241 | "id": 1, 242 | "name": "Lorraine Greene" 243 | }, 244 | { 245 | "id": 2, 246 | "name": "Marci Mayer" 247 | } 248 | ] 249 | }, 250 | { 251 | "_id": "575e76e8634ee721c2f7eb6b", 252 | "active": false, 253 | "balance": 2192.61, 254 | "age": 21, 255 | "name": "Gina Barton", 256 | "gender": "female", 257 | "tags": [ 258 | "minim", 259 | "non", 260 | "ipsum", 261 | "laboris", 262 | "commodo", 263 | "nostrud", 264 | "ad" 265 | ], 266 | "friends": [ 267 | { 268 | "id": 0, 269 | "name": "Padilla Craig" 270 | }, 271 | { 272 | "id": 1, 273 | "name": "Hamilton Hernandez" 274 | }, 275 | { 276 | "id": 2, 277 | "name": "Stewart Spears" 278 | } 279 | ] 280 | }, 281 | { 282 | "_id": "575e76e8d06206ce3a0c2a9c", 283 | "active": false, 284 | "balance": 1754.14, 285 | "age": 39, 286 | "name": "Rivas Marsh", 287 | "gender": "male", 288 | "tags": [ 289 | "commodo", 290 | "eiusmod", 291 | "reprehenderit", 292 | "ad", 293 | "excepteur", 294 | "esse", 295 | "sint" 296 | ], 297 | "friends": [ 298 | { 299 | "id": 0, 300 | "name": "Barron Palmer" 301 | }, 302 | { 303 | "id": 1, 304 | "name": "Lisa Shepard" 305 | }, 306 | { 307 | "id": 2, 308 | "name": "Guthrie Horne" 309 | } 310 | ] 311 | }, 312 | { 313 | "_id": "575e76e8f8df79a2cb49ff49", 314 | "active": false, 315 | "balance": 2926.84, 316 | "age": 32, 317 | "name": "Flynn Gonzalez", 318 | "gender": "male", 319 | "tags": [ 320 | "labore", 321 | "excepteur", 322 | "sunt", 323 | "sit", 324 | "esse", 325 | "irure", 326 | "ad" 327 | ], 328 | "friends": [ 329 | { 330 | "id": 0, 331 | "name": "Shelby Mendoza" 332 | }, 333 | { 334 | "id": 1, 335 | "name": "Horton Dawson" 336 | }, 337 | { 338 | "id": 2, 339 | "name": "Casey Head" 340 | } 341 | ] 342 | } 343 | ] --------------------------------------------------------------------------------