├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── bower.json ├── circle.yml ├── package.json ├── src ├── errors.js ├── mappedErrors.js └── slicer.js └── tests_and_examples ├── README.md ├── examples ├── aggregation.json ├── count_entity.json ├── count_event.json ├── delete.json ├── result.json ├── score.json ├── sql.json ├── sql_insert.json ├── top_values.json └── update.json └── runQueryTests.js /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | examples/* 3 | node_modules/* -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [2.1.0] 4 | ### Added 5 | - UPDATE support 6 | - DELETE support 7 | 8 | ## [2.0.2] 9 | ### Updated 10 | - Change SQL endpoint from `/query/sql` to `sql` 11 | - Improve client tests 12 | 13 | ## [2.0.1] 14 | ### Updated 15 | - Correct data extraction validator to accept columns: all 16 | - Add support for SQL queries on client 17 | - Adapt test queries to the changes on SlicingDice API 18 | 19 | ## [2.0.0] 20 | ### Updated 21 | - Update `existsEntity()` method to receive `table` as parameter 22 | - Update Api Errors code 23 | - Rename SlicingDice API endpoints 24 | 25 | ## [1.0.0] 26 | ### Added 27 | - Thin layers around SlicingDice API endpoints 28 | - Automatic regression test script runQueryTests.js with its JSON data 29 | - Test for data extraction 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SlicingDice LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlicingDice Official JavaScript Client (v2.1.0) 2 | 3 | Official JavaScript client for [SlicingDice - Data Warehouse and Analytics Database as a Service](https://www.slicingdice.com/). 4 | 5 | [SlicingDice](http://www.slicingdice.com/) is a serverless, SQL & API-based, easy-to-use and really cost-effective alternative to Amazon Redshift and Google BigQuery. 6 | 7 | ### Build Status: [![CircleCI](https://circleci.com/gh/SlicingDice/slicingdice-javascript/tree/master.svg?style=svg)](https://circleci.com/gh/SlicingDice/slicingdice-javascript/tree/master) 8 | 9 | ### Code Quality: [![Codacy Badge](https://api.codacy.com/project/badge/Grade/67f731bdb3cb4eb08624240d17280a26)](https://www.codacy.com/app/SimbioseVentures/slicingdice-javascript?utm_source=github.com&utm_medium=referral&utm_content=SlicingDice/slicingdice-javascript&utm_campaign=Badge_Grade) 10 | 11 | ## Documentation 12 | 13 | If you are new to SlicingDice, check our [quickstart guide](https://docs.slicingdice.com/docs/quickstart-guide) and learn to use it in 15 minutes. 14 | 15 | Please refer to the [SlicingDice official documentation](https://docs.slicingdice.com/) for more information on [how to create a database](https://docs.slicingdice.com/docs/how-to-create-a-database), [how to insert data](https://docs.slicingdice.com/docs/how-to-insert-data), [how to make queries](https://docs.slicingdice.com/docs/how-to-make-queries), [how to create columns](https://docs.slicingdice.com/docs/how-to-create-columns), [SlicingDice restrictions](https://docs.slicingdice.com/docs/current-restrictions) and [API details](https://docs.slicingdice.com/docs/api-details). 16 | 17 | ## Tests and Examples 18 | 19 | Whether you want to test the client installation or simply check more examples on how the client works, take a look at [tests and examples directory](tests_and_examples/). 20 | 21 | ## Installing 22 | 23 | In order to install the JavaScript client, you only need to use [`npm`](https://www.npmjs.com/). 24 | 25 | ```bash 26 | npm install slicerjs 27 | ``` 28 | 29 | ## Usage 30 | 31 | The following code snippet is an example of how to add and query data 32 | using the SlicingDice javascript client. We entry data informing 33 | `user1@slicingdice.com` has age 22 and then query the database for 34 | the number of users with age between 20 and 40 years old. 35 | If this is the first register ever entered into the system, 36 | the answer should be 1. 37 | 38 | ```javascript 39 | var SlicingDice = require('slicerjs'); // only required for Node.js 40 | 41 | // Configure the client 42 | const client = new SlicingDice({ 43 | masterKey: 'MASTER_API_KEY', 44 | writeKey: 'WRITE_API_KEY', 45 | readKey: 'READ_API_KEY' 46 | }); 47 | 48 | // Inserting data 49 | const insertData = { 50 | "user1@slicingdice.com": { 51 | "age": 22 52 | }, 53 | "auto-create": ["dimension", "column"] 54 | }; 55 | client.insert(insertData); 56 | 57 | // Querying data 58 | const queryData = { 59 | "query-name": "users-between-20-and-40", 60 | "query": [ 61 | { 62 | "age": { 63 | "range": [ 64 | 20, 65 | 40 66 | ] 67 | } 68 | } 69 | ] 70 | }; 71 | client.countEntity(queryData).then((resp) => { 72 | console.log(resp); 73 | }, (err) => { 74 | console.err(err); 75 | }); 76 | ``` 77 | 78 | ## Reference 79 | 80 | `SlicingDice` encapsulates logic for sending requests to the API. Its methods are thin layers around the [API endpoints](https://docs.slicingdice.com/docs/api-details), so their parameters and return values are JSON-like `Object` objects with the same syntax as the [API endpoints](https://docs.slicingdice.com/docs/api-details) 81 | 82 | ### Constructor 83 | 84 | `SlicingDice(apiKeys)` 85 | * `apiKeys (Object)` - [API key](https://docs.slicingdice.com/docs/api-keys) to authenticate requests with the SlicingDice API. 86 | 87 | ### `getDatabase()` 88 | Get information about current database. This method corresponds to a `GET` request at `/database`. 89 | 90 | #### Request example 91 | 92 | ```javascript 93 | let SlicingDice = require('slicerjs'); 94 | 95 | const client = new SlicingDice({ 96 | masterKey: 'MASTER_API_KEY' 97 | }); 98 | 99 | client.getDatabase().then((resp) => { 100 | console.log(resp); 101 | }, (err) => { 102 | console.error(err); 103 | }); 104 | ``` 105 | 106 | #### Output example 107 | 108 | ```json 109 | { 110 | "name": "Database 1", 111 | "description": "My first database", 112 | "dimensions": [ 113 | "default", 114 | "users" 115 | ], 116 | "updated-at": "2017-05-19T14:27:47.417415", 117 | "created-at": "2017-05-12T02:23:34.231418" 118 | } 119 | ``` 120 | 121 | ### `getColumns()` 122 | Get all created columns, both active and inactive ones. This method corresponds to a [GET request at /column](https://docs.slicingdice.com/docs/how-to-list-edit-or-delete-columns). 123 | 124 | #### Request example 125 | 126 | ```javascript 127 | let SlicingDice = require('slicerjs'); 128 | 129 | const client = new SlicingDice({ 130 | masterKey: 'MASTER_API_KEY' 131 | }); 132 | 133 | client.getColumns().then((resp) => { 134 | console.log(resp); 135 | }, (err) => { 136 | console.error(err); 137 | }); 138 | ``` 139 | 140 | #### Output example 141 | 142 | ```json 143 | { 144 | "active": [ 145 | { 146 | "name": "Model", 147 | "api-name": "car-model", 148 | "description": "Car models from dealerships", 149 | "type": "string", 150 | "category": "general", 151 | "cardinality": "high", 152 | "storage": "latest-value" 153 | } 154 | ], 155 | "inactive": [ 156 | { 157 | "name": "Year", 158 | "api-name": "car-year", 159 | "description": "Year of manufacture", 160 | "type": "integer", 161 | "category": "general", 162 | "storage": "latest-value" 163 | } 164 | ] 165 | } 166 | ``` 167 | 168 | ### `createColumn(jsonData)` 169 | Create a new column. This method corresponds to a [POST request at /column](https://docs.slicingdice.com/docs/how-to-create-columns#section-creating-columns-using-column-endpoint). 170 | 171 | #### Request example 172 | 173 | ```javascript 174 | let SlicingDice = require('slicerjs'); 175 | 176 | const client = new SlicingDice({ 177 | masterKey: 'MASTER_API_KEY' 178 | }); 179 | 180 | column = { 181 | "name": "Year", 182 | "api-name": "year", 183 | "type": "integer", 184 | "description": "Year of manufacturing", 185 | "storage": "latest-value" 186 | }; 187 | 188 | client.createColumn(column).then((resp) => { 189 | console.log(resp); 190 | }, (err) => { 191 | console.error(err); 192 | }); 193 | ``` 194 | 195 | #### Output example 196 | 197 | ```json 198 | { 199 | "status": "success", 200 | "api-name": "year" 201 | } 202 | ``` 203 | 204 | ### `insert(jsonData)` 205 | Insert data to existing entities or create new entities, if necessary. This method corresponds to a [POST request at /insert](https://docs.slicingdice.com/docs/how-to-insert-data). 206 | 207 | #### Request example 208 | 209 | ```javascript 210 | let SlicingDice = require('slicerjs'); 211 | 212 | const client = new SlicingDice({ 213 | masterKey: 'MASTER_API_KEY', 214 | writeKey: 'WRITE_API_KEY' 215 | }); 216 | 217 | const insertData = { 218 | "user1@slicingdice.com": { 219 | "car-model": "Ford Ka", 220 | "year": 2016 221 | }, 222 | "user2@slicingdice.com": { 223 | "car-model": "Honda Fit", 224 | "year": 2016 225 | }, 226 | "user3@slicingdice.com": { 227 | "car-model": "Toyota Corolla", 228 | "year": 2010, 229 | "test-drives": [ 230 | { 231 | "value": "NY", 232 | "date": "2016-08-17T13:23:47+00:00" 233 | }, { 234 | "value": "NY", 235 | "date": "2016-08-17T13:23:47+00:00" 236 | }, { 237 | "value": "CA", 238 | "date": "2016-04-05T10:20:30Z" 239 | } 240 | ] 241 | }, 242 | "user4@slicingdice.com": { 243 | "car-model": "Ford Ka", 244 | "year": 2005, 245 | "test-drives": { 246 | "value": "NY", 247 | "date": "2016-08-17T13:23:47+00:00" 248 | } 249 | } 250 | }; 251 | 252 | client.insert(insertData).then((resp) => { 253 | console.log(resp); 254 | }, (err) => { 255 | console.error(err); 256 | }); 257 | ``` 258 | 259 | #### Output example 260 | 261 | ```json 262 | { 263 | "status": "success", 264 | "inserted-entities": 4, 265 | "inserted-columns": 10, 266 | "took": 0.023 267 | } 268 | ``` 269 | 270 | ### `existsEntity(ids, dimension = null)` 271 | Verify which entities exist in a tabdimensionle (uses `default` dimension if not provided) given a list of entity IDs. This method corresponds to a [POST request at /query/exists/entity](https://docs.slicingdice.com/docs/exists). 272 | 273 | #### Request example 274 | 275 | ```javascript 276 | let SlicingDice = require('slicerjs'); 277 | 278 | const client = new SlicingDice({ 279 | masterKey: 'MASTER_KEY', 280 | readKey: 'READ_KEY' 281 | }); 282 | 283 | ids = [ 284 | "user1@slicingdice.com", 285 | "user2@slicingdice.com", 286 | "user3@slicingdice.com" 287 | ]; 288 | 289 | client.existsEntity(ids).then((resp) => { 290 | console.log(resp); 291 | }, (err) => { 292 | console.error(err); 293 | }); 294 | ``` 295 | 296 | #### Output example 297 | 298 | ```json 299 | { 300 | "status": "success", 301 | "exists": [ 302 | "user1@slicingdice.com", 303 | "user2@slicingdice.com" 304 | ], 305 | "not-exists": [ 306 | "user3@slicingdice.com" 307 | ], 308 | "took": 0.103 309 | } 310 | ``` 311 | 312 | ### `countEntityTotal()` 313 | Count the number of inserted entities in the whole database. This method corresponds to a [POST request at /query/count/entity/total](https://docs.slicingdice.com/docs/total). 314 | 315 | #### Request example 316 | 317 | ```javascript 318 | let SlicingDice = require('slicerjs'); 319 | 320 | const client = new SlicingDice({ 321 | masterKey: 'MASTER_KEY', 322 | readKey: 'READ_KEY' 323 | }); 324 | 325 | client.countEntityTotal().then((resp) => { 326 | console.log(resp); 327 | }, (err) => { 328 | console.error(err); 329 | }); 330 | ``` 331 | 332 | #### Output example 333 | 334 | ```json 335 | { 336 | "status": "success", 337 | "result": { 338 | "total": 42 339 | }, 340 | "took": 0.103 341 | } 342 | ``` 343 | 344 | ### `countEntityTotal(dimensions)` 345 | Count the total number of inserted entities in the given dimensions. This method corresponds to a [POST request at /query/count/entity/total](https://docs.slicingdice.com/docs/total#section-counting-specific-tables). 346 | 347 | #### Request example 348 | 349 | ```javascript 350 | let SlicingDice = require('slicerjs'); 351 | 352 | const client = new SlicingDice({ 353 | masterKey: 'MASTER_KEY', 354 | readKey: 'READ_KEY' 355 | }); 356 | 357 | const dimensions = ["default"]; 358 | 359 | client.countEntityTotal(dimensions).then((resp) => { 360 | console.log(resp); 361 | }, (err) => { 362 | console.error(err); 363 | }); 364 | ``` 365 | 366 | #### Output example 367 | 368 | ```json 369 | { 370 | "status": "success", 371 | "result": { 372 | "total": 42 373 | }, 374 | "took": 0.103 375 | } 376 | ``` 377 | 378 | ### `countEntity(jsonData)` 379 | Count the number of entities matching the given query. This method corresponds to a [POST request at /query/count/entity](https://docs.slicingdice.com/docs/count-entities). 380 | 381 | #### Request example 382 | 383 | ```javascript 384 | let SlicingDice = require('slicerjs'); 385 | 386 | const client = new SlicingDice({ 387 | masterKey: 'MASTER_KEY', 388 | readKey: 'READ_KEY' 389 | }); 390 | 391 | const query = [ 392 | { 393 | "query-name": "corolla-or-fit", 394 | "query": [ 395 | { 396 | "car-model": { 397 | "equals": "toyota corolla" 398 | } 399 | }, 400 | "or", 401 | { 402 | "car-model": { 403 | "equals": "honda fit" 404 | } 405 | } 406 | ], 407 | "bypass-cache": false 408 | }, 409 | { 410 | "query-name": "ford-ka", 411 | "query": [ 412 | { 413 | "car-model": { 414 | "equals": "ford ka" 415 | } 416 | } 417 | ], 418 | "bypass-cache": false 419 | } 420 | ]; 421 | 422 | client.countEntity(query).then((resp) => { 423 | console.log(resp); 424 | }, (err) => { 425 | console.error(err); 426 | }); 427 | ``` 428 | 429 | #### Output example 430 | 431 | ```json 432 | { 433 | "result":{ 434 | "ford-ka":2, 435 | "corolla-or-fit":2 436 | }, 437 | "took":0.083, 438 | "status":"success" 439 | } 440 | ``` 441 | 442 | ### `countEvent(jsonData)` 443 | Count the number of occurrences for time-series events matching the given query. This method corresponds to a [POST request at /query/count/event](https://docs.slicingdice.com/docs/count-events). 444 | 445 | #### Request example 446 | 447 | ```javascript 448 | let SlicingDice = require('slicerjs'); 449 | 450 | const client = new SlicingDice({ 451 | masterKey: 'MASTER_KEY', 452 | readKey: 'READ_KEY' 453 | }); 454 | 455 | const query = [ 456 | { 457 | "query-name": "test-drives-in-ny", 458 | "query": [ 459 | { 460 | "test-drives": { 461 | "equals": "NY", 462 | "between": [ 463 | "2016-08-16T00:00:00Z", 464 | "2016-08-18T00:00:00Z" 465 | ] 466 | } 467 | } 468 | ], 469 | "bypass-cache": true 470 | }, 471 | { 472 | "query-name": "test-drives-in-ca", 473 | "query": [ 474 | { 475 | "test-drives": { 476 | "equals": "CA", 477 | "between": [ 478 | "2016-04-04T00:00:00Z", 479 | "2016-04-06T00:00:00Z" 480 | ] 481 | } 482 | } 483 | ], 484 | "bypass-cache": true 485 | } 486 | ]; 487 | 488 | client.countEvent(query).then((resp) => { 489 | console.log(resp); 490 | }, (err) => { 491 | console.error(err); 492 | }); 493 | ``` 494 | 495 | #### Output example 496 | 497 | ```json 498 | { 499 | "result":{ 500 | "test-drives-in-ny":3, 501 | "test-drives-in-ca":0 502 | }, 503 | "took":0.063, 504 | "status":"success" 505 | } 506 | ``` 507 | 508 | ### `topValues(jsonData)` 509 | Return the top values for entities matching the given query. This method corresponds to a [POST request at /query/top_values](https://docs.slicingdice.com/docs/top-values). 510 | 511 | #### Request example 512 | 513 | ```javascript 514 | let SlicingDice = require('slicerjs'); 515 | 516 | const client = new SlicingDice({ 517 | masterKey: 'MASTER_KEY', 518 | readKey: 'READ_KEY' 519 | }); 520 | 521 | query = { 522 | "car-year": { 523 | "year": 2 524 | }, 525 | "car models": { 526 | "car-model": 3 527 | } 528 | } 529 | 530 | client.topValues(query).then((resp) => { 531 | console.log(resp); 532 | }, (err) => { 533 | console.error(err); 534 | }); 535 | ``` 536 | 537 | #### Output example 538 | 539 | ```json 540 | { 541 | "result":{ 542 | "car models":{ 543 | "car-model":[ 544 | { 545 | "quantity":2, 546 | "value":"ford ka" 547 | }, 548 | { 549 | "quantity":1, 550 | "value":"honda fit" 551 | }, 552 | { 553 | "quantity":1, 554 | "value":"toyota corolla" 555 | } 556 | ] 557 | }, 558 | "car-year":{ 559 | "year":[ 560 | { 561 | "quantity":2, 562 | "value":"2016" 563 | }, 564 | { 565 | "quantity":1, 566 | "value":"2010" 567 | } 568 | ] 569 | } 570 | }, 571 | "took":0.034, 572 | "status":"success" 573 | } 574 | ``` 575 | 576 | ### `aggregation(jsonData)` 577 | Return the aggregation of all columns in the given query. This method corresponds to a [POST request at /query/aggregation](https://docs.slicingdice.com/docs/aggregations). 578 | 579 | #### Request example 580 | 581 | ```javascript 582 | let SlicingDice = require('slicerjs'); 583 | 584 | const client = new SlicingDice({ 585 | masterKey: 'MASTER_KEY', 586 | readKey: 'READ_KEY' 587 | }); 588 | 589 | query = { 590 | "query": [ 591 | { 592 | "year": 2 593 | }, 594 | { 595 | "car-model": 2, 596 | "equals": [ 597 | "honda fit", 598 | "toyota corolla" 599 | ] 600 | } 601 | ] 602 | }; 603 | 604 | client.aggregation(query).then((resp) => { 605 | console.log(resp); 606 | }, (err) => { 607 | console.error(err); 608 | }); 609 | ``` 610 | 611 | #### Output example 612 | 613 | ```json 614 | { 615 | "result":{ 616 | "year":[ 617 | { 618 | "quantity":2, 619 | "value":"2016", 620 | "car-model":[ 621 | { 622 | "quantity":1, 623 | "value":"honda fit" 624 | } 625 | ] 626 | }, 627 | { 628 | "quantity":1, 629 | "value":"2005" 630 | } 631 | ] 632 | }, 633 | "took":0.079, 634 | "status":"success" 635 | } 636 | ``` 637 | 638 | ### `getSavedQueries()` 639 | Get all saved queries. This method corresponds to a [GET request at /query/saved](https://docs.slicingdice.com/docs/saved-queries). 640 | 641 | #### Request example 642 | 643 | ```javascript 644 | let SlicingDice = require('slicerjs'); 645 | 646 | const client = new SlicingDice({ 647 | masterKey: 'MASTER_KEY' 648 | }); 649 | 650 | client.getSavedQueries().then((resp) => { 651 | console.log(resp); 652 | }, (err) => { 653 | console.error(err); 654 | }); 655 | ``` 656 | 657 | #### Output example 658 | 659 | ```json 660 | { 661 | "status": "success", 662 | "saved-queries": [ 663 | { 664 | "name": "users-in-ny-or-from-ca", 665 | "type": "count/entity", 666 | "query": [ 667 | { 668 | "state": { 669 | "equals": "NY" 670 | } 671 | }, 672 | "or", 673 | { 674 | "state-origin": { 675 | "equals": "CA" 676 | } 677 | } 678 | ], 679 | "cache-period": 100 680 | }, { 681 | "name": "users-from-ca", 682 | "type": "count/entity", 683 | "query": [ 684 | { 685 | "state": { 686 | "equals": "NY" 687 | } 688 | } 689 | ], 690 | "cache-period": 60 691 | } 692 | ], 693 | "took": 0.103 694 | } 695 | ``` 696 | 697 | ### `createSavedQuery(jsonData)` 698 | Create a saved query at SlicingDice. This method corresponds to a [POST request at /query/saved](https://docs.slicingdice.com/docs/saved-queries). 699 | 700 | #### Request example 701 | 702 | ```javascript 703 | let SlicingDice = require('slicerjs'); 704 | 705 | const client = new SlicingDice({ 706 | masterKey: 'MASTER_KEY' 707 | }); 708 | 709 | query = { 710 | "name": "my-saved-query", 711 | "type": "count/entity", 712 | "query": [ 713 | { 714 | "car-model": { 715 | "equals": "honda fit" 716 | } 717 | }, 718 | "or", 719 | { 720 | "car-model": { 721 | "equals": "toyota corolla" 722 | } 723 | } 724 | ], 725 | "cache-period": 100 726 | } 727 | 728 | client.createSavedQuery(query).then((resp) => { 729 | console.log(resp); 730 | }, (err) => { 731 | console.error(err); 732 | }); 733 | ``` 734 | 735 | #### Output example 736 | 737 | ```json 738 | { 739 | "took":0.053, 740 | "query":[ 741 | { 742 | "car-model":{ 743 | "equals":"honda fit" 744 | } 745 | }, 746 | "or", 747 | { 748 | "car-model":{ 749 | "equals":"toyota corolla" 750 | } 751 | } 752 | ], 753 | "name":"my-saved-query", 754 | "type":"count/entity", 755 | "cache-period":100, 756 | "status":"success" 757 | } 758 | ``` 759 | 760 | ### `updateSavedQuery(queryName, jsonData)` 761 | Update an existing saved query at SlicingDice. This method corresponds to a [PUT request at /query/saved/QUERY_NAME](https://docs.slicingdice.com/docs/saved-queries). 762 | 763 | #### Request example 764 | 765 | ```javascript 766 | let SlicingDice = require('slicerjs'); 767 | 768 | const client = new SlicingDice({ 769 | masterKey: 'MASTER_KEY' 770 | }); 771 | 772 | newQuery = { 773 | "type": "count/entity", 774 | "query": [ 775 | { 776 | "car-model": { 777 | "equals": "ford ka" 778 | } 779 | }, 780 | "or", 781 | { 782 | "car-model": { 783 | "equals": "toyota corolla" 784 | } 785 | } 786 | ], 787 | "cache-period": 100 788 | }; 789 | 790 | client.updateSavedQuery("my-saved-query", newQuery).then((resp) => { 791 | console.log(resp); 792 | }, (err) => { 793 | console.error(err); 794 | }); 795 | ``` 796 | 797 | #### Output example 798 | 799 | ```json 800 | { 801 | "took":0.037, 802 | "query":[ 803 | { 804 | "car-model":{ 805 | "equals":"ford ka" 806 | } 807 | }, 808 | "or", 809 | { 810 | "car-model":{ 811 | "equals":"toyota corolla" 812 | } 813 | } 814 | ], 815 | "type":"count/entity", 816 | "cache-period":100, 817 | "status":"success" 818 | } 819 | ``` 820 | 821 | ### `getSavedQuery(queryName)` 822 | Executed a saved query at SlicingDice. This method corresponds to a [GET request at /query/saved/QUERY_NAME](https://docs.slicingdice.com/docs/saved-queries). 823 | 824 | #### Request example 825 | 826 | ```javascript 827 | let SlicingDice = require('slicerjs'); 828 | 829 | const client = new SlicingDice({ 830 | masterKey: 'MASTER_KEY', 831 | readKey: 'READ_KEY' 832 | }); 833 | 834 | client.getSavedQuery("my-saved-query").then((resp) => { 835 | console.log(resp); 836 | }, (err) => { 837 | console.error(err); 838 | }); 839 | ``` 840 | 841 | #### Output example 842 | 843 | ```json 844 | { 845 | "result":{ 846 | "query":2 847 | }, 848 | "took":0.035, 849 | "query":[ 850 | { 851 | "car-model":{ 852 | "equals":"honda fit" 853 | } 854 | }, 855 | "or", 856 | { 857 | "car-model":{ 858 | "equals":"toyota corolla" 859 | } 860 | } 861 | ], 862 | "type":"count/entity", 863 | "status":"success" 864 | } 865 | ``` 866 | 867 | ### `deleteSavedQuery(queryName)` 868 | Delete a saved query at SlicingDice. This method corresponds to a [DELETE request at /query/saved/QUERY_NAME](https://docs.slicingdice.com/docs/saved-queries). 869 | 870 | #### Request example 871 | 872 | ```javascript 873 | let SlicingDice = require('slicerjs'); 874 | 875 | const client = new SlicingDice({ 876 | masterKey: 'MASTER_KEY' 877 | }); 878 | 879 | client.deleteSavedQuery("my-saved-query").then((resp) => { 880 | console.log(resp); 881 | }, (err) => { 882 | console.error(err); 883 | }); 884 | ``` 885 | 886 | #### Output example 887 | 888 | ```json 889 | { 890 | "took":0.029, 891 | "query":[ 892 | { 893 | "car-model":{ 894 | "equals":"honda fit" 895 | } 896 | }, 897 | "or", 898 | { 899 | "car-model":{ 900 | "equals":"toyota corolla" 901 | } 902 | } 903 | ], 904 | "type":"count/entity", 905 | "cache-period":100, 906 | "status":"success", 907 | "deleted-query":"my-saved-query" 908 | } 909 | ``` 910 | 911 | ### `result(jsonData)` 912 | Retrieve inserted values for entities matching the given query. This method corresponds to a [POST request at /data_extraction/result](https://docs.slicingdice.com/docs/result-extraction). 913 | 914 | #### Request example 915 | 916 | ```javascript 917 | let SlicingDice = require('slicerjs'); 918 | 919 | const client = new SlicingDice({ 920 | masterKey: 'MASTER_KEY', 921 | readKey: 'READ_KEY' 922 | }); 923 | 924 | query = { 925 | "query": [ 926 | { 927 | "car-model": { 928 | "equals": "ford ka" 929 | } 930 | }, 931 | "or", 932 | { 933 | "car-model": { 934 | "equals": "toyota corolla" 935 | } 936 | } 937 | ], 938 | "columns": ["car-model", "year"], 939 | "limit": 2 940 | }; 941 | 942 | client.result(query).then((resp) => { 943 | console.log(resp); 944 | }, (err) => { 945 | console.error(err); 946 | }); 947 | ``` 948 | 949 | #### Output example 950 | 951 | ```json 952 | { 953 | "took":0.113, 954 | "next-page":null, 955 | "data":{ 956 | "customer5@mycustomer.com":{ 957 | "year":"2005", 958 | "car-model":"ford ka" 959 | }, 960 | "user1@slicingdice.com":{ 961 | "year":"2016", 962 | "car-model":"ford ka" 963 | } 964 | }, 965 | "page":1, 966 | "status":"success" 967 | } 968 | ``` 969 | 970 | ### `score(jsonData)` 971 | Retrieve inserted values as well as their relevance for entities matching the given query. This method corresponds to a [POST request at /data_extraction/score](https://docs.slicingdice.com/docs/score-extraction). 972 | 973 | #### Request example 974 | 975 | ```javascript 976 | let SlicingDice = require('slicerjs'); 977 | 978 | const client = new SlicingDice({ 979 | masterKey: 'MASTER_KEY', 980 | readKey: 'READ_KEY' 981 | }); 982 | 983 | query = { 984 | "query": [ 985 | { 986 | "car-model": { 987 | "equals": "ford ka" 988 | } 989 | }, 990 | "or", 991 | { 992 | "car-model": { 993 | "equals": "toyota corolla" 994 | } 995 | } 996 | ], 997 | "columns": ["car-model", "year"], 998 | "limit": 2 999 | }; 1000 | 1001 | client.score(query).then((resp) => { 1002 | console.log(resp); 1003 | }, (err) => { 1004 | console.error(err); 1005 | }); 1006 | ``` 1007 | 1008 | #### Output example 1009 | 1010 | ```json 1011 | { 1012 | "took":0.063, 1013 | "next-page":null, 1014 | "data":{ 1015 | "user3@slicingdice.com":{ 1016 | "score":1, 1017 | "year":"2010", 1018 | "car-model":"toyota corolla" 1019 | }, 1020 | "user2@slicingdice.com":{ 1021 | "score":1, 1022 | "year":"2016", 1023 | "car-model":"honda fit" 1024 | } 1025 | }, 1026 | "page":1, 1027 | "status":"success" 1028 | } 1029 | ``` 1030 | 1031 | ### `sql(query)` 1032 | Retrieve inserted values using a SQL syntax. This method corresponds to a POST request at /query/sql. 1033 | 1034 | #### Query statement 1035 | 1036 | ```javascript 1037 | let SlicingDice = require('slicerjs'); 1038 | 1039 | const client = new SlicingDice({ 1040 | masterKey: 'MASTER_KEY', 1041 | readKey: 'READ_KEY' 1042 | }); 1043 | 1044 | query = "SELECT COUNT(*) FROM default WHERE age BETWEEN 0 AND 49"; 1045 | 1046 | client.sql(query).then((resp) => { 1047 | console.log(resp); 1048 | }, (err) => { 1049 | console.error(err); 1050 | }); 1051 | ``` 1052 | 1053 | #### Insert statement 1054 | ```javascript 1055 | let SlicingDice = require('slicerjs'); 1056 | 1057 | const client = new SlicingDice({ 1058 | masterKey: 'MASTER_KEY', 1059 | readKey: 'READ_KEY' 1060 | }); 1061 | 1062 | query = "INSERT INTO default([entity-id], name, age) VALUES(1, 'john', 10)"; 1063 | 1064 | client.sql(query).then((resp) => { 1065 | console.log(resp); 1066 | }, (err) => { 1067 | console.error(err); 1068 | }); 1069 | ``` 1070 | 1071 | #### Output example 1072 | 1073 | ```json 1074 | { 1075 | "took":0.063, 1076 | "result":[ 1077 | {"COUNT": 3} 1078 | ], 1079 | "count":1, 1080 | "status":"success" 1081 | } 1082 | ``` 1083 | 1084 | ## License 1085 | 1086 | [MIT](https://opensource.org/licenses/MIT) 1087 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slicerjs", 3 | "description": "Official JavaScript client for SlicingDice, Data Warehouse and Analytics Database as a Service.", 4 | "main": "dist/slicer.js", 5 | "authors": [ 6 | "SlicingDice LLC " 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "slicingdice", 11 | "slicing", 12 | "dice", 13 | "data", 14 | "analysis", 15 | "business", 16 | "intelligence" 17 | ], 18 | "homepage": "https://github.com/SlicingDice/slicingdice-javascript", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://blog.scottnonnenberg.com/hands-on-with-circleci-and-node-js/ 2 | 3 | machine: 4 | node: 5 | version: 6.1.0 6 | 7 | dependencies: 8 | pre: 9 | - rm -rf ./node_modules 10 | cache_directories: 11 | - ~/.npm 12 | override: 13 | - nvm install 6 && npm install 14 | 15 | test: 16 | override: 17 | - mv tests_and_examples/examples/ . 18 | - nvm use 6 && node tests_and_examples/runQueryTests.js -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slicerjs", 3 | "version": "2.1.0", 4 | "description": "Official JavaScript client for SlicingDice, Data Warehouse and Analytics Database as a Service.", 5 | "main": "src/slicer.js", 6 | "scripts": { 7 | "test": "node tests_and_examples/runQueryTests.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/SlicingDice/slicingdice-javascript.git" 12 | }, 13 | "keywords": [ 14 | "slicingdice", 15 | "slicing", 16 | "dice", 17 | "data", 18 | "analysis", 19 | "business", 20 | "intelligence" 21 | ], 22 | "author": "SlicingDice LLC ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/SlicingDice/slicingdice-javascript/issues" 26 | }, 27 | "homepage": "https://github.com/SlicingDice/slicingdice-javascript", 28 | "devDependencies": { 29 | "babel-cli": "^6.26.0", 30 | "babel-preset-es2015": "^6.24.0", 31 | "async": "^2.6.0", 32 | "sleep": "^5.1.1" 33 | }, 34 | "dependencies": {} 35 | } 36 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | class ExtendableError extends Error { 2 | constructor(data) { 3 | let message = ''; 4 | if (data.code) { 5 | message += 'code=' + data.code + ' '; 6 | } 7 | if (data.message) { 8 | message += 'message="' + data.message + '" '; 9 | } 10 | if (data.more_info) { 11 | message += 'more_info="' + JSON.stringify(data.more_info) + '" '; 12 | } 13 | 14 | super(message); 15 | 16 | this.code = data.code; 17 | this.more_info = data.more_info; 18 | 19 | if (typeof Error.captureStackTrace === 'function') { 20 | Error.captureStackTrace(this, this.constructor); 21 | } else { 22 | this.stack = (new Error(data.message)).stack; 23 | } 24 | } 25 | } 26 | 27 | class SlicingDiceError extends ExtendableError { 28 | constructor(data) { 29 | super(data); 30 | } 31 | } 32 | 33 | class SlicingDiceClientError extends ExtendableError { 34 | constructor(data) { 35 | super(data); 36 | } 37 | } 38 | 39 | // Specific Errors 40 | 41 | class DemoUnavailableError extends SlicingDiceError { 42 | constructor(data) { 43 | super(data); 44 | } 45 | } 46 | 47 | class RequestRateLimitError extends SlicingDiceError { 48 | constructor(data) { 49 | super(data); 50 | } 51 | } 52 | 53 | class RequestBodySizeExceededError extends SlicingDiceError { 54 | constructor(data) { 55 | super(data); 56 | } 57 | } 58 | 59 | class IndexEntitiesLimitError extends SlicingDiceError { 60 | constructor(data) { 61 | super(data); 62 | } 63 | } 64 | 65 | class IndexColumnsLimitError extends SlicingDiceError { 66 | constructor(data) { 67 | super(data); 68 | } 69 | } 70 | 71 | 72 | 73 | module.exports = { 74 | "SlicingDiceError": SlicingDiceError, 75 | "SlicingDiceClientError": SlicingDiceClientError, 76 | "DemoUnavailableError": DemoUnavailableError, 77 | "RequestRateLimitError": RequestRateLimitError, 78 | "RequestBodySizeExceededError": RequestBodySizeExceededError, 79 | "IndexEntitiesLimitError": IndexEntitiesLimitError, 80 | "IndexColumnsLimitError": IndexColumnsLimitError, 81 | } -------------------------------------------------------------------------------- /src/mappedErrors.js: -------------------------------------------------------------------------------- 1 | var errors = require('./errors'); 2 | 3 | var mappedSDErrors = { 4 | 2: errors['DemoUnavailableError'], 5 | 1502: errors['RequestRateLimitError'], 6 | 1507: errors['RequestBodySizeExceededError'], 7 | 2012: errors['IndexEntitiesLimitError'], 8 | 2013: errors['IndexColumnsLimitError'], 9 | } 10 | 11 | module.exports = mappedSDErrors; 12 | -------------------------------------------------------------------------------- /src/slicer.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict' 3 | // add http module to make requests 4 | let https = require('https'); 5 | const url = require('url'); 6 | // add api mapped errors to client 7 | let mappedErrors = require('./mappedErrors'); 8 | // add all errors 9 | let errors = require('./errors'); 10 | 11 | // RequesterBrowser make http requests from the browser 12 | class RequesterBrowser { 13 | run(token, url, reqType, data = null, sql) { 14 | let content_type; 15 | 16 | if (sql) { 17 | content_type = 'application/sql'; 18 | } else { 19 | content_type = 'application/json'; 20 | } 21 | 22 | return new Promise(function(resolve, reject) { 23 | let req = new XMLHttpRequest(); 24 | req.open(reqType, url, true); 25 | req.setRequestHeader("Authorization", token); 26 | req.setRequestHeader('Content-Type', content_type); 27 | req.setRequestHeader('Accept', "application/json"); 28 | req.onload = function() { 29 | if (req.status == 200) { 30 | // Resolve the promise with the response text 31 | resolve(req.response); 32 | } 33 | else { 34 | reject(Error(req.statusText)); 35 | } 36 | }; 37 | 38 | // Handle network errors 39 | req.onerror = function() { 40 | reject(Error("Network Error")); 41 | }; 42 | req.send(sql ? data : JSON.stringify(data)); 43 | }); 44 | } 45 | } 46 | 47 | // RequesterNode make http requests from the node.js (with we're not running in a web-browser) 48 | class RequesterNode { 49 | run(token, urlReq, reqType, data = null, sql) { 50 | 51 | return new Promise((resolve, reject) => { 52 | let port; 53 | let jsonToSend; 54 | let urlData = url.parse(urlReq); 55 | if (urlData.port === null){ 56 | port = 443; 57 | } else{ 58 | port = urlData.port; 59 | } 60 | 61 | let content_type; 62 | 63 | if (sql) { 64 | content_type = 'application/sql'; 65 | } else { 66 | content_type = 'application/json'; 67 | } 68 | 69 | let headers = { 70 | 'Content-type': content_type, 71 | 'Authorization': token 72 | } 73 | if (data !== null && !sql) { 74 | jsonToSend = JSON.stringify(data); 75 | headers['Content-Length'] = Buffer.byteLength(jsonToSend); 76 | } else { 77 | jsonToSend = data; 78 | } 79 | let options = { 80 | hostname: urlData.hostname, 81 | port: port, 82 | path: urlData.path, 83 | method: reqType, 84 | headers: headers, 85 | rejectUnauthorized: false, 86 | }; 87 | let req = https.request(options, function(response){ 88 | 89 | // temporary data holder 90 | const body = []; 91 | // on every content chunk, push it to the data array 92 | response.on('data', (data) => body.push(data)); 93 | // we are done, resolve promise with those joined chunks 94 | response.on('end', () => { 95 | resolve(body.join('')); 96 | }); 97 | }); 98 | if (reqType == "POST" || reqType == "PUT"){ 99 | req.write(jsonToSend); 100 | } 101 | 102 | req.on('error', (err) => reject(err)); 103 | req.end(); 104 | }); 105 | } 106 | } 107 | 108 | // VALIDATORS 109 | class SDBaseQueryValidator { 110 | constructor(query) { 111 | this.query = query; 112 | } 113 | } 114 | 115 | // Validator for saved queries 116 | class SavedQueryValidator extends SDBaseQueryValidator { 117 | constructor(query) { 118 | super(query) 119 | this.listQueryTypes = [ 120 | "count/entity", "count/event", "count/entity/total", 121 | "aggregation", "top_values"]; 122 | } 123 | 124 | // Check if saved query has valid type 125 | _has_valid_type(){ 126 | let typeQuery = this.query.type; 127 | if (!this.listQueryTypes.includes(typeQuery)) { 128 | throw new errors.InvalidQueryTypeError("The saved query has an invalid type(" + typeQuery + ")"); 129 | } 130 | return true; 131 | } 132 | 133 | // If saved query is valid this returns true 134 | validator() { 135 | return this._has_valid_type(); 136 | } 137 | } 138 | 139 | // Validator for count queries 140 | class QueryCountValidator extends SDBaseQueryValidator { 141 | constructor(query){ 142 | super(query) 143 | } 144 | 145 | // If count query is valid this returns true 146 | validator() { 147 | if (Object.keys(this.query).length > 10) { 148 | throw new errors.MaxLimitError("The query count entity has a limit of 10 queries per request."); 149 | } 150 | return true; 151 | } 152 | } 153 | 154 | // Validator for top values queries 155 | class QueryTopValuesValidator extends SDBaseQueryValidator{ 156 | constructor(query) { 157 | super(query) 158 | } 159 | 160 | // Check query limit 161 | _exceedsQueriesLimit() { 162 | if (Object.keys(this.query).length > 5) { 163 | return true; 164 | } 165 | return false; 166 | } 167 | 168 | // Check columns limit 169 | _exceedsColumnsLimit() { 170 | for(let key in this.query) { 171 | let column = this.query[key]; 172 | if (Object.keys(column).length > 5){ 173 | throw new errors.MaxLimitError("The query " + column + " exceeds the limit of columns per query in request"); 174 | } 175 | } 176 | } 177 | 178 | // Check contains limit 179 | _exceedsValuesContainsLimit() { 180 | for (let key in this.query){ 181 | let query = this.query[key]; 182 | if (query.hasOwnProperty("contains") && query["contains"].length > 5) { 183 | throw new errors.MaxLimitError("The query " + query + " exceeds the limit of contains per query in request"); 184 | } 185 | } 186 | } 187 | 188 | // if top values query is valid this returns true, otherwise false 189 | validator() { 190 | this._exceedsColumnsLimit(); 191 | this._exceedsValuesContainsLimit(); 192 | if (!this._exceedsQueriesLimit()){ 193 | return true 194 | } 195 | return false; 196 | } 197 | } 198 | 199 | // Validator for score or result queries 200 | class QueryDataExtractionValidator extends SDBaseQueryValidator{ 201 | constructor(query) { 202 | super(query) 203 | } 204 | 205 | // Check if data extraction query is valid 206 | validKeys() { 207 | for(let key in this.query) { 208 | let value = this.query[key]; 209 | // Check columns property, columns should have a maximum of 10 itens 210 | if (key == "columns") { 211 | if (value.constructor != Array) { 212 | if (value != 'all') { 213 | throw new errors.InvalidQueryException("The key 'columns' in query has a invalid value."); 214 | } 215 | } 216 | else { 217 | if (value.length > 10) { 218 | throw new errors.InvalidQueryException("The key 'columns' in data extraction result must have up to 10 columns."); 219 | } 220 | } 221 | } 222 | // Check limit property, limit should be less or equal than 100 223 | if (key == "limit") { 224 | if (value.constructor != Number){ 225 | throw new errors.InvalidQueryError("The key 'limit' in query has a invalid value."); 226 | } 227 | } 228 | } 229 | return true; 230 | } 231 | 232 | // If data extraction query is valid this returns true 233 | validator() { 234 | return this.validKeys(); 235 | } 236 | } 237 | 238 | // Validator for column 239 | class ColumnValidator extends SDBaseQueryValidator{ 240 | constructor(query) { 241 | super(query) 242 | } 243 | 244 | // Check column name 245 | validateName(query) { 246 | if (!query.hasOwnProperty("name")) { 247 | throw new errors.InvalidColumnDescriptionError("The column's name can't be empty/None."); 248 | } 249 | else { 250 | let name = query["name"]; 251 | if (name.length > 80) { 252 | throw new errors.InvalidColumnDescriptionError("The column's name have a very big content. (Max: 80 chars)"); 253 | } 254 | } 255 | } 256 | 257 | // Check column description 258 | validateDescription(query) { 259 | let description = query.description; 260 | if (description.length > 80){ 261 | throw new errors.InvalidColumnDescriptionError("The column's description have a very big content. (Max: 300chars)"); 262 | } 263 | } 264 | 265 | // Check column type 266 | validateColumnType(query) { 267 | // The column should have a type property 268 | if (!query.hasOwnProperty("type")){ 269 | throw new errors.InvalidColumnError("The column should have a type."); 270 | } 271 | } 272 | 273 | // If column is decimal check if it has decimal or decimal-time-series type 274 | validateDecimalType(query) { 275 | let decimal_types = ["decimal", "decimal-event"]; 276 | if (!decimal_types.includes(query["decimal-place"])) { 277 | throw new errors.InvalidColumnError("This column is only accepted on type 'decimal' or 'decimal-event'"); 278 | } 279 | } 280 | 281 | // Check if string column is valid 282 | checkStrTypeIntegrity(query) { 283 | if (!query.hasOwnProperty("cardinality")){ 284 | throw new errors.InvalidColumnError("The column with type string should have 'cardinality' key."); 285 | } 286 | } 287 | 288 | // Check if enumerated column is valid 289 | validateEnumeratedType(query) { 290 | if (!query.hasOwnProperty("range")){ 291 | throw new errors.InvalidColumnError("The 'enumerated' type needs of the 'range' parameter."); 292 | } 293 | } 294 | 295 | // If column is valid this returns true 296 | validator() { 297 | if (this.query instanceof Array) { 298 | for (let i = 0; i < this.query.length; i++) { 299 | this.validateColumn(this.query[i]); 300 | } 301 | } else { 302 | this.validateColumn(this.query); 303 | } 304 | 305 | return true; 306 | } 307 | 308 | validateColumn(query) { 309 | this.validateName(query); 310 | this.validateColumnType(query); 311 | if (query["type"] === "string") { 312 | this.checkStrTypeIntegrity(query); 313 | } 314 | if (query["type"] === "enumerated") { 315 | this.validateEnumeratedType(query); 316 | } 317 | if (query.hasOwnProperty("description")) { 318 | this.validateDescription(query); 319 | } 320 | if (query.hasOwnProperty("decimal-place")) { 321 | this.validateDecimalType(query); 322 | } 323 | } 324 | } 325 | 326 | // Class to handle response from Slicing Dice API 327 | class SlicerResponse { 328 | constructor(jsonResponse) { 329 | this.jsonResponse = JSON.parse(jsonResponse); 330 | } 331 | 332 | _raiseErrors(error) { 333 | let codeError = error['code']; 334 | if (mappedErrors[codeError] === undefined){ 335 | throw new errors.SlicingDiceClientError(error); 336 | } else { 337 | throw new Error(error); 338 | } 339 | } 340 | 341 | requestSuccessful(){ 342 | if (this.jsonResponse["errors"] !== undefined){ 343 | this._raiseErrors(this.jsonResponse["errors"][0]); 344 | } 345 | return true; 346 | } 347 | } 348 | 349 | class SlicingDice{ 350 | constructor(apiKeys) { 351 | this._key = apiKeys; 352 | this._checkKey(apiKeys); 353 | this._sdRoutes = { 354 | column: '/column/', 355 | insert: '/insert/', 356 | countEntity: '/query/count/entity/', 357 | countEntityTotal: '/query/count/entity/total/', 358 | countEvent: '/query/count/event/', 359 | aggregation: '/query/aggregation/', 360 | topValues: '/query/top_values/', 361 | existsEntity: '/query/exists/entity/', 362 | result: '/data_extraction/result/', 363 | score: '/data_extraction/score/', 364 | saved: '/query/saved/', 365 | database: '/database/', 366 | sql: '/sql/', 367 | delete: '/delete/', 368 | update: '/update/' 369 | }; 370 | this._setUpRequest(); 371 | } 372 | 373 | get sdAddress() { 374 | return this.BASE_URL; 375 | } 376 | 377 | set sdAddress(value){ 378 | this.BASE_URL = value; 379 | } 380 | 381 | _checkKey(key) { 382 | if (!key.hasOwnProperty("writeKey") && !key.hasOwnProperty("readKey") && !key.hasOwnProperty("masterKey") && !key.hasOwnProperty("customKey")) { 383 | throw new errors.InvalidKeyError("The keys aren't valid."); 384 | } 385 | } 386 | 387 | _setUpRequest() { 388 | // Check if this script is running on a web-browser 389 | if (typeof window === 'undefined') { 390 | this.requester = new RequesterNode(); 391 | // Get the base URL on an enviroment variable 392 | this.BASE_URL = this._getEnviromentSDAddress(); 393 | } 394 | else{ 395 | this.requester = new RequesterBrowser(); 396 | this.BASE_URL = "https://api.slicingdice.com/v1"; 397 | } 398 | } 399 | 400 | _getEnviromentSDAddress() { 401 | let sdAddress = process.env.SD_API_ADDRESS; 402 | if (sdAddress === undefined){ 403 | return "https://api.slicingdice.com/v1"; 404 | } 405 | else { 406 | return sdAddress; 407 | } 408 | } 409 | 410 | _getCurrentKey(){ 411 | if (this._key.hasOwnProperty("masterKey")) 412 | return [this._key["masterKey"], 2]; 413 | else if(this._key.hasOwnProperty("customKey")) 414 | return [this._key["customKey"], 2]; 415 | else if(this._key.hasOwnProperty("writeKey")) 416 | return [this._key["writeKey"], 1]; 417 | else 418 | return [this._key["readKey"], 0]; 419 | } 420 | 421 | _getAPIKey(levelKey){ 422 | let currentLevelKey = this._getCurrentKey(); 423 | if (currentLevelKey[1] == 2){ 424 | return currentLevelKey[0]; 425 | } 426 | else if (currentLevelKey[1] != levelKey){ 427 | throw new errors.InvalidKeyError("This key is not allowed to perform this operation.") 428 | } 429 | return currentLevelKey[0]; 430 | } 431 | 432 | /* Make request to Slicing Dice API 433 | */ 434 | makeRequest(objRequest, sql = false) { 435 | let token = this._getAPIKey(objRequest.levelKey); 436 | let urlReq = this.BASE_URL + objRequest.path; 437 | let requestMethods = ["POST", "PUT", "GET", "DELETE", "PATCH"]; 438 | if (requestMethods.indexOf(objRequest.reqType) === -1){ 439 | throw new errors.InvalidMethodRequestError('This method request is invalid.'); 440 | } 441 | let req = this.requester.run( 442 | token, 443 | urlReq, 444 | objRequest.reqType, 445 | objRequest.data, 446 | sql); 447 | 448 | return req.then((resp) => { 449 | let slicerResponse = new SlicerResponse(resp); 450 | slicerResponse.requestSuccessful(); 451 | return JSON.parse(resp); 452 | }, (err) => { return err;}); 453 | } 454 | 455 | /* Get information about current database */ 456 | getDatabase(){ 457 | let path = this._sdRoutes.database; 458 | return this.makeRequest({ 459 | path: path, 460 | reqType: "GET", 461 | levelKey: 2 462 | }); 463 | } 464 | 465 | /* Get all columns */ 466 | getColumns(){ 467 | let path = this._sdRoutes.column; 468 | return this.makeRequest({ 469 | path: path, 470 | reqType: "GET", 471 | levelKey: 2 472 | }); 473 | } 474 | 475 | /* Get all saved queries */ 476 | getSavedQueries() { 477 | let path = this._sdRoutes.saved; 478 | return this.makeRequest({ 479 | path: path, 480 | reqType: "GET", 481 | levelKey: 2 482 | }); 483 | } 484 | 485 | /* Delete a saved query 486 | * 487 | * @param (string) name - the name of the saved query that will be deleted 488 | */ 489 | deleteSavedQuery(name) { 490 | let path = this._sdRoutes.saved + name; 491 | return this.makeRequest({ 492 | path: path, 493 | reqType: "DELETE", 494 | levelKey: 2 495 | }); 496 | } 497 | 498 | /* Get saved query by name 499 | * 500 | * @param (string) name - the name of the saved query that will be retrieved 501 | */ 502 | getSavedQuery(name) { 503 | let path = this._sdRoutes.saved + name; 504 | return this.makeRequest({ 505 | path: path, 506 | reqType: "GET", 507 | levelKey: 0 508 | }); 509 | } 510 | 511 | /* Send a insert command to the Slicing Dice API 512 | * 513 | * @param (array) query - the query to send to Slicing Dice API 514 | */ 515 | insert(query){ 516 | let path = this._sdRoutes.insert; 517 | return this.makeRequest({ 518 | path: path, 519 | reqType: "POST", 520 | data: query, 521 | levelKey: 1 522 | }); 523 | } 524 | 525 | /* Create a column on Slicing Dice API 526 | * 527 | * @param (array) query - the query to send to Slicing Dice API 528 | */ 529 | createColumn(query){ 530 | let path = this._sdRoutes.column; 531 | let sdValidator = new ColumnValidator(query); 532 | if (sdValidator.validator()){ 533 | return this.makeRequest({ 534 | path: path, 535 | reqType: "POST", 536 | data: query, 537 | levelKey: 1 538 | }); 539 | } 540 | } 541 | 542 | /* Makes a count query on Slicing Dice API 543 | * 544 | * @param (array) query - the query to send to Slicing Dice API 545 | * @param (string) path - the path to send the query (count entity or count event path) 546 | */ 547 | countQueryWrapper(query, path){ 548 | let sdValidator = new QueryCountValidator(query); 549 | if (sdValidator.validator()){ 550 | return this.makeRequest({ 551 | path: path, 552 | reqType: "POST", 553 | data: query, 554 | levelKey: 0 555 | }); 556 | } 557 | } 558 | 559 | /* Makes a count entity query on Slicing Dice API 560 | * 561 | * @param (array) query - the query to send to Slicing Dice API 562 | */ 563 | countEntity(query) { 564 | let path = this._sdRoutes.countEntity; 565 | let sdValidator = new QueryCountValidator(query); 566 | return this.countQueryWrapper(query, path); 567 | } 568 | 569 | /* Makes a total query on Slicing Dice API 570 | * 571 | * @param (array) dimensions - the dimensions in which the total query will be performed 572 | */ 573 | countEntityTotal(dimensions = []) { 574 | let query = { 575 | "dimensions": dimensions 576 | }; 577 | let path = this._sdRoutes.countEntityTotal; 578 | return this.makeRequest({ 579 | path: path, 580 | reqType: "POST", 581 | data: query, 582 | levelKey: 0 583 | }) 584 | } 585 | 586 | /* Makes a count event query on Slicing Dice API 587 | * 588 | * @param (array) query - the query to send to Slicing Dice API 589 | */ 590 | countEvent(query) { 591 | let path = this._sdRoutes.countEvent; 592 | return this.countQueryWrapper(query, path); 593 | } 594 | 595 | /* Makes a exists query on Slicing Dice API 596 | * 597 | * @param (array) ids - the array of IDs to check 598 | * @param (string) dimension - the dimension to check for IDs 599 | */ 600 | existsEntity(ids, dimension = null) { 601 | if (ids.constructor != Array) { 602 | throw new errors.WrongTypeError("This method should receive an array as parameter"); 603 | } 604 | if (ids.length > 100) { 605 | throw new errors.MaxLimitError("The query exists entity must have up to 100 ids."); 606 | } 607 | let path = this._sdRoutes.existsEntity; 608 | let query = { 609 | "ids": ids 610 | } 611 | if (dimension) { 612 | query["dimension"] = dimension 613 | } 614 | return this.makeRequest({ 615 | path: path, 616 | reqType: "POST", 617 | data: query, 618 | levelKey: 0 619 | }); 620 | } 621 | 622 | /* Makes an aggregation query on Slicing Dice API 623 | * 624 | * @param (array) query - the query to send to Slicing Dice API 625 | */ 626 | aggregation(query){ 627 | let path = this._sdRoutes.aggregation; 628 | return this.makeRequest({ 629 | path: path, 630 | reqType: "POST", 631 | data: query, 632 | levelKey: 0 633 | }); 634 | } 635 | 636 | /* Makes a top values query on Slicing Dice API 637 | * 638 | * @param (array) query - the query to send to Slicing Dice API 639 | */ 640 | topValues(query) { 641 | let path = this._sdRoutes.topValues; 642 | let sdValidator = new QueryTopValuesValidator(query); 643 | if (sdValidator.validator()){ 644 | return this.makeRequest({ 645 | path: path, 646 | reqType: "POST", 647 | data: query, 648 | levelKey: 0 649 | }); 650 | } 651 | } 652 | 653 | /* Create a saved query on Slicing Dice API 654 | * 655 | * @param (array) query - the query to send to Slicing Dice API 656 | */ 657 | createSavedQuery(query) { 658 | let path = this._sdRoutes.saved; 659 | let sdValidator = new SavedQueryValidator(query); 660 | if (sdValidator.validator()){ 661 | return this.makeRequest({ 662 | path: path, 663 | reqType: "POST", 664 | data: query, 665 | levelKey: 2 666 | }); 667 | } 668 | } 669 | 670 | /* Update a previous saved query on Slicing Dice API 671 | * 672 | * @param (string) name - the name of the saved query to update 673 | * @param (array) query - the query to send to Slicing Dice API 674 | */ 675 | updateSavedQuery(name, query) { 676 | let path = this._sdRoutes.saved + name; 677 | return this.makeRequest({ 678 | path: path, 679 | reqType: "PUT", 680 | data: query, 681 | levelKey: 2 682 | }); 683 | } 684 | 685 | /* Makes a data extraction query (result or score) on Slicing Dice API 686 | * 687 | * @param (array) query - the query to send to Slicing Dice API 688 | * @param (string) path - the path to send the query (result or score path) 689 | */ 690 | dataExtractionWrapper(query, path) { 691 | let sdValidator = new QueryDataExtractionValidator(query); 692 | if (sdValidator.validator()){ 693 | return this.makeRequest({ 694 | path: path, 695 | reqType: "POST", 696 | data: query, 697 | levelKey: 0 698 | }); 699 | } 700 | } 701 | 702 | /* Makes a result query on Slicing Dice API 703 | * 704 | * @param (array) query - the query to send to Slicing Dice API 705 | */ 706 | result(query) { 707 | let path = this._sdRoutes.result; 708 | return this.dataExtractionWrapper(query, path); 709 | } 710 | 711 | /* Makes a score query on Slicing Dice API 712 | * 713 | * @param (array) query - the query to send to Slicing Dice API 714 | */ 715 | score(query) { 716 | let path = this._sdRoutes.score; 717 | return this.dataExtractionWrapper(query, path); 718 | } 719 | 720 | sql(query) { 721 | let path = this._sdRoutes.sql; 722 | return this.makeRequest({ 723 | path: path, 724 | reqType: "POST", 725 | data: query, 726 | levelKey: 0 727 | }, true); 728 | } 729 | 730 | /* Make a delete request 731 | * 732 | * @param (array) query - The query that represents the data to be deleted 733 | */ 734 | delete(query) { 735 | let path = this._sdRoutes.delete; 736 | return this.makeRequest({ 737 | path: path, 738 | reqType: "POST", 739 | data: query, 740 | levelKey: 2 741 | }); 742 | } 743 | 744 | /* Make a update request 745 | * 746 | * @param (array) query - The query that represents the data to be updated 747 | */ 748 | update(query) { 749 | let path = this._sdRoutes.update; 750 | return this.makeRequest({ 751 | path: path, 752 | reqType: "POST", 753 | data: query, 754 | levelKey: 2 755 | }); 756 | } 757 | } 758 | 759 | module.exports = SlicingDice; 760 | if (typeof window !== 'undefined'){ 761 | window.SlicingDice = SlicingDice; 762 | } 763 | }()); -------------------------------------------------------------------------------- /tests_and_examples/README.md: -------------------------------------------------------------------------------- 1 | # Tests and Examples 2 | 3 | In this directory, you find unitary and regression tests for SlicingDice client. These tests are self-contained and have two purposes: 4 | 5 | 1. Present examples of column creation, data insertion, and queries, as well as expected query results 6 | 2. Provide regression tests that can be executed by anyone with one simple command 7 | 8 | ## Data 9 | The `examples/` directory contains one file for each [SlicingDice query](https://docs.slicingdice.com/docs/how-to-make-queries) in JSON format. 10 | 11 | Each JSON file contains a list of examples, such as the following excerpt, with the following elements: 12 | 13 | * `name`: Test name, as it will be printed on screen. 14 | * `columns`: [Columns](https://docs.slicingdice.com/docs/how-to-create-columns) that will be created for this test. 15 | * `insert`: Data that will be [inserted](https://docs.slicingdice.com/docs/how-to-insert-data) in this test. 16 | * `query`: [Query](https://docs.slicingdice.com/docs/how-to-make-queries) that will extract some information for the inserted data. 17 | * `expected`: Expected result message after SlicingDice executes the query. Values marked as `"ignore"` won't be taken into account to determine whether the test has passed or failed. 18 | 19 | ```json 20 | [ 21 | { 22 | "name": "Test for a \"COUNT ENTITY\" query using column \"STRING\" and parameter \"EQUALS\".", 23 | "description": "Optional description about the test", 24 | "columns": [ 25 | { 26 | "name": "string_test_column", 27 | "api-name": "string-test-column", 28 | "type": "string", 29 | "cardinality": "high", 30 | "storage": "latest-value" 31 | } 32 | ], 33 | "insert": { 34 | "24": { 35 | "string-test-column": "value:matched_value" 36 | } 37 | }, 38 | "query": { 39 | "query-name": "test_result_query", 40 | "query": [ 41 | { 42 | "string-test-column": { 43 | "equals": "value:matched_value" 44 | } 45 | } 46 | ] 47 | }, 48 | "expected": { 49 | "status": "ignore", 50 | "result": { 51 | "test_result_query": 1 52 | }, 53 | "took": "ignore" 54 | } 55 | }, 56 | { 57 | "name": "Test for a \"COUNT ENTITY\" query using column \"INTEGER\" and parameter \"EQUALS\".", 58 | "description": "Optional description about the test", 59 | "columns": [ 60 | { 61 | "name": "integer_test_column", 62 | "api-name": "integer-test-column", 63 | "type": "integer", 64 | "storage": "latest-value" 65 | } 66 | ], 67 | "insert": { 68 | "1": { 69 | "integer-test-column": 1000001 70 | }, 71 | "2": { 72 | "integer-test-column": 1234567 73 | }, 74 | "3": { 75 | "integer-test-column": 1000001 76 | } 77 | }, 78 | "query": { 79 | "query-name": "test_result_query", 80 | "query": [ 81 | { 82 | "integer-test-column": { 83 | "equals": 1000001 84 | } 85 | } 86 | ] 87 | }, 88 | "expected": { 89 | "status": "ignore", 90 | "result": { 91 | "test_result_query": 2 92 | }, 93 | "took": "ignore" 94 | } 95 | } 96 | ] 97 | ``` 98 | 99 | ## Executing 100 | 101 | In order to run all tests stored at `examples/`, simply run the `run_query_tests.js` script: 102 | 103 | ```bash 104 | $ node run_query_tests.js 105 | ``` 106 | 107 | ## Output 108 | 109 | The test script will execute one test at a time, printing results such as the following: 110 | 111 | ``` 112 | (1/2) Executing test "Test for a "COUNT ENTITY" query using column "STRING" and parameter "EQUALS"." 113 | Query type: count_entity 114 | Creating 1 column 115 | Inserting 1 entity 116 | Querying 117 | Status: Passed 118 | 119 | (2/2) Executing test "Test for a "COUNT ENTITY" query using column "INTEGER" and parameter "EQUALS"." 120 | Query type: count_entity 121 | Creating 1 column 122 | Inserting 3 entities 123 | Querying 124 | Status: Passed 125 | 126 | Results: 127 | Successes: 2 128 | Fails: 0 129 | 130 | SUCCESS: All tests passed 131 | ``` 132 | 133 | In case a test fails, the script will output the expected and actual result messages, continue executing other tests and, at the end, consolidate all failed tests. 134 | 135 | ``` 136 | (1/2) Executing test "Test for a "COUNT ENTITY" query using column "STRING" and parameter "EQUALS"." 137 | Query type: count_entity 138 | Creating 1 column 139 | Inserting 1 entity 140 | Querying 141 | Expected: "result": {u'test_result_query': 1} 142 | Result: "result": {u'test_result_query': 0} 143 | Status: Failed 144 | 145 | (2/2) Executing test "Test for a "COUNT ENTITY" query using column "INTEGER" and parameter "EQUALS"." 146 | Query type: count_entity 147 | Creating 1 column 148 | Inserting 3 entities 149 | Querying 150 | Status: Passed 151 | 152 | Results: 153 | Successes: 1 154 | Fails: 1 155 | - Test for a "COUNT ENTITY" query using column "STRING" and parameter "EQUALS". 156 | 157 | FAIL: 1 test has failed 158 | ``` 159 | -------------------------------------------------------------------------------- /tests_and_examples/examples/delete.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Test for a DELETION on a specific ENTITY ID.", 4 | "description": "In this test we will delete a record based on the ENTITY ID.", 5 | "columns": [ 6 | { 7 | "type": "string", 8 | "cardinality": "high", 9 | "storage": "latest-value", 10 | "name": "string_test_column", 11 | "api-name": "string-test-column", 12 | "dimension": "delete-test" 13 | }, 14 | { 15 | "storage": "latest-value", 16 | "type": "integer", 17 | "name": "integer_test_column", 18 | "api-name": "integer-test-column", 19 | "dimension": "delete-test" 20 | } 21 | ], 22 | "insert": { 23 | "Entity1": { 24 | "string-test-column": "value:deleted_entity", 25 | "integer-test-column": 1000001, 26 | "dimension": "delete-test" 27 | }, 28 | "Entity2": { 29 | "string-test-column": "value:deleted_entity", 30 | "integer-test-column": 1000001, 31 | "dimension": "delete-test" 32 | }, 33 | "Entity3": { 34 | "string-test-column": "value:deleted_entity", 35 | "integer-test-column": 1000001, 36 | "dimension": "delete-test" 37 | }, 38 | "Entity4": { 39 | "string-test-column": "value:deleted_entity", 40 | "integer-test-column": 1000001, 41 | "dimension": "delete-test" 42 | }, 43 | "Entity5": { 44 | "string-test-column": "value:deleted_entity", 45 | "integer-test-column": 1000001, 46 | "dimension": "delete-test" 47 | }, 48 | "Entity6": { 49 | "string-test-column": "value:deleted_entity", 50 | "integer-test-column": 1000001, 51 | "dimension": "delete-test" 52 | } 53 | }, 54 | "additional_operation": { 55 | "query":[ 56 | { 57 | "entity-id":{ 58 | "equals":"Entity3" 59 | } 60 | } 61 | ], 62 | "dimension": "delete-test" 63 | }, 64 | "result_additional": { 65 | "status": "ignore", 66 | "result": { 67 | "deleted": 1 68 | }, 69 | "took": "ignore" 70 | }, 71 | "query": { 72 | "query-name": "deleted_query", 73 | "query": [ 74 | { 75 | "string-test-column": { 76 | "equals": "value:deleted_entity" 77 | } 78 | } 79 | ], 80 | "dimension": "delete-test" 81 | }, 82 | "expected": { 83 | "status": "ignore", 84 | "result": { 85 | "deleted_query": 5 86 | }, 87 | "took": "ignore" 88 | } 89 | }, 90 | { 91 | "name": "Test for a DELETION on multiple values.", 92 | "description": "In this test we will delete a record based on a value from a column.", 93 | "columns": [ 94 | { 95 | "type": "string", 96 | "cardinality": "high", 97 | "storage": "latest-value", 98 | "name": "string_test_column", 99 | "api-name": "string-test-column", 100 | "dimension": "delete-test" 101 | }, 102 | { 103 | "storage": "latest-value", 104 | "type": "integer", 105 | "name": "integer_test_column", 106 | "api-name": "integer-test-column", 107 | "dimension": "delete-test" 108 | } 109 | ], 110 | "insert": { 111 | "Entity1": { 112 | "string-test-column": "value:deleted_entity", 113 | "integer-test-column": 1000001, 114 | "dimension": "delete-test" 115 | }, 116 | "Entity2": { 117 | "string-test-column": "value:deleted_entity", 118 | "integer-test-column": 1000001, 119 | "dimension": "delete-test" 120 | }, 121 | "Entity3": { 122 | "string-test-column": "value:deleted_entity", 123 | "integer-test-column": 1000001, 124 | "dimension": "delete-test" 125 | }, 126 | "Entity4": { 127 | "string-test-column": "value:not_deleted_entity", 128 | "integer-test-column": 1000001, 129 | "dimension": "delete-test" 130 | }, 131 | "Entity5": { 132 | "string-test-column": "value:not_deleted_entity", 133 | "integer-test-column": 1000001, 134 | "dimension": "delete-test" 135 | }, 136 | "Entity6": { 137 | "string-test-column": "value:not_deleted_entity", 138 | "integer-test-column": 1000001, 139 | "dimension": "delete-test" 140 | } 141 | }, 142 | "additional_operation": { 143 | "query":[ 144 | { 145 | "string-test-column":{ 146 | "equals":"value:deleted_entity" 147 | } 148 | } 149 | ], 150 | "dimension": "delete-test" 151 | }, 152 | "result_additional": { 153 | "status": "ignore", 154 | "result": { 155 | "deleted": 3 156 | }, 157 | "took": "ignore" 158 | }, 159 | "query": { 160 | "query-name": "deleted_query", 161 | "query": [ 162 | { 163 | "string-test-column": { 164 | "equals": "value:deleted_entity" 165 | } 166 | } 167 | ], 168 | "dimension": "delete-test" 169 | }, 170 | "expected": { 171 | "status": "ignore", 172 | "result": { 173 | "deleted_query": 0 174 | }, 175 | "took": "ignore" 176 | } 177 | } 178 | ] -------------------------------------------------------------------------------- /tests_and_examples/examples/sql.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "query": "SELECT COUNT(*) FROM users", 4 | "expected": { 5 | "result": [ 6 | { 7 | "COUNT": 3 8 | } 9 | ], 10 | "took": "ignore", 11 | "count": "ignore", 12 | "status": "ignore" 13 | }, 14 | "name": "Test #1" 15 | }, 16 | { 17 | "query": "SELECT COUNT(*) FROM users WHERE name='Eduardo' OR age='19'", 18 | "expected": { 19 | "result": [ 20 | { 21 | "COUNT": 2 22 | } 23 | ], 24 | "took": "ignore", 25 | "count": "ignore", 26 | "status": "ignore" 27 | }, 28 | "name": "Test #2" 29 | }, 30 | { 31 | "query": "SELECT COUNT(*) FROM users WHERE name='Joao' AND [clicks.value]='Pay Now' AND [clicks.date] BETWEEN '2017-05-14' AND '2017-05-29'", 32 | "expected": { 33 | "result": [ 34 | { 35 | "COUNT": 1 36 | } 37 | ], 38 | "took": "ignore", 39 | "count": "ignore", 40 | "status": "ignore" 41 | }, 42 | "name": "Test #3" 43 | }, 44 | { 45 | "query": "SELECT COUNT(*) FROM users WHERE name='Joao'", 46 | "expected": { 47 | "result": [ 48 | { 49 | "COUNT": 1 50 | } 51 | ], 52 | "took": "ignore", 53 | "count": "ignore", 54 | "status": "ignore" 55 | }, 56 | "name": "Test #4" 57 | }, 58 | { 59 | "query": "SELECT COUNT(*) FROM users WHERE age BETWEEN 0 AND 49", 60 | "expected": { 61 | "result": [ 62 | { 63 | "COUNT": 3 64 | } 65 | ], 66 | "took": "ignore", 67 | "count": "ignore", 68 | "status": "ignore" 69 | }, 70 | "name": "Test #5" 71 | }, 72 | { 73 | "query": "SELECT [purchased-products.value] FROM users WHERE country='USA' GROUP BY [purchased-products.value] HAVING [purchased-products.date] BETWEEN '2017-05-09T00:00:00' AND '2017-05-29T00:00:00'", 74 | "expected": { 75 | "result": [ 76 | { 77 | "purchased-products.value": 5 78 | } 79 | ], 80 | "took": "ignore", 81 | "count": "ignore", 82 | "status": "ignore" 83 | }, 84 | "name": "Test #6" 85 | }, 86 | { 87 | "query": "SELECT browser FROM users GROUP BY browser LIMIT 5", 88 | "expected": { 89 | "result": [ 90 | { 91 | "browser": "chrome" 92 | }, 93 | { 94 | "browser": "firefox" 95 | }, 96 | { 97 | "browser": "safari" 98 | } 99 | ], 100 | "took": "ignore", 101 | "count": "ignore", 102 | "status": "ignore" 103 | }, 104 | "name": "Test #7" 105 | }, 106 | { 107 | "query": "SELECT os, browser FROM users GROUP BY os, browser LIMIT 5", 108 | "expected": { 109 | "result": [ 110 | { 111 | "os": "windows", 112 | "browser": "chrome" 113 | }, 114 | { 115 | "os": "mac", 116 | "browser": "safari" 117 | }, 118 | { 119 | "os": "linux", 120 | "browser": "firefox" 121 | } 122 | ], 123 | "took": "ignore", 124 | "count": "ignore", 125 | "status": "ignore" 126 | }, 127 | "name": "Test #8" 128 | }, 129 | { 130 | "query": "SELECT AVG(age) FROM users", 131 | "expected": { 132 | "result": [ 133 | { 134 | "AVG": 19.0 135 | } 136 | ], 137 | "took": "ignore", 138 | "count": "ignore", 139 | "status": "ignore" 140 | }, 141 | "name": "Test #9" 142 | }, 143 | { 144 | "query": "SELECT os, browser, AVG(age) FROM users GROUP BY os, browser LIMIT 5", 145 | "expected": { 146 | "result": [ 147 | { 148 | "AVG": 21.0, 149 | "os": "windows", 150 | "browser": "chrome" 151 | }, 152 | { 153 | "AVG": 17.0, 154 | "os": "mac", 155 | "browser": "safari" 156 | }, 157 | { 158 | "AVG": 19.0, 159 | "os": "linux", 160 | "browser": "firefox" 161 | } 162 | ], 163 | "took": "ignore", 164 | "count": "ignore", 165 | "status": "ignore" 166 | }, 167 | "name": "Test #10" 168 | }, 169 | { 170 | "query": "SELECT os, browser, AVG(age), MIN(age), MAX(age) FROM users GROUP BY os, browser LIMIT 5", 171 | "expected": { 172 | "result": [ 173 | { 174 | "AVG": 21.0, 175 | "MIN": 21, 176 | "os": "windows", 177 | "MAX": 21, 178 | "browser": "chrome" 179 | }, 180 | { 181 | "AVG": 17.0, 182 | "MIN": 17, 183 | "os": "mac", 184 | "MAX": 17, 185 | "browser": "safari" 186 | }, 187 | { 188 | "AVG": 19.0, 189 | "MIN": 19, 190 | "os": "linux", 191 | "MAX": 19, 192 | "browser": "firefox" 193 | } 194 | ], 195 | "took": "ignore", 196 | "count": "ignore", 197 | "status": "ignore" 198 | }, 199 | "name": "Test #11" 200 | }, 201 | { 202 | "query": "SELECT AVG(age) FROM users WHERE country='USA'", 203 | "expected": { 204 | "result": [ 205 | { 206 | "AVG": 19.0 207 | } 208 | ], 209 | "took": "ignore", 210 | "count": "ignore", 211 | "status": "ignore" 212 | }, 213 | "name": "Test #12" 214 | }, 215 | { 216 | "query": "SELECT MAX([purchase-values.value]) FROM users HAVING [purchase-values.date] BETWEEN '2017-05-19' AND '2017-05-30'", 217 | "expected": { 218 | "result": [ 219 | { 220 | "MAX": 15.450000 221 | } 222 | ], 223 | "took": "ignore", 224 | "count": "ignore", 225 | "status": "ignore" 226 | }, 227 | "name": "Test #13" 228 | }, 229 | { 230 | "query": "SELECT MAX([purchase-values.value]) FROM users WHERE country='USA' HAVING [purchase-values.date] BETWEEN '2017-05-19' AND '2017-05-30'", 231 | "expected": { 232 | "result": [ 233 | { 234 | "MAX": 10.500000 235 | } 236 | ], 237 | "took": "ignore", 238 | "count": "ignore", 239 | "status": "ignore" 240 | }, 241 | "name": "Test #14" 242 | }, 243 | { 244 | "query": "SELECT browser, COUNT(browser) AS browser_count FROM users GROUP BY browser ORDER BY browser_count ASC", 245 | "expected": { 246 | "result": [ 247 | { 248 | "browser": "chrome", 249 | "browser_count": 1 250 | }, 251 | { 252 | "browser": "firefox", 253 | "browser_count": 1 254 | }, 255 | { 256 | "browser": "safari", 257 | "browser_count": 1 258 | } 259 | ], 260 | "took": "ignore", 261 | "count": "ignore", 262 | "status": "ignore" 263 | }, 264 | "name": "Test #15" 265 | }, 266 | { 267 | "query": "SELECT * FROM users", 268 | "expected": { 269 | "result": [ 270 | { 271 | "age": 21, 272 | "browser": "chrome", 273 | "city": "natal", 274 | "country": "brazil", 275 | "entity-id": "user2@slicingdice.com", 276 | "gender": "male", 277 | "name": "eduardo", 278 | "os": "windows", 279 | "state": "rn", 280 | "score": 1.0 281 | }, 282 | { 283 | "age": 19, 284 | "browser": "firefox", 285 | "city": "new york", 286 | "country": "usa", 287 | "entity-id": "user1@slicingdice.com", 288 | "gender": "female", 289 | "name": "joao", 290 | "os": "linux", 291 | "state": "ny", 292 | "score": 1.0 293 | }, 294 | { 295 | "age": 17, 296 | "browser": "safari", 297 | "city": "salvador", 298 | "country": "brazil", 299 | "entity-id": "user3@slicingdice.com", 300 | "gender": "female", 301 | "name": "gabriela", 302 | "os": "mac", 303 | "state": "ba", 304 | "score": 1.0 305 | } 306 | ], 307 | "took": "ignore", 308 | "count": "ignore", 309 | "status": "ignore" 310 | }, 311 | "name": "Test #16" 312 | }, 313 | { 314 | "query": "SELECT [entity-id] FROM users WHERE age BETWEEN 17 AND 24 LIMIT 100", 315 | "expected": { 316 | "result": [ 317 | { 318 | "entity-id": "user2@slicingdice.com" 319 | }, 320 | { 321 | "entity-id": "user1@slicingdice.com" 322 | }, 323 | { 324 | "entity-id": "user3@slicingdice.com" 325 | } 326 | ], 327 | "took": "ignore", 328 | "count": "ignore", 329 | "status": "ignore" 330 | }, 331 | "name": "Test #17" 332 | }, 333 | { 334 | "query": "SELECT name FROM users WHERE age BETWEEN 17 AND 24 LIMIT 100", 335 | "expected": { 336 | "result": [ 337 | { 338 | "name": "eduardo" 339 | }, 340 | { 341 | "name": "joao" 342 | }, 343 | { 344 | "name": "gabriela" 345 | } 346 | ], 347 | "took": "ignore", 348 | "count": "ignore", 349 | "status": "ignore" 350 | }, 351 | "name": "Test #18" 352 | }, 353 | { 354 | "query": "SELECT * FROM users WHERE age BETWEEN 17 AND 24 LIMIT 100", 355 | "expected": { 356 | "result": [ 357 | { 358 | "age": 21, 359 | "browser": "chrome", 360 | "city": "natal", 361 | "country": "brazil", 362 | "entity-id": "user2@slicingdice.com", 363 | "gender": "male", 364 | "name": "eduardo", 365 | "os": "windows", 366 | "state": "rn", 367 | "score": 1.0 368 | }, 369 | { 370 | "age": 19, 371 | "browser": "firefox", 372 | "city": "new york", 373 | "country": "usa", 374 | "entity-id": "user1@slicingdice.com", 375 | "gender": "female", 376 | "name": "joao", 377 | "os": "linux", 378 | "state": "ny", 379 | "score": 1.0 380 | }, 381 | { 382 | "age": 17, 383 | "browser": "safari", 384 | "city": "salvador", 385 | "country": "brazil", 386 | "entity-id": "user3@slicingdice.com", 387 | "gender": "female", 388 | "name": "gabriela", 389 | "os": "mac", 390 | "state": "ba", 391 | "score": 1.0 392 | } 393 | ], 394 | "took": "ignore", 395 | "count": "ignore", 396 | "status": "ignore" 397 | }, 398 | "name": "Test #19" 399 | }, 400 | { 401 | "query": "SELECT AVG([purchase-values.value]) FROM users WHERE age BETWEEN 17 AND 24 AND [purchase-values.date] BETWEEN '2017-05-14T00:00:00' AND '2017-05-29T00:00:00' LIMIT 100", 402 | "expected": { 403 | "result": [ 404 | { 405 | "AVG": 11.201333 406 | } 407 | ], 408 | "took": "ignore", 409 | "count": "ignore", 410 | "status": "ignore" 411 | }, 412 | "name": "Test #20" 413 | }, 414 | { 415 | "query": "SELECT score FROM users WHERE age BETWEEN 17 AND 24 LIMIT 100", 416 | "expected": { 417 | "result": [ 418 | { 419 | "score": 1.0 420 | }, 421 | { 422 | "score": 1.0 423 | }, 424 | { 425 | "score": 1.0 426 | } 427 | ], 428 | "took": "ignore", 429 | "count": "ignore", 430 | "status": "ignore" 431 | }, 432 | "name": "Test #21" 433 | }, 434 | { 435 | "query": "SELECT name, score FROM users WHERE age BETWEEN 17 AND 24 LIMIT 100", 436 | "expected": { 437 | "result": [ 438 | { 439 | "score": 1.0, 440 | "name": "gabriela" 441 | }, 442 | { 443 | "score": 1.0, 444 | "name": "eduardo" 445 | }, 446 | { 447 | "score": 1.0, 448 | "name": "joao" 449 | } 450 | ], 451 | "took": "ignore", 452 | "count": "ignore", 453 | "status": "ignore" 454 | }, 455 | "name": "Test #22" 456 | }, 457 | { 458 | "query": "SELECT * FROM users WHERE age BETWEEN 17 AND 24 LIMIT 100", 459 | "expected": { 460 | "result": [ 461 | { 462 | "age": 21, 463 | "browser": "chrome", 464 | "city": "natal", 465 | "country": "brazil", 466 | "entity-id": "user2@slicingdice.com", 467 | "gender": "male", 468 | "name": "eduardo", 469 | "os": "windows", 470 | "state": "rn", 471 | "score": 1.0 472 | }, 473 | { 474 | "age": 19, 475 | "browser": "firefox", 476 | "city": "new york", 477 | "country": "usa", 478 | "entity-id": "user1@slicingdice.com", 479 | "gender": "female", 480 | "name": "joao", 481 | "os": "linux", 482 | "state": "ny", 483 | "score": 1.0 484 | }, 485 | { 486 | "age": 17, 487 | "browser": "safari", 488 | "city": "salvador", 489 | "country": "brazil", 490 | "entity-id": "user3@slicingdice.com", 491 | "gender": "female", 492 | "name": "gabriela", 493 | "os": "mac", 494 | "state": "ba", 495 | "score": 1.0 496 | } 497 | ], 498 | "took": "ignore", 499 | "count": "ignore", 500 | "status": "ignore" 501 | }, 502 | "name": "Test #23" 503 | }, 504 | { 505 | "query": "SELECT AVG([purchase-values.value]), score FROM users WHERE age BETWEEN 17 AND 24 HAVING [purchase-values.date] BETWEEN '2017-05-14T00:00:00' AND '2017-05-29T00:00:00' LIMIT 100", 506 | "expected": { 507 | "result": [ 508 | { 509 | "score": 1.0, 510 | "AVG": 7.654000 511 | }, 512 | { 513 | "score": 1.0, 514 | "AVG": 15.450000 515 | }, 516 | { 517 | "score": 1.0, 518 | "AVG": 10.500000 519 | } 520 | ], 521 | "took": "ignore", 522 | "count": "ignore", 523 | "status": "ignore" 524 | }, 525 | "name": "Test #24" 526 | }, 527 | { 528 | "query": "SELECT COUNT(*) FROM users WHERE age = 21", 529 | "expected": { 530 | "result": [ 531 | { 532 | "COUNT": 1 533 | } 534 | ], 535 | "took": "ignore", 536 | "count": "ignore", 537 | "status": "ignore" 538 | }, 539 | "name": "Test #25" 540 | }, 541 | { 542 | "query": "SELECT COUNT(*) FROM users WHERE [visited-page-events.value]='visited-page' AND [visited-page-events.date] BETWEEN '2017-05-14T00:00:00' AND '2017-05-28T00:00:00'", 543 | "expected": { 544 | "result": [ 545 | { 546 | "COUNT": 1 547 | } 548 | ], 549 | "took": "ignore", 550 | "count": "ignore", 551 | "status": "ignore" 552 | }, 553 | "name": "Test #26" 554 | }, 555 | { 556 | "query": "SELECT os FROM users WHERE os IN ('linux', 'mac', 'windows') GROUP BY os LIMIT 3", 557 | "expected": { 558 | "result": [ 559 | { 560 | "os": "linux" 561 | }, 562 | { 563 | "os": "mac" 564 | }, 565 | { 566 | "os": "windows" 567 | } 568 | ], 569 | "took": "ignore", 570 | "count": "ignore", 571 | "status": "ignore" 572 | }, 573 | "name": "Test #27" 574 | }, 575 | { 576 | "query": "SELECT city, state FROM users WHERE state = 'NY' LIMIT 3", 577 | "expected": { 578 | "result": [ 579 | { 580 | "city": "new york", 581 | "state": "ny" 582 | } 583 | ], 584 | "took": "ignore", 585 | "count": "ignore", 586 | "status": "ignore" 587 | }, 588 | "name": "Test #28" 589 | }, 590 | { 591 | "query": "SELECT COUNT(*) FROM users WHERE age != 25", 592 | "expected": { 593 | "result": [ 594 | { 595 | "COUNT": 3 596 | } 597 | ], 598 | "took": "ignore", 599 | "count": "ignore", 600 | "status": "ignore" 601 | }, 602 | "name": "Test #29" 603 | }, 604 | { 605 | "query": "SELECT os FROM users WHERE os NOT IN ('mac', 'windows') GROUP BY os LIMIT 2", 606 | "expected": { 607 | "result": [ 608 | { 609 | "os": "linux" 610 | } 611 | ], 612 | "took": "ignore", 613 | "count": "ignore", 614 | "status": "ignore" 615 | }, 616 | "name": "Test #30" 617 | }, 618 | { 619 | "query": "SELECT city, state FROM users WHERE state != 'NY' LIMIT 3", 620 | "expected": { 621 | "result": [ 622 | { 623 | "city": "salvador", 624 | "state": "ba" 625 | }, 626 | { 627 | "city": "natal", 628 | "state": "rn" 629 | } 630 | ], 631 | "took": "ignore", 632 | "count": "ignore", 633 | "status": "ignore" 634 | }, 635 | "name": "Test #31" 636 | }, 637 | { 638 | "query": "SELECT COUNT(*) FROM users WHERE age BETWEEN 18 AND 24", 639 | "expected": { 640 | "result": [ 641 | { 642 | "COUNT": 2 643 | } 644 | ], 645 | "took": "ignore", 646 | "count": "ignore", 647 | "status": "ignore" 648 | }, 649 | "name": "Test #32" 650 | }, 651 | { 652 | "query": "SELECT os FROM users WHERE age BETWEEN 18 AND 24 GROUP BY os LIMIT 5", 653 | "expected": { 654 | "result": [ 655 | { 656 | "os": "linux" 657 | }, 658 | { 659 | "os": "windows" 660 | } 661 | ], 662 | "took": "ignore", 663 | "count": "ignore", 664 | "status": "ignore" 665 | }, 666 | "name": "Test #33" 667 | }, 668 | { 669 | "query": "SELECT city, state FROM users WHERE age BETWEEN 18 AND 24 LIMIT 3", 670 | "expected": { 671 | "result": [ 672 | { 673 | "city": "natal", 674 | "state": "rn" 675 | }, 676 | { 677 | "city": "new york", 678 | "state": "ny" 679 | } 680 | ], 681 | "took": "ignore", 682 | "count": "ignore", 683 | "status": "ignore" 684 | }, 685 | "name": "Test #34" 686 | }, 687 | { 688 | "query": "SELECT MAX([purchase-values.value]) FROM users GROUP BY INTERVAL([purchase-values.value])='days' HAVING [purchase-values.date] BETWEEN '2017-05-09' AND '2017-05-30'", 689 | "expected": { 690 | "result": [ 691 | { 692 | "MAX": 15.450000 693 | }, 694 | { 695 | "MAX": 10.500000 696 | } 697 | ], 698 | "took": "ignore", 699 | "count": "ignore", 700 | "status": "ignore" 701 | }, 702 | "name": "Test #35" 703 | }, 704 | { 705 | "query": "SELECT COUNT(*) FROM users WHERE age > 17", 706 | "expected": { 707 | "result": [ 708 | { 709 | "COUNT": 2 710 | } 711 | ], 712 | "took": "ignore", 713 | "count": "ignore", 714 | "status": "ignore" 715 | }, 716 | "name": "Test #36" 717 | }, 718 | { 719 | "query": "SELECT os FROM users WHERE age > 17 GROUP BY os LIMIT 5", 720 | "expected": { 721 | "result": [ 722 | { 723 | "os": "linux" 724 | }, 725 | { 726 | "os": "windows" 727 | } 728 | ], 729 | "took": "ignore", 730 | "count": "ignore", 731 | "status": "ignore" 732 | }, 733 | "name": "Test #37" 734 | }, 735 | { 736 | "query": "SELECT city, state FROM users WHERE age > 17 LIMIT 3", 737 | "expected": { 738 | "result": [ 739 | { 740 | "city": "natal", 741 | "state": "rn" 742 | }, 743 | { 744 | "city": "new york", 745 | "state": "ny" 746 | } 747 | ], 748 | "took": "ignore", 749 | "count": "ignore", 750 | "status": "ignore" 751 | }, 752 | "name": "Test #38" 753 | }, 754 | { 755 | "query": "SELECT COUNT(*) FROM users WHERE age >= 18", 756 | "expected": { 757 | "result": [ 758 | { 759 | "COUNT": 2 760 | } 761 | ], 762 | "took": "ignore", 763 | "count": "ignore", 764 | "status": "ignore" 765 | }, 766 | "name": "Test #39" 767 | }, 768 | { 769 | "query": "SELECT os FROM users WHERE age >= 18 GROUP BY os LIMIT 5", 770 | "expected": { 771 | "result": [ 772 | { 773 | "os": "linux" 774 | }, 775 | { 776 | "os": "windows" 777 | } 778 | ], 779 | "took": "ignore", 780 | "count": "ignore", 781 | "status": "ignore" 782 | }, 783 | "name": "Test #40" 784 | }, 785 | { 786 | "query": "SELECT city, state FROM users WHERE age >= 18 LIMIT 3", 787 | "expected": { 788 | "result": [ 789 | { 790 | "city": "natal", 791 | "state": "rn" 792 | }, 793 | { 794 | "city": "new york", 795 | "state": "ny" 796 | } 797 | ], 798 | "took": "ignore", 799 | "count": "ignore", 800 | "status": "ignore" 801 | }, 802 | "name": "Test #41" 803 | }, 804 | { 805 | "query": "SELECT COUNT(*) FROM users WHERE age < 25", 806 | "expected": { 807 | "result": [ 808 | { 809 | "COUNT": 3 810 | } 811 | ], 812 | "took": "ignore", 813 | "count": "ignore", 814 | "status": "ignore" 815 | }, 816 | "name": "Test #42" 817 | }, 818 | { 819 | "query": "SELECT os FROM users WHERE age < 25 GROUP BY os LIMIT 5", 820 | "expected": { 821 | "result": [ 822 | { 823 | "os": "linux" 824 | }, 825 | { 826 | "os": "mac" 827 | }, 828 | { 829 | "os": "windows" 830 | } 831 | ], 832 | "took": "ignore", 833 | "count": "ignore", 834 | "status": "ignore" 835 | }, 836 | "name": "Test #43" 837 | }, 838 | { 839 | "query": "SELECT city, state FROM users WHERE age < 25 LIMIT 3", 840 | "expected": { 841 | "result": [ 842 | { 843 | "city": "salvador", 844 | "state": "ba" 845 | }, 846 | { 847 | "city": "natal", 848 | "state": "rn" 849 | }, 850 | { 851 | "city": "new york", 852 | "state": "ny" 853 | } 854 | ], 855 | "took": "ignore", 856 | "count": "ignore", 857 | "status": "ignore" 858 | }, 859 | "name": "Test #44" 860 | }, 861 | { 862 | "query": "SELECT COUNT(*) FROM users WHERE age <= 24", 863 | "expected": { 864 | "result": [ 865 | { 866 | "COUNT": 3 867 | } 868 | ], 869 | "took": "ignore", 870 | "count": "ignore", 871 | "status": "ignore" 872 | }, 873 | "name": "Test #45" 874 | }, 875 | { 876 | "query": "SELECT os FROM users WHERE age <= 24 GROUP BY os LIMIT 5", 877 | "expected": { 878 | "result": [ 879 | { 880 | "os": "linux" 881 | }, 882 | { 883 | "os": "mac" 884 | }, 885 | { 886 | "os": "windows" 887 | } 888 | ], 889 | "took": "ignore", 890 | "count": "ignore", 891 | "status": "ignore" 892 | }, 893 | "name": "Test #46" 894 | }, 895 | { 896 | "query": "SELECT city, state FROM users WHERE age <= 24 LIMIT 3", 897 | "expected": { 898 | "result": [ 899 | { 900 | "city": "salvador", 901 | "state": "ba" 902 | }, 903 | { 904 | "city": "natal", 905 | "state": "rn" 906 | }, 907 | { 908 | "city": "new york", 909 | "state": "ny" 910 | } 911 | ], 912 | "took": "ignore", 913 | "count": "ignore", 914 | "status": "ignore" 915 | }, 916 | "name": "Test #47" 917 | }, 918 | { 919 | "query": "SELECT os FROM users ORDER BY os ASC LIMIT 3", 920 | "expected": { 921 | "result": [ 922 | { 923 | "os": "linux" 924 | }, 925 | { 926 | "os": "mac" 927 | }, 928 | { 929 | "os": "windows" 930 | } 931 | ], 932 | "took": "ignore", 933 | "count": "ignore", 934 | "status": "ignore" 935 | }, 936 | "name": "Test #48" 937 | }, 938 | { 939 | "query": "SELECT COUNT(*) FROM users WHERE [clicks.value] = 'Pay Now' AND [clicks.date] BETWEEN '2017-05-14' AND '2017-05-29'", 940 | "expected": { 941 | "result": [ 942 | { 943 | "COUNT": 1 944 | } 945 | ], 946 | "took": "ignore", 947 | "count": "ignore", 948 | "status": "ignore" 949 | }, 950 | "name": "Test #49" 951 | }, 952 | { 953 | "query": "SELECT city, state FROM users WHERE state = 'NY' OR state = 'CA' LIMIT 3 ", 954 | "expected": { 955 | "result": [ 956 | { 957 | "city": "new york", 958 | "state": "ny" 959 | } 960 | ], 961 | "took": "ignore", 962 | "count": "ignore", 963 | "status": "ignore" 964 | }, 965 | "name": "Test #50" 966 | }, 967 | { 968 | "query": "SELECT city, state FROM users", 969 | "expected": { 970 | "result": [ 971 | { 972 | "city": "salvador", 973 | "state": "ba" 974 | }, 975 | { 976 | "city": "natal", 977 | "state": "rn" 978 | }, 979 | { 980 | "city": "new york", 981 | "state": "ny" 982 | } 983 | ], 984 | "took": "ignore", 985 | "count": "ignore", 986 | "status": "ignore" 987 | }, 988 | "name": "Test #51" 989 | }, 990 | { 991 | "query": "SELECT * FROM users WHERE state='NY' OR state='CA' LIMIT 3", 992 | "expected": { 993 | "result": [ 994 | { 995 | "age": 19, 996 | "browser": "firefox", 997 | "city": "new york", 998 | "country": "usa", 999 | "entity-id": "user1@slicingdice.com", 1000 | "gender": "female", 1001 | "name": "joao", 1002 | "os": "linux", 1003 | "state": "ny", 1004 | "score": 1.0 1005 | } 1006 | ], 1007 | "took": "ignore", 1008 | "count": "ignore", 1009 | "status": "ignore" 1010 | }, 1011 | "name": "Test #52" 1012 | }, 1013 | { 1014 | "query": "SELECT city, state, AVG([purchased-products.value]) FROM users WHERE state = 'NY' OR state='CA' HAVING [purchased-products.date] BETWEEN '2017-05-24' AND '2017-05-30'", 1015 | "expected": { 1016 | "result": [ 1017 | { 1018 | "AVG": 5.0, 1019 | "city": "new york", 1020 | "state": "ny" 1021 | } 1022 | ], 1023 | "took": "ignore", 1024 | "count": "ignore", 1025 | "status": "ignore" 1026 | }, 1027 | "name": "Test #53" 1028 | }, 1029 | { 1030 | "query": "SELECT city, state, score FROM users", 1031 | "expected": { 1032 | "result": [ 1033 | { 1034 | "score": 1.0, 1035 | "city": "salvador", 1036 | "state": "ba" 1037 | }, 1038 | { 1039 | "score": 1.0, 1040 | "city": "natal", 1041 | "state": "rn" 1042 | }, 1043 | { 1044 | "score": 1.0, 1045 | "city": "new york", 1046 | "state": "ny" 1047 | } 1048 | ], 1049 | "took": "ignore", 1050 | "count": "ignore", 1051 | "status": "ignore" 1052 | }, 1053 | "name": "Test #55" 1054 | }, 1055 | { 1056 | "query": "SELECT * FROM users WHERE state='NY' OR state='CA' LIMIT 3", 1057 | "expected": { 1058 | "result": [ 1059 | { 1060 | "age": 19, 1061 | "browser": "firefox", 1062 | "city": "new york", 1063 | "country": "usa", 1064 | "entity-id": "user1@slicingdice.com", 1065 | "gender": "female", 1066 | "name": "joao", 1067 | "os": "linux", 1068 | "state": "ny", 1069 | "score": 1.0 1070 | } 1071 | ], 1072 | "took": "ignore", 1073 | "count": "ignore", 1074 | "status": "ignore" 1075 | }, 1076 | "name": "Test #56" 1077 | }, 1078 | { 1079 | "query": "SELECT city, state, AVG([purchased-products.value]), score FROM users WHERE state = 'NY' OR state='CA' HAVING [purchased-products.date] BETWEEN '2017-05-24' AND '2017-05-30'", 1080 | "expected": { 1081 | "result": [ 1082 | { 1083 | "score": 1.0, 1084 | "AVG": 5.0, 1085 | "city": "new york", 1086 | "state": "ny" 1087 | } 1088 | ], 1089 | "took": "ignore", 1090 | "count": "ignore", 1091 | "status": "ignore" 1092 | }, 1093 | "name": "Test #57" 1094 | }, 1095 | { 1096 | "query": "SELECT name, country, age FROM users WHERE gender = 'female' ORDER BY name DESC, age DESC", 1097 | "expected": { 1098 | "result": [ 1099 | { 1100 | "country": "usa", 1101 | "name": "joao", 1102 | "age": 19 1103 | }, 1104 | { 1105 | "country": "brazil", 1106 | "name": "gabriela", 1107 | "age": 17 1108 | } 1109 | ], 1110 | "took": "ignore", 1111 | "count": "ignore", 1112 | "status": "ignore" 1113 | }, 1114 | "name": "Test #58" 1115 | }, 1116 | { 1117 | "query": "SELECT COUNT(*) FROM users WHERE name LIKE '%joao%'", 1118 | "expected": { 1119 | "result": [ 1120 | { 1121 | "COUNT": 1 1122 | } 1123 | ], 1124 | "took": "ignore", 1125 | "count": "ignore", 1126 | "status": "ignore" 1127 | }, 1128 | "name": "Test #59" 1129 | }, 1130 | { 1131 | "query": "SELECT COUNT(*) FROM users WHERE name LIKE 'joao%'", 1132 | "expected": { 1133 | "result": [ 1134 | { 1135 | "COUNT": 1 1136 | } 1137 | ], 1138 | "took": "ignore", 1139 | "count": "ignore", 1140 | "status": "ignore" 1141 | }, 1142 | "name": "Test #60" 1143 | }, 1144 | { 1145 | "query": "SELECT COUNT(*) FROM users WHERE name LIKE '%joao'", 1146 | "expected": { 1147 | "result": [ 1148 | { 1149 | "COUNT": 1 1150 | } 1151 | ], 1152 | "took": "ignore", 1153 | "count": "ignore", 1154 | "status": "ignore" 1155 | }, 1156 | "name": "Test #61" 1157 | }, 1158 | { 1159 | "query": "SELECT COUNT(*) FROM users WHERE name NOT LIKE '%joao%'", 1160 | "expected": { 1161 | "result": [ 1162 | { 1163 | "COUNT": 2 1164 | } 1165 | ], 1166 | "took": "ignore", 1167 | "count": "ignore", 1168 | "status": "ignore" 1169 | }, 1170 | "name": "Test #62" 1171 | }, 1172 | { 1173 | "query": "SELECT age FROM users WHERE name LIKE '%joao%' LIMIT 100", 1174 | "expected": { 1175 | "result": [ 1176 | { 1177 | "age": 19 1178 | } 1179 | ], 1180 | "took": "ignore", 1181 | "count": "ignore", 1182 | "status": "ignore" 1183 | }, 1184 | "name": "Test #63" 1185 | }, 1186 | { 1187 | "query": "SELECT age, score FROM users WHERE name LIKE '%joao%' LIMIT 100", 1188 | "expected": { 1189 | "result": [ 1190 | { 1191 | "score": 1.0, 1192 | "age": 19 1193 | } 1194 | ], 1195 | "took": "ignore", 1196 | "count": "ignore", 1197 | "status": "ignore" 1198 | }, 1199 | "name": "Test #64" 1200 | }, 1201 | { 1202 | "query": "SELECT COUNT(*) FROM users WHERE [visited-page-events.value] LIKE 'visit%' AND [visited-page-events.date] BETWEEN '2017-05-25' AND '2017-06-08'", 1203 | "expected": { 1204 | "result": [ 1205 | { 1206 | "COUNT": 1 1207 | } 1208 | ], 1209 | "took": "ignore", 1210 | "count": "ignore", 1211 | "status": "ignore" 1212 | }, 1213 | "name": "Test #65" 1214 | }, 1215 | { 1216 | "query": "SELECT COUNT(*) FROM users WHERE [visited-page-events.value] LIKE '%visit%' AND [visited-page-events.date] BETWEEN '2017-05-25' AND '2017-06-08'", 1217 | "expected": { 1218 | "result": [ 1219 | { 1220 | "COUNT": 1 1221 | } 1222 | ], 1223 | "took": "ignore", 1224 | "count": "ignore", 1225 | "status": "ignore" 1226 | }, 1227 | "name": "Test #66" 1228 | }, 1229 | { 1230 | "query": "SELECT COUNT(*) FROM users WHERE [visited-page-events.value] NOT LIKE '%visit%' AND [visited-page-events.date] BETWEEN '2017-05-25' AND '2017-06-08'", 1231 | "expected": { 1232 | "result": [ 1233 | { 1234 | "COUNT": 2 1235 | } 1236 | ], 1237 | "took": "ignore", 1238 | "count": "ignore", 1239 | "status": "ignore" 1240 | }, 1241 | "name": "Test #67" 1242 | }, 1243 | { 1244 | "query": "SELECT os FROM users WHERE os LIKE 'linu%' GROUP BY os LIMIT 2", 1245 | "expected": { 1246 | "result": [ 1247 | { 1248 | "os": "linux" 1249 | } 1250 | ], 1251 | "took": "ignore", 1252 | "count": "ignore", 1253 | "status": "ignore" 1254 | }, 1255 | "name": "Test #68" 1256 | }, 1257 | { 1258 | "query": "SELECT os FROM users WHERE os LIKE '%linu' GROUP BY os LIMIT 2", 1259 | "expected": { 1260 | "result": [], 1261 | "took": "ignore", 1262 | "count": "ignore", 1263 | "status": "ignore" 1264 | }, 1265 | "name": "Test #69" 1266 | }, 1267 | { 1268 | "query": "SELECT os FROM users WHERE os NOT LIKE '%linu%' GROUP BY os LIMIT 2", 1269 | "expected": { 1270 | "result": [ 1271 | { 1272 | "os": "mac" 1273 | }, 1274 | { 1275 | "os": "windows" 1276 | } 1277 | ], 1278 | "took": "ignore", 1279 | "count": "ignore", 1280 | "status": "ignore" 1281 | }, 1282 | "name": "Test #70" 1283 | }, 1284 | { 1285 | "query": "SELECT browser FROM users WHERE os LIKE 'win%' GROUP BY browser LIMIT 2", 1286 | "expected": { 1287 | "result": [ 1288 | { 1289 | "browser": "chrome" 1290 | } 1291 | ], 1292 | "took": "ignore", 1293 | "count": "ignore", 1294 | "status": "ignore" 1295 | }, 1296 | "name": "Test #71" 1297 | }, 1298 | { 1299 | "query": "SELECT browser FROM users WHERE os LIKE '%win%' GROUP BY browser LIMIT 2", 1300 | "expected": { 1301 | "result": [ 1302 | { 1303 | "browser": "chrome" 1304 | } 1305 | ], 1306 | "took": "ignore", 1307 | "count": "ignore", 1308 | "status": "ignore" 1309 | }, 1310 | "name": "Test #72" 1311 | }, 1312 | { 1313 | "query": "SELECT COUNT(*) FROM users WHERE [visited-page-events.value] NOT LIKE '%visit%' AND [visited-page-events.date] BETWEEN '2017-05-25' AND '2017-06-08' AND (age = 21 OR name LIKE 'j%')", 1314 | "expected": { 1315 | "result": [ 1316 | { 1317 | "COUNT": 1 1318 | } 1319 | ], 1320 | "took": "ignore", 1321 | "count": "ignore", 1322 | "status": "ignore" 1323 | }, 1324 | "name": "Test #73" 1325 | }, 1326 | { 1327 | "query": "SELECT COUNT(*) FROM users WHERE [visited-page-events.value] NOT LIKE '%visit%' AND [visited-page-events.date] BETWEEN '2017-05-25' AND '2017-06-08' AND [clicks.value] = 'Pay Now' AND [clicks.date] BETWEEN '2017-05-25' AND '2017-06-08'", 1328 | "expected": { 1329 | "result": [ 1330 | { 1331 | "COUNT": 0 1332 | } 1333 | ], 1334 | "took": "ignore", 1335 | "count": "ignore", 1336 | "status": "ignore" 1337 | }, 1338 | "name": "Test #74" 1339 | }, 1340 | { 1341 | "query": "SELECT name, age, MAX(age), AVG([purchased-products.value]) FROM users WHERE [purchased-products.date] BETWEEN '2017-05-22' AND '2017-06-08' LIMIT 100", 1342 | "expected": { 1343 | "result": [ 1344 | { 1345 | "AVG": 25.0, 1346 | "MAX": 17, 1347 | "name": "gabriela", 1348 | "age": 17 1349 | }, 1350 | { 1351 | "AVG": 10.0, 1352 | "MAX": 21, 1353 | "name": "eduardo", 1354 | "age": 21 1355 | }, 1356 | { 1357 | "AVG": 5.0, 1358 | "MAX": 19, 1359 | "name": "joao", 1360 | "age": 19 1361 | } 1362 | ], 1363 | "took": "ignore", 1364 | "count": "ignore", 1365 | "status": "ignore" 1366 | }, 1367 | "name": "Test #78" 1368 | }, 1369 | { 1370 | "query": "SELECT COUNT([entity-id]) FROM users WHERE [clicks.value] = 'Add to Cart' AND [clicks.date] BETWEEN '2017-05-14T00:00:00Z' AND '2017-05-28T00:00:00Z' HAVING COUNT([clicks.value]) > 5\n", 1371 | "expected": { 1372 | "result": [ 1373 | { 1374 | "COUNT": 0 1375 | } 1376 | ], 1377 | "took": "ignore", 1378 | "count": "ignore", 1379 | "status": "ignore" 1380 | }, 1381 | "name": "Test #79" 1382 | }, 1383 | { 1384 | "query": "SELECT COUNT([entity-id]) FROM users WHERE [clicks.value] = 'pay now' AND [clicks.date] BETWEEN '2017-05-14T00:00:00Z' AND '2017-05-28T00:00:00Z' AND [clicks.value] = 'add to cart' AND [clicks.date] BETWEEN '2017-05-14T00:00:00Z' AND '2017-05-28T00:00:00Z' HAVING COUNT([clicks.value]) > 4", 1385 | "expected": { 1386 | "result": [ 1387 | { 1388 | "COUNT": 0 1389 | } 1390 | ], 1391 | "took": "ignore", 1392 | "count": "ignore", 1393 | "status": "ignore" 1394 | }, 1395 | "name": "Test #80" 1396 | }, 1397 | { 1398 | "query": "SELECT jobs.* FROM jobs WHERE DATEPART('dd', jobs.admission) = '17'", 1399 | "expected": { 1400 | "result": [ 1401 | { 1402 | "score": 1.0, 1403 | "admission": "2017-07-17", 1404 | "salary": 2350.5, 1405 | "user": "user1@slicingdice.com", 1406 | "age": 19, 1407 | "entity-id": "1" 1408 | } 1409 | ], 1410 | "took": "ignore", 1411 | "count": "ignore", 1412 | "status": "ignore" 1413 | }, 1414 | "name": "Test #81" 1415 | }, 1416 | { 1417 | "query": "SELECT jobs.* FROM jobs WHERE DATEPART('dd', jobs.admission) = '18'", 1418 | "expected": { 1419 | "result": [], 1420 | "took": "ignore", 1421 | "count": "ignore", 1422 | "status": "ignore" 1423 | }, 1424 | "name": "Test #82" 1425 | }, 1426 | { 1427 | "query": "SELECT jobs.* FROM jobs WHERE DATEPART('mm', jobs.admission) = '7'", 1428 | "expected": { 1429 | "result": [ 1430 | { 1431 | "score": 1.0, 1432 | "admission": "2017-07-17", 1433 | "salary": 2350.500000, 1434 | "user": "user1@slicingdice.com", 1435 | "age": 19, 1436 | "entity-id": "1" 1437 | } 1438 | ], 1439 | "took": "ignore", 1440 | "count": "ignore", 1441 | "status": "ignore" 1442 | }, 1443 | "name": "Test #83" 1444 | }, 1445 | { 1446 | "query": "SELECT jobs.* FROM jobs WHERE DATEPART('mm', jobs.admission) = '8'", 1447 | "expected": { 1448 | "result": [], 1449 | "took": "ignore", 1450 | "count": "ignore", 1451 | "status": "ignore" 1452 | }, 1453 | "name": "Test #84" 1454 | }, 1455 | { 1456 | "query": "SELECT jobs.* FROM jobs WHERE DATEPART('yy', jobs.admission) = '2017'", 1457 | "expected": { 1458 | "result": [ 1459 | { 1460 | "score": 1.0, 1461 | "admission": "2017-07-17", 1462 | "salary": 2350.500000, 1463 | "user": "user1@slicingdice.com", 1464 | "age": 19, 1465 | "entity-id": "1" 1466 | } 1467 | ], 1468 | "took": "ignore", 1469 | "count": "ignore", 1470 | "status": "ignore" 1471 | }, 1472 | "name": "Test #85" 1473 | }, 1474 | { 1475 | "query": "SELECT jobs.* FROM jobs WHERE DATEPART('mm', jobs.admission) = '2017'", 1476 | "expected": { 1477 | "result": [], 1478 | "took": "ignore", 1479 | "count": "ignore", 1480 | "status": "ignore" 1481 | }, 1482 | "name": "Test #86" 1483 | }, 1484 | { 1485 | "query": "SELECT CASE WHEN age > 19 THEN 'adult' ELSE 'child' END FROM users", 1486 | "expected": { 1487 | "result": [ 1488 | { 1489 | "CASE": "child" 1490 | }, 1491 | { 1492 | "CASE": "adult" 1493 | }, 1494 | { 1495 | "CASE": "child" 1496 | } 1497 | ], 1498 | "took": "ignore", 1499 | "count": "ignore", 1500 | "status": "ignore" 1501 | }, 1502 | "name": "Test #87" 1503 | } 1504 | ] -------------------------------------------------------------------------------- /tests_and_examples/examples/sql_insert.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "user1@slicingdice.com": { 4 | "gender": "female", 5 | "state": "NY", 6 | "city": "New York", 7 | "country": "USA", 8 | "age": 19, 9 | "dimension": "users", 10 | "name": "joao", 11 | "browser": "firefox", 12 | "os": "Linux", 13 | "clicks": [ 14 | { 15 | "value": "Pay Now", 16 | "date": "2017-05-28T00:00:00Z" 17 | } 18 | ], 19 | "purchase-values": [ 20 | { 21 | "value": 10.5, 22 | "date": "2017-05-28T00:00:00Z" 23 | } 24 | ], 25 | "purchased-products": [ 26 | { 27 | "value": 5, 28 | "date": "2017-05-28T00:00:00Z" 29 | } 30 | ], 31 | "visited-page-events": [ 32 | { 33 | "value": "visited-page", 34 | "date": "2017-05-27T00:00:00Z" 35 | } 36 | ] 37 | }, 38 | "user2@slicingdice.com": { 39 | "gender": "male", 40 | "state": "RN", 41 | "city": "Natal", 42 | "country": "Brazil", 43 | "age": 21, 44 | "dimension": "users", 45 | "name": "eduardo", 46 | "browser": "chrome", 47 | "os": "Windows", 48 | "clicks": [ 49 | { 50 | "value": "Buy", 51 | "date": "2017-05-23T00:00:00Z" 52 | } 53 | ], 54 | "purchase-values": [ 55 | { 56 | "value": 15.45, 57 | "date": "2017-05-24T00:00:00Z" 58 | } 59 | ], 60 | "purchased-products": [ 61 | { 62 | "value": 10, 63 | "date": "2017-05-22T00:00:00Z" 64 | } 65 | ], 66 | "visited-page-events": [ 67 | { 68 | "value": "index", 69 | "date": "2017-05-27T00:00:00Z" 70 | } 71 | ] 72 | }, 73 | "user3@slicingdice.com": { 74 | "gender": "female", 75 | "state": "BA", 76 | "city": "Salvador", 77 | "country": "Brazil", 78 | "age": 17, 79 | "dimension": "users", 80 | "name": "gabriela", 81 | "browser": "safari", 82 | "os": "mac", 83 | "clicks": [ 84 | { 85 | "value": "Buy", 86 | "date": "2017-05-28T00:00:00Z" 87 | } 88 | ], 89 | "purchase-values": [ 90 | { 91 | "value": 7.654, 92 | "date": "2017-05-28T00:00:00Z" 93 | } 94 | ], 95 | "purchased-products": [ 96 | { 97 | "value": 25, 98 | "date": "2017-05-28T00:00:00Z" 99 | } 100 | ], 101 | "visited-page-events": [ 102 | { 103 | "value": "login", 104 | "date": "2017-05-27T00:00:00Z" 105 | } 106 | ] 107 | }, 108 | "auto-create": [ 109 | "dimension", 110 | "column" 111 | ] 112 | }, 113 | { 114 | "1": { 115 | "user": "user1@slicingdice.com", 116 | "salary": 2350.50, 117 | "age": 19, 118 | "admission": "2017-07-17", 119 | "dimension": "jobs" 120 | }, 121 | "auto-create": [ 122 | "dimension", 123 | "column" 124 | ] 125 | }, 126 | { 127 | "1": { 128 | "somecollumn": 1, 129 | "str": "john", 130 | "dec": 105.5, 131 | "ts": { 132 | "value": "doe", 133 | "date": "2017-08-18T00:00:00Z" 134 | }, 135 | "tsd": { 136 | "value": 15.10, 137 | "date": "2017-08-18T00:00:00Z" 138 | }, 139 | "tsi": { 140 | "value": 15, 141 | "date": "2017-08-18T00:00:00Z" 142 | }, 143 | "dimension": "inserttest" 144 | }, 145 | "auto-create": [ 146 | "dimension", 147 | "column" 148 | ] 149 | } 150 | ] -------------------------------------------------------------------------------- /tests_and_examples/examples/update.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Test for an UPDATE on a specific column.", 4 | "description": "In this test we will update a single column from a record.", 5 | "columns": [ 6 | { 7 | "type": "string", 8 | "cardinality": "high", 9 | "storage": "latest-value", 10 | "name": "string_test_column", 11 | "api-name": "string-test-column", 12 | "dimension": "delete-test" 13 | }, 14 | { 15 | "storage": "latest-value", 16 | "type": "boolean", 17 | "name": "boolean_test_column", 18 | "api-name": "boolean-test-column", 19 | "dimension": "delete-test" 20 | }, 21 | { 22 | "storage": "latest-value", 23 | "type": "integer", 24 | "name": "integer_test_column", 25 | "api-name": "integer-test-column", 26 | "dimension": "delete-test" 27 | }, 28 | { 29 | "storage": "latest-value", 30 | "type": "datetime", 31 | "name": "datetime_test_column", 32 | "api-name": "datetime-test-column", 33 | "dimension": "delete-test" 34 | } 35 | ], 36 | "insert": { 37 | "Entity1": { 38 | "string-test-column": "value:matched_value", 39 | "boolean-test-column": "true", 40 | "integer-test-column": 1000001, 41 | "datetime-test-column": "2018-01-21T00:50:00Z", 42 | "dimension": "delete-test" 43 | }, 44 | "Entity2": { 45 | "string-test-column": "value:matched_value", 46 | "boolean-test-column": "true", 47 | "integer-test-column": 1000001, 48 | "datetime-test-column": "2018-01-21T00:50:00Z", 49 | "dimension": "delete-test" 50 | }, 51 | "Entity3": { 52 | "string-test-column": "value:matched_value", 53 | "boolean-test-column": "true", 54 | "integer-test-column": 1000001, 55 | "datetime-test-column": "2018-01-21T00:50:00Z", 56 | "dimension": "delete-test" 57 | }, 58 | "Entity4": { 59 | "string-test-column": "value:garbage_value", 60 | "boolean-test-column": "true", 61 | "integer-test-column": 1000001, 62 | "datetime-test-column": "2018-01-21T00:50:00Z", 63 | "dimension": "delete-test" 64 | }, 65 | "Entity5": { 66 | "string-test-column": "value:garbage_value", 67 | "boolean-test-column": "true", 68 | "integer-test-column": 1000001, 69 | "datetime-test-column": "2018-01-21T00:50:00Z", 70 | "dimension": "delete-test" 71 | }, 72 | "Entity6": { 73 | "string-test-column": "value:garbage_value", 74 | "boolean-test-column": "true", 75 | "integer-test-column": 1000001, 76 | "datetime-test-column": "2018-01-21T00:50:00Z", 77 | "dimension": "delete-test" 78 | } 79 | }, 80 | "additional_operation": { 81 | "query":[ 82 | { 83 | "string-test-column":{ 84 | "equals":"value:garbage_value" 85 | } 86 | } 87 | ], 88 | "set":{ 89 | "integer-test-column":2000002 90 | }, 91 | "dimension": "delete-test" 92 | }, 93 | "result_additional": { 94 | "status": "ignore", 95 | "result": { 96 | "updated": 3 97 | }, 98 | "took": "ignore" 99 | }, 100 | "query": { 101 | "query-name": "updated_query", 102 | "query": [ 103 | { 104 | "integer-test-column": { 105 | "equals": 2000002 106 | } 107 | } 108 | ], 109 | "dimension": "delete-test" 110 | }, 111 | "expected": { 112 | "status": "ignore", 113 | "result": { 114 | "updated_query": 3 115 | }, 116 | "took": "ignore" 117 | } 118 | }, 119 | { 120 | "name": "Test for a UPDATE that changes multiple values.", 121 | "description": "In this test we will update a record changing all the 4 values indexed.", 122 | "columns": [ 123 | { 124 | "type": "string", 125 | "cardinality": "high", 126 | "storage": "latest-value", 127 | "name": "string_test_column", 128 | "api-name": "string-test-column", 129 | "dimension": "delete-test" 130 | }, 131 | { 132 | "storage": "latest-value", 133 | "type": "boolean", 134 | "name": "boolean_test_column", 135 | "api-name": "boolean-test-column", 136 | "dimension": "delete-test" 137 | }, 138 | { 139 | "storage": "latest-value", 140 | "type": "integer", 141 | "name": "integer_test_column", 142 | "api-name": "integer-test-column", 143 | "dimension": "delete-test" 144 | }, 145 | { 146 | "storage": "latest-value", 147 | "type": "datetime", 148 | "name": "datetime_test_column", 149 | "api-name": "datetime-test-column", 150 | "dimension": "delete-test" 151 | } 152 | ], 153 | "insert": { 154 | "Entity1": { 155 | "string-test-column": "value:matched_value", 156 | "boolean-test-column": "true", 157 | "integer-test-column": 1000001, 158 | "datetime-test-column": "2018-01-21T00:50:00Z", 159 | "dimension": "delete-test" 160 | }, 161 | "Entity2": { 162 | "string-test-column": "value:matched_value", 163 | "boolean-test-column": "true", 164 | "integer-test-column": 1000001, 165 | "datetime-test-column": "2018-01-21T00:50:00Z", 166 | "dimension": "delete-test" 167 | }, 168 | "Entity3": { 169 | "string-test-column": "value:matched_value", 170 | "boolean-test-column": "true", 171 | "integer-test-column": 1000001, 172 | "datetime-test-column": "2018-01-21T00:50:00Z", 173 | "dimension": "delete-test" 174 | }, 175 | "Entity4": { 176 | "string-test-column": "value:garbage_value", 177 | "boolean-test-column": "true", 178 | "integer-test-column": 1000002, 179 | "datetime-test-column": "2018-01-21T00:50:00Z", 180 | "dimension": "delete-test" 181 | }, 182 | "Entity5": { 183 | "string-test-column": "value:garbage_value", 184 | "boolean-test-column": "true", 185 | "integer-test-column": 1000002, 186 | "datetime-test-column": "2018-01-21T00:50:00Z", 187 | "dimension": "delete-test" 188 | }, 189 | "Entity6": { 190 | "string-test-column": "value:garbage_value", 191 | "boolean-test-column": "true", 192 | "integer-test-column": 1000002, 193 | "datetime-test-column": "2018-01-21T00:50:00Z", 194 | "dimension": "delete-test" 195 | } 196 | }, 197 | "additional_operation": { 198 | "query":[ 199 | { 200 | "string-test-column":{ 201 | "equals":"value:garbage_value" 202 | } 203 | }, 204 | "and", 205 | { 206 | "integer-test-column":{ 207 | "equals":1000002 208 | } 209 | } 210 | ], 211 | "set":{ 212 | "string-test-column": "value:another_garbage_value", 213 | "boolean-test-column": "false", 214 | "integer-test-column": 3000003, 215 | "datetime-test-column": "2018-01-24T10:50:00Z" 216 | }, 217 | "dimension": "delete-test" 218 | }, 219 | "result_additional": { 220 | "status": "ignore", 221 | "result": { 222 | "updated": 3 223 | }, 224 | "took": "ignore" 225 | }, 226 | "query": { 227 | "query-name": "updated", 228 | "query": [ 229 | { 230 | "string-test-column": { 231 | "equals": "value:another_garbage_value" 232 | } 233 | }, 234 | "and", 235 | { 236 | "boolean-test-column": { 237 | "equals": "false" 238 | } 239 | }, 240 | "and", 241 | { 242 | "integer-test-column": { 243 | "equals": 3000003 244 | } 245 | }, 246 | "and", 247 | { 248 | "datetime-test-column": { 249 | "equals": "2018-01-24T10:50:00Z" 250 | } 251 | } 252 | ], 253 | "dimension": "delete-test" 254 | }, 255 | "expected": { 256 | "status": "ignore", 257 | "result": { 258 | "updated": 3 259 | }, 260 | "took": "ignore" 261 | } 262 | } 263 | ] -------------------------------------------------------------------------------- /tests_and_examples/runQueryTests.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tests SlicingDice endpoints. 3 | 4 | This script tests SlicingDice by running tests suites, each composed by: 5 | - Creating columns 6 | - Inserting data 7 | - Querying 8 | - Comparing results 9 | 10 | All tests are stored in JSON files at ./examples named as the query being 11 | tested: 12 | - count_entity.json 13 | - count_event.json 14 | 15 | In order to execute the tests, simply replace API_KEY by the demo API key and 16 | run the script with: 17 | $ node run_tests.js 18 | */ 19 | 20 | "use strict"; 21 | 22 | var SlicingDice = require('../src/slicer.js'); 23 | var errors = require('../src/errors.js'); 24 | var fs = require('fs'); 25 | var async = require('async'); 26 | var https = require('https'); 27 | var sleep = require('sleep'); 28 | 29 | var HAS_FAILED_TESTS = false; 30 | 31 | var perTestInsertion; 32 | 33 | class SlicingDiceTester { 34 | constructor(api_key, verbose=false) { 35 | this.client = new SlicingDice({masterKey: api_key}); 36 | 37 | // Translation table for columns with timestamp 38 | this.columnTranslation = {} 39 | 40 | // Sleep Time in seconds 41 | this.sleepTime = 20; 42 | // Directory containing examples to test 43 | this.path = 'examples/'; 44 | // Examples file format 45 | this.extension = '.json'; 46 | 47 | this.numSuccesses = 0; 48 | this.numFails = 0; 49 | this.failedTests = []; 50 | 51 | this.verbose = verbose; 52 | 53 | this.perTestInsertion; 54 | 55 | this.insertSqlData = false; 56 | 57 | String.prototype.replaceAll = function(search, replacement) { 58 | var target = this; 59 | return target.replace(new RegExp(search, 'g'), replacement); 60 | }; 61 | String.prototype.format = function() { 62 | var args = arguments; 63 | return this.replace(/{(\d+)}/g, function(match, number) { 64 | return typeof args[number] != 'undefined' 65 | ? args[number] 66 | : match 67 | ; 68 | }); 69 | }; 70 | } 71 | 72 | /* Run all tests for a determined query type 73 | * 74 | * @param (string) queryType - the type of the query to test 75 | */ 76 | runTests(queryType, callback) { 77 | let testData = this.loadTestData(queryType); 78 | let numTests = testData.length; 79 | let result; 80 | 81 | this.perTestInsertion = testData[0].hasOwnProperty("insert"); 82 | 83 | if (!this.perTestInsertion && this.insertSqlData) { 84 | let insertData = this.loadTestData(queryType, "_insert"); 85 | for (let i = 0; i < insertData.length; i++) { 86 | async.series([ 87 | (callback) => { 88 | this.client.insert(insertData[i], false).then(() => { 89 | // Wait a few seconds so the data can be inserted by SlicingDice 90 | callback(); 91 | }, (err) => { 92 | callback(); 93 | }); 94 | } 95 | ]); 96 | } 97 | sleep.sleep(this.sleepTime); 98 | } 99 | 100 | let tasks = []; 101 | for(let i = 0; i < numTests; i++){ 102 | let _queryType = queryType; 103 | tasks.push(((i) => (callback) => { 104 | try { 105 | let test = testData[i]; 106 | this._emptyColumnTranslation(); 107 | console.log("({0}/{1}) Executing test \"{2}\"".format(i + 1, numTests, test['name'])); 108 | if("description" in test){ 109 | console.log(" Description: {0}".format(test['description'])); 110 | } 111 | 112 | console.log(" Query type: {0}".format(queryType)); 113 | 114 | if (this.perTestInsertion) { 115 | async.series([ 116 | (callback) => { 117 | this.createColumns(test, callback); 118 | }, 119 | (callback) => { 120 | this.insertData(test, callback); 121 | }, 122 | (callback) => { 123 | this._runAdditionalOperations(queryType, test, callback); 124 | } 125 | ], callback); 126 | } else { 127 | async.series([ 128 | (callback) => { 129 | this.executeQuery(queryType, test, callback); 130 | } 131 | ], callback); 132 | } 133 | } catch (err) { 134 | callback(); 135 | } 136 | })(i)); 137 | } 138 | async.series(tasks, callback); 139 | } 140 | 141 | // Method used to run delete and update operations, this operations 142 | // are executed before the query and the result comparison 143 | _runAdditionalOperations(queryType, test, callback) { 144 | if (queryType === "delete" || queryType === "update") { 145 | let queryData = this._translateColumnNames(test['additional_operation']); 146 | 147 | if (queryType == "delete") { 148 | console.log(" Deleting"); 149 | } else { 150 | console.log(" Updating"); 151 | } 152 | 153 | if (this.verbose){ 154 | console.log(" - {0}".format(queryData)); 155 | } 156 | 157 | if (queryType == "delete") { 158 | async.series([(callback) => { 159 | this.client.delete(queryData).then((resp) => { 160 | this.compareResultAndMakeQuery('count_entity', test, callback, resp).then((resp) => { 161 | callback(); 162 | }, (err) => { 163 | throw "An error occurred"; 164 | }); 165 | }, (err) => { 166 | throw "An error occurred"; 167 | }) 168 | }], callback); 169 | } else if (queryType == "update") { 170 | async.series([(callback) => { 171 | this.client.update(queryData).then((resp) => { 172 | this.compareResultAndMakeQuery('count_entity', test, callback, resp).then((resp) => { 173 | callback(); 174 | }, (err) => { 175 | throw "An error occurred"; 176 | }); 177 | }, (err) => { 178 | throw "An error occurred"; 179 | }) 180 | }], callback); 181 | } 182 | } else { 183 | async.series([(callback) => { 184 | this.executeQuery(queryType, test, callback); 185 | }], callback); 186 | } 187 | } 188 | 189 | compareResultAndMakeQuery(queryType, test, callback, result) { 190 | async.series([(callback) => { 191 | let expected = this._translateColumnNames(test['result_additional']); 192 | for(var key in expected) { 193 | let value = expected[key]; 194 | if (value === 'ignore') { 195 | continue; 196 | } 197 | 198 | if (!this.compareJson(expected[key], result[key])){ 199 | this.numFails += 1; 200 | this.failedTests.push(test['name']); 201 | 202 | console.log(" Expected: \"{0}\": {1}".format(key, JSON.stringify(expected[key]))); 203 | console.log(" Result: \"{0}\": {1}".format(key, JSON.stringify(result[key]))); 204 | console.log(" Status: Failed\n"); 205 | this.updateResult(); 206 | return; 207 | } 208 | 209 | this.numSuccesses += 1; 210 | 211 | console.log(' Status: Passed\n'); 212 | this.updateResult(); 213 | } 214 | 215 | this.executeQuery(queryType, test, callback); 216 | }], callback); 217 | } 218 | 219 | // Erase columnTranslation object 220 | _emptyColumnTranslation(){ 221 | this.columnTranslation = {}; 222 | } 223 | 224 | // Load test data from examples files 225 | loadTestData(queryType, suffix = "") { 226 | let filename = this.path + queryType + suffix + this.extension; 227 | return JSON.parse(fs.readFileSync(filename)); 228 | } 229 | 230 | /* Create columns on Slicing Dice API 231 | * 232 | * @param (array) test - the test data containing the column to create 233 | */ 234 | createColumns(test, callback){ 235 | let isSingular = test['columns'].length == 1; 236 | let column_or_columns; 237 | if (isSingular){ 238 | column_or_columns = 'column'; 239 | } else{ 240 | column_or_columns = 'columns'; 241 | } 242 | console.log(" Creating {0} {1}".format(test['columns'].length, column_or_columns)); 243 | 244 | let tasks = []; 245 | for(var data in test['columns']) { 246 | let column = test['columns'][data]; 247 | this._appendTimestampToColumnName(column); 248 | tasks.push((callback) => { 249 | this.client.createColumn(column).then((resp) => { 250 | callback(); 251 | }, (err) => { 252 | this.compareResult(test, err); 253 | callback(); 254 | }); 255 | }); 256 | if (this.verbose){ 257 | console.log(" - {0}".format(column['api-name'])); 258 | } 259 | } 260 | 261 | async.series(tasks, callback); 262 | } 263 | 264 | /* Append integer timestamp to column name 265 | * 266 | * This technique allows the same test suite to be executed over and over 267 | * again, since each execution will use different column names. 268 | * 269 | * @param (array) column - array containing column name 270 | */ 271 | _appendTimestampToColumnName(column){ 272 | let oldName = '"{0}"'.format(column['api-name']); 273 | 274 | let timestamp = this._getTimestamp(); 275 | column['name'] += timestamp 276 | column['api-name'] += timestamp 277 | let newName = '"{0}"'.format(column['api-name']) 278 | 279 | this.columnTranslation[oldName] = newName 280 | } 281 | 282 | // Get actual timestamp in string format 283 | _getTimestamp(){ 284 | return new Date().getTime().toString(); 285 | } 286 | 287 | /* Insert data on Slicing Dice API 288 | * 289 | * @param (array) test - the test data containing the data to insert on Slicing Dice API 290 | */ 291 | insertData(test, callback) { 292 | let isSingular = test['insert'].length == 1; 293 | let column_or_columns; 294 | if (isSingular){ 295 | column_or_columns = 'entity'; 296 | } else{ 297 | column_or_columns = 'entities'; 298 | } 299 | console.log(" Inserting {0} {1}".format(Object.keys(test['insert']).length, column_or_columns)); 300 | 301 | let insertData = this._translateColumnNames(test['insert']); 302 | 303 | if (this.verbose){ 304 | console.log(insertData); 305 | } 306 | 307 | this.client.insert(insertData, false).then(() => { 308 | // Wait a few seconds so the data can be inserted by SlicingDice 309 | sleep.sleep(this.sleepTime); 310 | callback(); 311 | }, (err) => { 312 | this.compareResult(test, err); 313 | callback(); 314 | }); 315 | } 316 | 317 | /* Execute query on Slicing Dice API 318 | * 319 | * @param (string) queryType - the type of the query to send 320 | * @param (string) test - the test data containing the data to query on Slicing Dice API 321 | */ 322 | executeQuery(queryType, test, callback) { 323 | let result; 324 | let queryData; 325 | if (this.perTestInsertion) { 326 | queryData = this._translateColumnNames(test['query']); 327 | } else { 328 | queryData = test['query']; 329 | } 330 | console.log(' Querying'); 331 | 332 | if (this.verbose){ 333 | console.log(' - ' + JSON.stringify(queryData)); 334 | } 335 | 336 | var queryTypeMethodMap = { 337 | 'count_entity': 'countEntity', 338 | 'count_event': 'countEvent', 339 | 'top_values': 'topValues', 340 | 'aggregation': 'aggregation', 341 | 'result': 'result', 342 | 'score': 'score', 343 | 'sql': 'sql' 344 | }; 345 | 346 | this.client[queryTypeMethodMap[queryType]](queryData).then((resp) =>{ 347 | this.compareResult(test, resp); 348 | callback(); 349 | }, (err) =>{ 350 | this.compareResult(test, err); 351 | callback(err); 352 | }) 353 | } 354 | 355 | /* Translate column name to match column name with timestamp 356 | * 357 | * @param (array) jsonData - the json to translate the column name 358 | */ 359 | _translateColumnNames(jsonData){ 360 | let dataString = JSON.stringify(jsonData); 361 | for(var oldName in this.columnTranslation){ 362 | let newName = this.columnTranslation[oldName]; 363 | dataString = dataString.replaceAll(oldName, newName); 364 | } 365 | return JSON.parse(dataString); 366 | } 367 | 368 | /* Compare the result received from Slicing Dice API and the expected 369 | * 370 | * @param (array) test - the data expected 371 | * @param (array) result - the data received from Slicing Dice API 372 | */ 373 | compareResult(test, result) { 374 | let expected; 375 | if (this.perTestInsertion) { 376 | expected = this._translateColumnNames(test['expected']); 377 | } else { 378 | expected = test['expected']; 379 | } 380 | 381 | let dataExpected = test['expected']; 382 | 383 | for(var key in dataExpected) { 384 | let value = dataExpected[key]; 385 | if (value === 'ignore') { 386 | continue; 387 | } 388 | 389 | if (!this.compareJson(expected[key], result[key])){ 390 | this.numFails += 1; 391 | this.failedTests.push(test['name']); 392 | 393 | console.log(" Expected: \"{0}\": {1}".format(key, JSON.stringify(expected[key]))); 394 | console.log(" Result: \"{0}\": {1}".format(key, JSON.stringify(result[key]))); 395 | console.log(" Status: Failed\n"); 396 | this.updateResult(); 397 | return; 398 | } 399 | 400 | this.numSuccesses += 1; 401 | 402 | console.log(' Status: Passed\n'); 403 | this.updateResult(); 404 | } 405 | } 406 | 407 | /* Compare two JSON's 408 | * 409 | * @param (object) expected - the data expected 410 | * @param (object) result - the data received from Slicing Dice API 411 | */ 412 | compareJson(expected, result) { 413 | if (typeof expected !== typeof result) return false; 414 | if (expected.constructor !== result.constructor) return false; 415 | 416 | if (expected instanceof Array) { 417 | return this.arrayEqual(expected, result); 418 | } 419 | 420 | if (typeof expected === "object") { 421 | return this.compareJsonValue(expected, result); 422 | } 423 | 424 | if (isNaN(expected)) { 425 | return expected === result; 426 | } 427 | 428 | return this.numberIsClose(expected, result); 429 | } 430 | 431 | numberIsClose(a, b, rel_tol=1e-09, abs_tol=0.0) { 432 | return Math.abs(a - b) <= Math.max(rel_tol * Math.max(Math.abs(a), Math.abs(b)), abs_tol); 433 | } 434 | 435 | /* Compare two JSON's values 436 | * 437 | * @param (object) expected - the data expected 438 | * @param (object) result - the data received from Slicing Dice API 439 | */ 440 | compareJsonValue(expected, result) { 441 | for (let key in expected) { 442 | if (expected.hasOwnProperty(key)) { 443 | if (!result.hasOwnProperty(key)) { 444 | return false; 445 | } 446 | 447 | if (!this.compareJson(expected[key], result[key])) { 448 | return false; 449 | } 450 | } 451 | } 452 | 453 | return true; 454 | } 455 | 456 | /* Compare two JSON's arrays 457 | * 458 | * @param (array) expected - the data expected 459 | * @param (array) result - the data received from Slicing Dice API 460 | */ 461 | arrayEqual(expected, result) { 462 | if (expected.length !== result.length) { 463 | return false; 464 | } 465 | 466 | let i = expected.length; 467 | 468 | while (i--) { 469 | let j = result.length; 470 | let found = false; 471 | 472 | while (!found && j--) { 473 | if (this.compareJson(expected[i], result[j])) { 474 | found = true; 475 | } 476 | } 477 | 478 | if (!found) { 479 | return false; 480 | } 481 | } 482 | 483 | return true; 484 | } 485 | 486 | // Write a file testResult.tmp with the result of the tests 487 | updateResult() { 488 | if (process.platform == "win32") { 489 | return; 490 | } 491 | let finalMessage; 492 | let failedTestsStr = ""; 493 | 494 | if (this.failedTests.length > 0){ 495 | for(let item in this.failedTests) { 496 | failedTestsStr += " - {0}\n".format(this.failedTests[item]); 497 | } 498 | } 499 | if (this.numFails > 0){ 500 | HAS_FAILED_TESTS = true; 501 | let isSingular = this.numFails == 1; 502 | let testOrTests = null; 503 | if (isSingular){ 504 | testOrTests = "test has"; 505 | } else { 506 | testOrTests = "tests have"; 507 | } 508 | 509 | finalMessage = "FAIL: {0} {1} failed\n".format(this.numFails, testOrTests); 510 | } else { 511 | finalMessage = "SUCCESS: All tests passed\n"; 512 | } 513 | 514 | let content = "\nResults:\n Successes: {0}\n Fails: {1} \n{2}\n{3}".format(this.numSuccesses, this.numFails, failedTestsStr, finalMessage); 515 | 516 | fs.writeFile('testResult.tmp', content, function (err) { 517 | if (err) return console.log(err); 518 | }); 519 | } 520 | } 521 | 522 | // Show results of the test saved on testResult.tmp file 523 | function showResults(){ 524 | console.log(fs.readFileSync('testResult.tmp', 'utf8')); 525 | fs.unlink('testResult.tmp', (err) => { 526 | if (err) throw err; 527 | }); 528 | 529 | if (HAS_FAILED_TESTS) 530 | process.exit(1); 531 | process.exit(0); 532 | } 533 | process.on('SIGINT', function() { 534 | showResults(); 535 | }); 536 | 537 | function main(){ 538 | // SlicingDice queries to be tested. Must match the JSON file name. 539 | let queryTypes = [ 540 | 'count_entity', 541 | 'count_event', 542 | 'top_values', 543 | 'aggregation', 544 | 'result', 545 | 'score', 546 | 'sql', 547 | 'delete', 548 | 'update' 549 | ]; 550 | 551 | let apiKey = process.env.SD_API_KEY; 552 | if (apiKey === undefined){ 553 | apiKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfX3NhbHQiOiIxNTI0MjQ4NzIwNzg2IiwicGVybWlzc2lvbl9sZXZlbCI6MywicHJvamVjdF9pZCI6MzAwMjksImNsaWVudF9pZCI6MTF9.SaRJX3Li_0AcrfDFaMOPHJNS9oGxYPjkYmpEza00IMk"; 554 | } 555 | 556 | // Testing class with demo API key 557 | // To get a demo api key visit: http://panel.slicingdice.com/docs/#api-details-api-connection-api-keys-demo-key 558 | let sdTester = new SlicingDiceTester(apiKey, false); 559 | 560 | let tests = []; 561 | for(let i = 0; i < queryTypes.length; i++) { 562 | tests.push((callback) => sdTester.runTests(queryTypes[i], callback)); 563 | } 564 | 565 | async.series(tests, () => { 566 | showResults(); 567 | }); 568 | } 569 | 570 | if (require.main === module) { 571 | main(); 572 | } 573 | --------------------------------------------------------------------------------