├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.xml ├── composer.json ├── composer.lock ├── docs ├── html-timeboundary-output.png └── sequence-diagram.png ├── examples ├── _examples-config.php ├── compare-transformation-and-not.php ├── healthcheck.php ├── html-query-printer-improved.php ├── html-query-printer.php ├── html-timeboundary-printer.php ├── src │ ├── ExampleGroupByQueries │ │ ├── ReferralsByCompanyGroupByQueryGenerator.php │ │ ├── ReferralsByCompanyGroupByQueryParameters.php │ │ ├── ReferralsByCompanyGroupByResponseHandler.php │ │ └── ReferralsByCompanyGroupByWithResponseObject.php │ └── ExampleResponseObjects │ │ └── ExampleReferralByCompanyResponseObject.php └── stdout-timeboundary-printer.php ├── phpunit.xml ├── src └── DruidFamiliar │ ├── Abstracts │ └── AbstractTaskParameters.php │ ├── DruidTime.php │ ├── Exception │ ├── ConnectionLostException.php │ ├── DruidBusyException.php │ ├── DruidUnavailableException.php │ ├── EmptyParametersException.php │ ├── MalformedQueryException.php │ ├── MissingParametersException.php │ └── UnexpectedTypeException.php │ ├── Interfaces │ ├── IDruidQueryExecutor.php │ ├── IDruidQueryGenerator.php │ ├── IDruidQueryParameters.php │ └── IDruidQueryResponseHandler.php │ ├── Interval.php │ ├── QueryExecutor │ ├── DruidNodeDruidQueryExecutor.php │ └── JSONDruidNodeDruidQueryExecutor.php │ ├── QueryGenerator │ ├── GroupByQueryGenerator.php │ ├── SegmentMetadataDruidQueryGenerator.php │ ├── SimpleGroupByDruidQueryGenerator.php │ └── TimeBoundaryDruidQueryGenerator.php │ ├── QueryParameters │ ├── GroupByQueryParameters.php │ ├── SegmentMetadataQueryParameters.php │ ├── SimpleGroupByQueryParameters.php │ └── TimeBoundaryQueryParameters.php │ ├── Response │ ├── GroupByResponse.php │ └── TimeBoundaryResponse.php │ └── ResponseHandler │ ├── DoNothingResponseHandler.php │ ├── GroupByResponseHandler.php │ ├── JsonFormattingResponseHandler.php │ └── TimeBoundaryResponseHandler.php └── tests └── DruidFamiliar └── Test ├── Abstracts └── AbstractTaskParametersTest.php ├── DruidTimeTest.php ├── Exception ├── EmptyParametersExceptionTest.php └── MissingParametersExceptionTest.php ├── IntervalTest.php ├── QueryExecutor └── DruidNodeDruidQueryExecutorTest.php ├── QueryGenerator ├── GroupByQueryGeneratorTest.php ├── SegmentMetadataDruidQueryGeneratorTest.php ├── SimpleGroupByDruidQueryGeneratorTest.php └── TimeBoundaryDruidQueryGeneratorTest.php ├── QueryParameters ├── GroupByQueryParametersTest.php ├── SegmentMetadataQueryParametersTest.php ├── SimpleGroupByQueryParametersTest.php └── TimeBoundaryQueryParametersTest.php ├── ResponseHandler ├── DoNothingResponseHandlerTest.php ├── GroupByResponseHandlerTest.php └── TimeBoundaryResponseHandlerTest.php ├── bootstrap.php └── phpunit.xml /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | vendor/ 3 | build/ 4 | .idea/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '5.3' 4 | - '5.5' 5 | - '7.0' 6 | - hhvm 7 | - nightly 8 | before_script: composer install 9 | script: vendor/bin/phpunit tests 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jasmine Hegman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-druid-query 2 | =============== 3 | 4 | PHP wrapper around executing HTTP requests to [Druid](http://druid.io). Usually this will be for queries, but can be 5 | used for other purposes (such as [ingestion](https://github.com/r4j4h/php-druid-ingest)). 6 | 7 | 8 | Overview 9 | --------------- 10 | 11 | The wrapper lives in the namespace `DruidFamiliar`. Druid itself was named in the sprit of the D&D character, and that 12 | character could have a familiar - a spiritually linked animal companion. This wrapper in a sense lives as a companion to 13 | Druid, and thus the name. 14 | 15 | I think the repo name should reflect the namespace for clarity, it currently is `php-druid-query` while 16 | the namespace is `DruidFamiliar`. This would be pretty breaking change and will be saved for the future. If you have 17 | other suggestions for naming of project or namespaces, feel free to suggest before then. 18 | 19 | 20 | Changelog 21 | ----------- 22 | 23 | 0.2.1 24 | 25 | - `DruidTime`/`Interval` classes added to make it easier to work with varieties of time inputs to Druid compatible time output. 26 | 27 | 0.2.0 Major refactoring 28 | 29 | - Query and Response Handling separated. All interfaces renamed and redesigned. 30 | - `IDruidConnection` is now `IDruidQueryExecutor`. 31 | - `IDruidQuery` is split into `IDruidQueryGenerator` and `IDruidQueryParameters` and `IDruidQueryResponseHandler`. 32 | - `BaseQuery` is no longer needed, many similar classes were deprecated or removed. 33 | - `DruidNodeConnection` is now `DruidNodeDruidQueryExecutor`. 34 | 35 | 0.1.0 Initial release 36 | 37 | - Quick sketch for sharing early. 38 | 39 | 40 | Typical Use 41 | --------------- 42 | 43 | In general, this wrapper's purpose is to streamline the execution of queries by encapsulating the cruft from the `HTTP` nature of Druid and the analytical grammar in query configuration. 44 | 45 | 1. Instantiate a connection, configured to hit a Druid endpoint. 46 | 2. Instantiate a query generator object for the desired query. 47 | 3. Instantiate a query parameters object, configured with desired query parameters. 48 | 4. Instantiate a result handler to format the results (otherwise use `DoNothingResponseHandler`) 49 | 5. Combine the connection, query, parameters, and response handler to execute it, getting the result from the result handler. 50 | 51 | Interface wise, this looks like: 52 | 53 | 1. Instantiate a `IDruidQueryExecutor`, configured to hit a Druid endpoint. 54 | 2. Instantiate a `IDruidQueryGenerator`. 55 | 3. Instantiate a `IDruidQueryParameters`, configured with parameters. 56 | 4. Instantiate a `IDruidQueryResponseHandler`. 57 | 5. Run the `IDruidQueryExecutor`'s `executeQuery` function with the `IDruidQueryGenerator`, `IDruidQueryParameters`, and the `IDruidQueryResponseHandler`, getting the result from the `IDruidQueryResponseHandler`. 58 | 59 | Implementation wise, this can look like: 60 | 61 | 1. Instantiate a `DruidNodeDruidQueryExecutor`, configured to hit a Druid endpoint. 62 | 2. Instantiate a `SegmentMetadataDruidQuery`. 63 | 3. Instantiate a `SegmentMetadataDruidQueryParameters`, configured with parameters. 64 | 4. Instantiate a `SegmentMetadataResponseHandler`. 65 | 5. Run the `DruidNodeDruidQueryExecutor`'s `executeQuery` function with the classes spawned in the previous steps, getting the result from `SegmentMetadataResponseHandler`. 66 | 67 | 68 | How to Install 69 | --------------- 70 | 71 | Right now, there is no tagged version. To be ready for it when it comes, branch-aliases are in place. 72 | 73 | - Stable branch: `~1.0@dev` 74 | - Cutting edge: `~1.1@dev` 75 | 76 | 77 | To install, it is suggested to use [Composer](http://getcomposer.org). If you have it installed, then the following instructions 78 | in a composer.json should be all you need to get started: 79 | 80 | ```json 81 | { 82 | "require": { 83 | "r4j4h/php-druid-query": "~1.0@dev" 84 | } 85 | } 86 | ``` 87 | 88 | Once that is in, `composer install` and `composer update` should work. 89 | 90 | Once those are run, require Composer's autoloader and you are off to the races, or tree circles as it were (bad Druid reference): 91 | 92 | 1. `require 'vendor/autoload.php';` 93 | 2. `$yay = new \DruidFamiliar\Query\TimeBoundaryDruidQuery('my-cool-data-source');` 94 | 3. Refer to the [Typical Use](#typical-use) section above. 95 | 96 | 97 | How to Test 98 | ------------- 99 | 100 | Once `composer install` has finished, from the root directory in a command terminal run: 101 | 102 | `vendor/bin/phing` 103 | 104 | or 105 | 106 | `vendor/bin/phpunit tests` 107 | 108 | 109 | Generate Documentation 110 | ------------- 111 | 112 | From the root directory, in a command terminal run: `php vendor/bin/phing docs`. 113 | 114 | 115 | Examples 116 | --------------- 117 | 118 | Examples are located in the [\examples](examples) folder. 119 | 120 | They share connection information from `_examples-config.php`. 121 | Change that match your Druid instance's connection info. 122 | 123 | Right now most are designed to run via the CLI, but will work in a browser if a web server running php serves them. 124 | 125 | The HTML outputting ones should print the query results to HTML: 126 | 127 | ![Example HTML TimeBoundary Output](docs/html-timeboundary-output.png) 128 | 129 | 130 | 131 | 132 | How it Works & How to Extend 133 | --------------- 134 | 135 | Please refer to this diagram for an overview of how this works underneath the hood. 136 | 137 | ![Sequence Diagram](docs/sequence-diagram.png) 138 | 139 | (From this [Dynamic LucidChart Source URL](https://www.lucidchart.com/publicSegments/view/542c92a6-f14c-4520-b004-04920a00caaf/image.png)) 140 | 141 | In general, to add support for a new query all you need to do is create a new class wherever you want that implements `IDruidQuery`. 142 | 143 | By wherever you want, that could be in a fork of this repo, or outside of this repo using this repo's interfaces. That is up to you. :) 144 | 145 | 146 | 147 | 148 | References 149 | --------------- 150 | 151 | - [Druid](http://druid.io) 152 | - [Composer](http://getcomposer.org) 153 | - [Guzzle](http://guzzle.readthedocs.org) 154 | 155 | 156 | Appendix A. Composer.json example that does not rely on Packagist.org: 157 | --------------- 158 | 159 | ```json 160 | { 161 | "repositories": [ 162 | { 163 | "type": "vcs", 164 | "url": "git@github.com:r4j4h/php-druid-query" 165 | } 166 | ], 167 | "require": { 168 | "r4j4h/php-druid-query": "~1.0@dev" 169 | } 170 | } 171 | ``` 172 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r4j4h/php-druid-query", 3 | "description": "Experimental PHP wrapper around querying druid", 4 | "keywords": ["druid", "querying", "query", "parameterized query"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Jasmine Hegman", 9 | "email": "hegpetz@gmail.com", 10 | "role": "Developer" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.3.0", 15 | "guzzle/guzzle": "~3.9" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "~4", 19 | "phing/phing": "~2", 20 | "phpmd/phpmd": "~2", 21 | "phploc/phploc": "~2" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "DruidFamiliar\\": "src/DruidFamiliar/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "DruidFamiliar\\": ["tests/DruidFamiliar/", "examples/src/"] 31 | } 32 | }, 33 | "extra": { 34 | "branch-alias": { 35 | "dev-develop": "1.1.x-dev", 36 | "dev-master": "1.0.x-dev" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/html-timeboundary-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4j4h/php-druid-query/c5f9f5b8c3efd233eada7f3ca0b38baea5407e83/docs/html-timeboundary-output.png -------------------------------------------------------------------------------- /docs/sequence-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4j4h/php-druid-query/c5f9f5b8c3efd233eada7f3ca0b38baea5407e83/docs/sequence-diagram.png -------------------------------------------------------------------------------- /examples/_examples-config.php: -------------------------------------------------------------------------------- 1 | '10.20.30.119', 5 | // 'druid-port' => '9001', 6 | // 'druid-dataSource' => 'referral-visit-test-data', 7 | 'druid-host' => 'devdruid.webpt.com', 8 | 'druid-port' => '8080', 9 | 'druid-dataSource' => 'referral-visit-old-format' 10 | ); -------------------------------------------------------------------------------- /examples/compare-transformation-and-not.php: -------------------------------------------------------------------------------- 1 | executeQuery($q, $p, new DruidFamiliar\ResponseHandler\JsonFormattingResponseHandler()); 18 | 19 | var_dump( $r ); 20 | 21 | //array(1) { 22 | // [0]=> 23 | // array(2) { 24 | // ["timestamp"]=> 25 | // string(24) "2011-06-01T00:00:11.000Z" 26 | // ["result"]=> 27 | // array(2) { 28 | // ["minTime"]=> 29 | // string(24) "2011-06-01T00:00:11.000Z" 30 | // ["maxTime"]=> 31 | // string(24) "2011-11-30T23:55:34.000Z" 32 | // } 33 | // } 34 | //} 35 | 36 | 37 | $q = new \DruidFamiliar\QueryGenerator\TimeBoundaryDruidQueryGenerator($druidDataSource); 38 | $p = new \DruidFamiliar\QueryParameters\TimeBoundaryQueryParameters($druidDataSource); 39 | $r = $c->executeQuery($q, $p, new DruidFamiliar\ResponseHandler\TimeBoundaryResponseHandler()); 40 | 41 | 42 | var_dump( $r ); 43 | 44 | //object(DruidFamiliar\Response\TimeBoundaryResponse)#11 (2) { 45 | // ["minTime"]=> 46 | // string(24) "2008-02-06T11:47:39.000Z" 47 | // ["maxTime"]=> 48 | // string(24) "2008-12-31T19:36:48.000Z" 49 | //} 50 | -------------------------------------------------------------------------------- /examples/healthcheck.php: -------------------------------------------------------------------------------- 1 | executeQuery($q, $params, $responseHandler); 37 | 38 | echo "DruidFamiliar\n"; 39 | echo "Talking to $druidHost on port $druidPort.\n"; 40 | 41 | if ( isset( $timeBoundaryResponse->minTime ) ) { 42 | echo "Good to go!\n"; 43 | } else { 44 | echo "Problem encountered :(\n"; 45 | echo "Talked to something, but it didn't seem to be druid."; 46 | } 47 | 48 | } 49 | catch ( Guzzle\Common\Exception\InvalidArgumentException $e ) { 50 | 51 | } 52 | catch ( Guzzle\Http\Exception\CurlException $e ) { 53 | echo "Problem encountered :(\n"; 54 | echo "Am I really pointed at a Druid broker node?\n"; 55 | } 56 | catch ( \Exception $e ) 57 | { 58 | $message = $e->getMessage(); 59 | 60 | if ( $message === 'Unexpected response format' ) 61 | { 62 | echo "Problem encountered :(\n"; 63 | echo "Does your Data Source exist?"; 64 | } 65 | else if ( $message === 'Unknown data source' ) 66 | { 67 | echo "Should be okay, did you point to a non-existant data source?\n"; 68 | } 69 | else 70 | { 71 | throw $e; 72 | } 73 | 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /examples/html-query-printer-improved.php: -------------------------------------------------------------------------------- 1 | executeQuery($q, $p, new DruidFamiliar\ResponseHandler\TimeBoundaryResponseHandler()); 31 | 32 | $q2 = new \DruidFamiliar\ExampleGroupByQueries\ReferralsByCompanyGroupByQueryGenerator(); 33 | $p2 = new ReferralsByCompanyGroupByQueryParameters( $druidDataSource, '2006-01-01T00:00', '2015-01-01T00' ); 34 | /** 35 | * @var ExampleReferralByCompanyResponseObject $r2 36 | */ 37 | $r2 = $c->executeQuery($q2, $p2, new ReferralsByCompanyGroupByResponseHandler()); 38 | 39 | 40 | $startTime = new DateTime( $r->minTime ); 41 | $endTime = new DateTime( $r->maxTime ); 42 | 43 | $formattedStartTime = $startTime->format("F m, Y h:i:s A"); 44 | $formattedEndTime = $endTime->format("F m, Y h:i:s A"); 45 | 46 | $groupByBodyRows = ''; 47 | 48 | foreach ( $r2 as $index => $val) 49 | { 50 | /** 51 | * @var \DruidFamiliar\ExampleResponseObjects\ExampleReferralByCompanyResponseObject $exampleReferralByCompanyResponseObject 52 | */ 53 | $exampleReferralByCompanyResponseObject = $val; 54 | 55 | $timestamp = $exampleReferralByCompanyResponseObject->getTimestamp(); 56 | $companyId = $exampleReferralByCompanyResponseObject->getCompanyId(); 57 | $facilityId = $exampleReferralByCompanyResponseObject->getFacilityId(); 58 | $referrals = $exampleReferralByCompanyResponseObject->getReferrals(); 59 | 60 | $groupByBodyRows .= << 62 | $timestamp 63 | $companyId 64 | $facilityId 65 | $referrals 66 | 67 | TABLEROW; 68 | 69 | } 70 | 71 | 72 | echo << 74 | 75 | 76 | 105 | 106 | 107 |

TimeBoundary data for DataSource "$druidDataSource":

108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 |
DataSourceStartEnd
$druidDataSource$formattedStartTime$formattedEndTime
124 |
125 |

Raw Group By Query Results

126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | $groupByBodyRows 137 | 138 |
timestampcompanyIdfacilityIdreferrals
139 |
140 | 141 | 142 | 143 | HTML_BODY; 144 | -------------------------------------------------------------------------------- /examples/html-query-printer.php: -------------------------------------------------------------------------------- 1 | executeQuery($q, $p, new DruidFamiliar\ResponseHandler\TimeBoundaryResponseHandler()); 30 | 31 | $q2 = new \DruidFamiliar\ExampleGroupByQueries\ReferralsByCompanyGroupByQueryGenerator(); 32 | $p2 = new ReferralsByCompanyGroupByQueryParameters( $druidDataSource, '2006-01-01T00:00', '2015-01-01T00' ); 33 | $r2 = $c->executeQuery($q2, $p2, new \DruidFamiliar\ResponseHandler\JsonFormattingResponseHandler()); 34 | 35 | 36 | $startTime = new DateTime( $r->minTime ); 37 | $endTime = new DateTime( $r->maxTime ); 38 | 39 | $formattedStartTime = $startTime->format("F m, Y h:i:s A"); 40 | $formattedEndTime = $endTime->format("F m, Y h:i:s A"); 41 | 42 | $groupByHeadRows = << 44 | timestamp 45 | companyId 46 | facilityId 47 | referrals 48 | 49 | TABLEHEADROW; 50 | ; 51 | $groupByBodyRows = ''; 52 | 53 | foreach ( $r2 as $index => $chunk) 54 | { 55 | $timestamp = $chunk['timestamp']; 56 | $companyId = $chunk['event']['company_id']; 57 | $facilityId = $chunk['event']['facility_id']; 58 | $referrals = $chunk['event']['referral_count']; 59 | 60 | $groupByBodyRows .= << 62 | $timestamp 63 | $companyId 64 | $facilityId 65 | $referrals 66 | 67 | TABLEROW; 68 | 69 | } 70 | 71 | 72 | echo << 74 | 75 | 76 | 105 | 106 | 107 |

TimeBoundary data for DataSource "$druidDataSource":

108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 |
DataSourceStartEnd
$druidDataSource$formattedStartTime$formattedEndTime
124 |
125 |

Raw Group By Query Results

126 | 127 | 128 | $groupByHeadRows 129 | 130 | 131 | $groupByBodyRows 132 | 133 |
134 |
135 | 136 | 137 | 138 | HTML_BODY; 139 | -------------------------------------------------------------------------------- /examples/html-timeboundary-printer.php: -------------------------------------------------------------------------------- 1 | executeQuery($q, $p, new DruidFamiliar\ResponseHandler\TimeBoundaryResponseHandler()); 28 | 29 | $startTime = new DateTime( $r->minTime ); 30 | $endTime = new DateTime( $r->maxTime ); 31 | 32 | $formattedStartTime = $startTime->format("F m, Y h:i:s A"); 33 | $formattedEndTime = $endTime->format("F m, Y h:i:s A"); 34 | 35 | echo << 37 | 38 | 39 | 68 | 69 | 70 |

TimeBoundary data for DataSource "$druidDataSource":

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
DataSourceStartEnd
$druidDataSource$formattedStartTime$formattedEndTime
87 | 88 | 89 | 90 | HTML_BODY; 91 | -------------------------------------------------------------------------------- /examples/src/ExampleGroupByQueries/ReferralsByCompanyGroupByQueryGenerator.php: -------------------------------------------------------------------------------- 1 | queryTemplate; 43 | 44 | $query = str_replace('{DATASOURCE}', $params->dataSource, $query); 45 | $query = str_replace('{STARTINTERVAL}', $params->startInterval, $query); 46 | $query = str_replace('{ENDINTERVAL}', $params->endInterval, $query); 47 | 48 | return $query; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /examples/src/ExampleGroupByQueries/ReferralsByCompanyGroupByQueryParameters.php: -------------------------------------------------------------------------------- 1 | dataSource = $dataSource; 34 | $this->startInterval = $startInterval; 35 | $this->endInterval = $endInterval; 36 | } 37 | 38 | /** 39 | * @throws MissingParametersException 40 | */ 41 | public function validate() 42 | { 43 | $missingParams = array(); 44 | 45 | if ( !isset( $this->dataSource ) ) { 46 | $missingParams[] = 'dataSource'; 47 | } 48 | if ( !isset( $this->startInterval ) ) { 49 | $missingParams[] = 'startInterval'; 50 | } 51 | if ( !isset( $this->endInterval ) ) { 52 | $missingParams[] = 'endInterval'; 53 | } 54 | 55 | if ( count( $missingParams ) > 0 ) { 56 | throw new MissingParametersException($missingParams); 57 | } 58 | 59 | return true; 60 | } 61 | } -------------------------------------------------------------------------------- /examples/src/ExampleGroupByQueries/ReferralsByCompanyGroupByResponseHandler.php: -------------------------------------------------------------------------------- 1 | json(); 28 | 29 | if ( empty( $response ) ) { 30 | throw new \Exception('Unknown data source.'); 31 | } 32 | 33 | $responseArray = array(); 34 | 35 | foreach ( $response as $index => $chunk) 36 | { 37 | $timestamp = $chunk['timestamp']; 38 | $companyId = $chunk['event']['company_id']; 39 | $facilityId = $chunk['event']['facility_id']; 40 | $referrals = $chunk['event']['referral_count']; 41 | 42 | $responseObj = new \DruidFamiliar\ExampleResponseObjects\ExampleReferralByCompanyResponseObject( 43 | $companyId, $facilityId, $referrals, $timestamp 44 | ); 45 | 46 | $responseArray[] = $responseObj; 47 | } 48 | 49 | return $responseArray; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /examples/src/ExampleGroupByQueries/ReferralsByCompanyGroupByWithResponseObject.php: -------------------------------------------------------------------------------- 1 | $chunk) 21 | { 22 | $timestamp = $chunk['timestamp']; 23 | $companyId = $chunk['event']['company_id']; 24 | $facilityId = $chunk['event']['facility_id']; 25 | $referrals = $chunk['event']['referral_count']; 26 | 27 | $dto = new \DruidFamiliar\ExampleResponseObjects\ExampleReferralByCompanyResponseObject( 28 | $companyId, $facilityId, $referrals, $timestamp 29 | ); 30 | 31 | $responseArray[] = $dto; 32 | } 33 | 34 | return $responseArray; 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /examples/src/ExampleResponseObjects/ExampleReferralByCompanyResponseObject.php: -------------------------------------------------------------------------------- 1 | companyId = $companyId; 19 | $this->facilityId = $facilityId; 20 | $this->referrals = $referrals; 21 | $this->timestamp = $timestamp; 22 | } 23 | 24 | /** 25 | * @return mixed 26 | */ 27 | public function getCompanyId() 28 | { 29 | return $this->companyId; 30 | } 31 | 32 | /** 33 | * @return mixed 34 | */ 35 | public function getFacilityId() 36 | { 37 | return $this->facilityId; 38 | } 39 | 40 | /** 41 | * @return mixed 42 | */ 43 | public function getReferrals() 44 | { 45 | return $this->referrals; 46 | } 47 | 48 | /** 49 | * @return mixed 50 | */ 51 | public function getTimestamp() 52 | { 53 | return $this->timestamp; 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /examples/stdout-timeboundary-printer.php: -------------------------------------------------------------------------------- 1 | executeQuery($q, $p, new DruidFamiliar\ResponseHandler\TimeBoundaryResponseHandler()); 29 | 30 | echo "TimeBoundary data for DataSource \"$druidDataSource\": "; 31 | 32 | $startTime = new DateTime( $r->minTime ); 33 | $endTime = new DateTime( $r->maxTime ); 34 | 35 | $formattedStartTime = $startTime->format("F m, Y h:i:s A"); 36 | $formattedEndTime = $endTime->format("F m, Y h:i:s A"); 37 | 38 | 39 | echo $formattedStartTime . " to " . $formattedEndTime . "\n"; 40 | 41 | // Outputs: 42 | // TimeBoundary data for DataSource "referral-visit-test-data": June 06, 2011 12:00:11 AM to June 06, 2011 12:00:11 AM 43 | 44 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/ 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/DruidFamiliar/Abstracts/AbstractTaskParameters.php: -------------------------------------------------------------------------------- 1 | validate(); 24 | } 25 | catch(MissingParametersException $e) 26 | { 27 | return false; 28 | } 29 | 30 | return true; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/DruidFamiliar/DruidTime.php: -------------------------------------------------------------------------------- 1 | setTime($time); 31 | } 32 | 33 | public function __toString() 34 | { 35 | // Format 36 | return $this->formatTimeForDruid(); 37 | } 38 | 39 | public function formatTimeForDruid() 40 | { 41 | // Missing params 42 | $missingParams = array(); 43 | if ( !isset( $this->time ) ) { $missingParams[] = 'time'; } 44 | if ( count( $missingParams ) > 0 ) { throw new MissingParametersException($missingParams); } 45 | 46 | // Invalid params 47 | if ( !$this->time instanceof DateTime ) { 48 | throw new UnexpectedTypeException($this->time, 'DateTime', 'For parameter time.'); 49 | } 50 | 51 | // Format 52 | return $this->time->format("Y-m-d\TH:i:s\Z"); 53 | } 54 | 55 | /** 56 | * @param string|DateTime $time 57 | * @throws RuntimeException 58 | */ 59 | public function setTime($time) 60 | { 61 | 62 | if ( is_string($time ) ) 63 | { 64 | $time = new DateTime( $time ); 65 | } 66 | 67 | if ( is_a($time, 'DateTime') ) 68 | { 69 | $this->time = $time; 70 | } 71 | else if ( is_a($time, 'DruidFamiliar\DruidTime') ) 72 | { 73 | /** @var \DruidFamiliar\DruidTime $time */ 74 | $this->time = $time->getTime(); 75 | } 76 | else 77 | { 78 | throw new RuntimeException('Encountered unexpected time. Expected either string or DateTime.'); 79 | } 80 | 81 | } 82 | 83 | public function getTime() 84 | { 85 | return $this->time; 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/Exception/ConnectionLostException.php: -------------------------------------------------------------------------------- 1 | emptyParameters = $emptyParameters; 34 | parent::__construct("Empty parameters: " . join(", ", $this->emptyParameters), count($this->emptyParameters), $previous); 35 | } 36 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/Exception/MalformedQueryException.php: -------------------------------------------------------------------------------- 1 | missingParameters = $missingParameters; 21 | parent::__construct("Missing parameters: " . join(", ", $this->missingParameters), count($this->missingParameters), $previous); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/DruidFamiliar/Exception/UnexpectedTypeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace DruidFamiliar\Exception; 13 | 14 | use Exception; 15 | 16 | class UnexpectedTypeException extends Exception 17 | { 18 | public function __construct($value, $expectedType, $extraMessage = "", $previous = NULL) 19 | { 20 | $message = sprintf('Expected argument of type "%s", "%s" given.', $expectedType, is_object($value) ? get_class($value) : gettype($value)); 21 | 22 | if($extraMessage) 23 | { 24 | $message .= " " . $extraMessage; 25 | } 26 | parent::__construct( $message, 0, $previous ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/DruidFamiliar/Interfaces/IDruidQueryExecutor.php: -------------------------------------------------------------------------------- 1 | setInterval($intervalStart, $intervalEnd); 40 | } 41 | 42 | 43 | /** 44 | * @param string|DateTime|DruidTime $intervalStart 45 | * @param string|DateTime|DruidTime $intervalEnd 46 | */ 47 | public function setInterval($intervalStart = "1970-01-01 01:30:00", $intervalEnd = "3030-01-01 01:30:00") 48 | { 49 | $this->setStart($intervalStart); 50 | $this->setEnd($intervalEnd); 51 | } 52 | 53 | /** 54 | * @param string|DateTime|DruidTime $intervalStart 55 | */ 56 | public function setStart($intervalStart = "1970-01-01 01:30:00") 57 | { 58 | if ( is_string($intervalStart ) ) 59 | { 60 | $intervalStart = new DateTime( $intervalStart ); 61 | } 62 | 63 | if ( is_a($intervalStart, 'DateTime') ) 64 | { 65 | $intervalStart = new DruidTime( $intervalStart ); 66 | } 67 | 68 | if ( is_a($intervalStart, 'DruidFamiliar\DruidTime') ) 69 | { 70 | $this->intervalStart = $intervalStart; 71 | } 72 | else 73 | { 74 | throw new RuntimeException('Encountered unexpected start time. Expected either string, DateTime, or DruidTime.'); 75 | } 76 | } 77 | 78 | /** 79 | * @param string|DateTime|DruidTime $intervalEnd 80 | */ 81 | public function setEnd($intervalEnd = "3030-01-01 01:30:00") 82 | { 83 | if ( is_string($intervalEnd ) ) 84 | { 85 | $intervalEnd = new DateTime( $intervalEnd ); 86 | } 87 | 88 | if ( is_a($intervalEnd, 'DateTime') ) 89 | { 90 | $intervalEnd = new DruidTime( $intervalEnd ); 91 | } 92 | 93 | if ( is_a($intervalEnd, 'DruidFamiliar\DruidTime') ) 94 | { 95 | $this->intervalEnd = $intervalEnd; 96 | } 97 | else 98 | { 99 | throw new RuntimeException('Encountered unexpected end time. Expected either string, DateTime, or DruidTime.'); 100 | } 101 | } 102 | 103 | 104 | /** 105 | * @return string 106 | * @throws MissingParametersException 107 | * @throws UnexpectedTypeException 108 | */ 109 | function __toString() 110 | { 111 | return $this->getIntervalsString(); 112 | } 113 | 114 | /** 115 | * @return string 116 | * @throws MissingParametersException 117 | * @throws UnexpectedTypeException 118 | */ 119 | public function getIntervalsString() 120 | { 121 | // Missing params 122 | $missingParams = array(); 123 | if ( !isset( $this->intervalStart ) ) { $missingParams[] = 'intervalStart'; } 124 | if ( !isset( $this->intervalEnd ) ) { $missingParams[] = 'intervalEnd'; } 125 | if ( count( $missingParams ) > 0 ) { throw new MissingParametersException($missingParams); } 126 | 127 | // Invalid params 128 | if ( !$this->intervalStart instanceof DruidTime ) { 129 | throw new UnexpectedTypeException($this->intervalStart, 'DruidTime', 'For parameter intervalStart.'); 130 | } 131 | if ( !$this->intervalEnd instanceof DruidTime ) { 132 | throw new UnexpectedTypeException($this->intervalEnd, 'DruidTime', 'For parameter intervalEnd.'); 133 | } 134 | 135 | // Format 136 | return $this->intervalStart . '/' . $this->intervalEnd; 137 | } 138 | 139 | 140 | /** 141 | * @return DruidTime 142 | */ 143 | public function getStart() 144 | { 145 | return $this->intervalStart; 146 | } 147 | 148 | /** 149 | * @return DruidTime 150 | */ 151 | public function getEnd() 152 | { 153 | return $this->intervalEnd; 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryExecutor/DruidNodeDruidQueryExecutor.php: -------------------------------------------------------------------------------- 1 | "application/json;charset=utf-8"); 55 | 56 | public function __construct($ip, $port, $endpoint = '/druid/v2/', $protocol = 'http', $httpMethod = 'POST') { 57 | $this->ip = $ip; 58 | $this->port = $port; 59 | $this->endpoint = $endpoint; 60 | $this->setProtocol($protocol); 61 | $this->setHttpMethod($httpMethod); 62 | } 63 | 64 | public function getBaseUrl() 65 | { 66 | $baseUrl = $this->protocol . '://' . $this->ip . ':' . $this->port; 67 | $url = $baseUrl . $this->endpoint; 68 | return $url; 69 | } 70 | 71 | /** 72 | * Create a Guzzle Request object using the given JSON parameters 73 | * 74 | * @param string $query JSON String 75 | * @return \Guzzle\Http\Message\RequestInterface 76 | * @throws \Exception 77 | */ 78 | public function createRequest($query) 79 | { 80 | $client = new \Guzzle\Http\Client(); 81 | 82 | $method = $this->httpMethod; 83 | $uri = $this->getBaseUrl(); 84 | $headers = $this->getHeaders(); 85 | $options = array(); 86 | 87 | if ( $method === 'POST' ) 88 | { 89 | $postBody = $query; 90 | $request = $client->createRequest( $method, $uri, $headers, $postBody, $options ); 91 | } 92 | else if ( $method === 'GET' ) 93 | { 94 | $request = $client->createRequest( $method, $uri, $headers, null, $options ); 95 | if ( $query ) { 96 | $queryObject = json_decode($query, true); 97 | $query = $request->getQuery(); 98 | foreach ($queryObject as $key => $val) { 99 | $query->set($key, $val); 100 | } 101 | } 102 | } 103 | else 104 | { 105 | throw new Exception('Unexpected HTTP Method: ' . $method); 106 | } 107 | 108 | return $request; 109 | } 110 | 111 | /** 112 | * Execute a Druid query using the provided query generator, parameters, and response payload handler. 113 | * 114 | * See DruidFamiliar\ResponseHandler\DoNothingResponseHandler. 115 | * 116 | * @param IDruidQueryGenerator $queryGenerator 117 | * @param IDruidQueryParameters $params 118 | * @param IDruidQueryResponseHandler $responseHandler 119 | * @return mixed 120 | */ 121 | public function executeQuery(IDruidQueryGenerator $queryGenerator, IDruidQueryParameters $params, IDruidQueryResponseHandler $responseHandler) 122 | { 123 | $params->validate(); 124 | 125 | $generatedQuery = $queryGenerator->generateQuery($params); 126 | 127 | // Create a request 128 | $request = $this->createRequest( $generatedQuery ); 129 | 130 | // Send the request and parse the JSON response into an array 131 | try 132 | { 133 | $response = $request->send(); 134 | } 135 | catch (\Guzzle\Http\Exception\CurlException $curlException) 136 | { 137 | throw new $curlException; 138 | } 139 | 140 | $formattedResponse = $responseHandler->handleResponse($response); 141 | 142 | return $formattedResponse; 143 | } 144 | 145 | /** 146 | * Get the HTTP Method. 147 | * 148 | * @return string 149 | */ 150 | public function getHttpMethod() 151 | { 152 | return $this->httpMethod; 153 | } 154 | 155 | /** 156 | * Set the HTTP Method. 157 | * 158 | * Supported methods are: GET, POST 159 | * 160 | * @param $method 161 | * @throws \Exception 162 | */ 163 | public function setHttpMethod($method) 164 | { 165 | $allowed_methods = array('GET', 'POST'); 166 | 167 | $method = strtoupper( $method ); 168 | 169 | if ( !in_array( $method, $allowed_methods ) ) { 170 | throw new Exception('Unsupported HTTP Method: ' . $method . '. Supported methods are: ' . join($allowed_methods, ', ')); 171 | } 172 | 173 | $this->httpMethod = $method; 174 | } 175 | 176 | /** 177 | * Get the protocol. 178 | * 179 | * @return string 180 | */ 181 | public function getProtocol() 182 | { 183 | return $this->protocol; 184 | } 185 | 186 | /** 187 | * Set the protocol. 188 | * 189 | * Supported protocols are: http, https 190 | * 191 | * @param string $protocol 192 | */ 193 | public function setProtocol($protocol) 194 | { 195 | $allowedProtocols = array('http', 'https'); 196 | 197 | $protocol = strtolower( $protocol ); 198 | 199 | if ( !in_array( $protocol,$allowedProtocols ) ) { 200 | throw new Exception('Unsupported Protocol: ' . $protocol . '. Supported protocols are: ' . join($allowedProtocols, ', ')); 201 | } 202 | 203 | $this->protocol = $protocol; 204 | } 205 | 206 | /** 207 | * @return String 208 | */ 209 | public function getIp() 210 | { 211 | return $this->ip; 212 | } 213 | 214 | /** 215 | * @param String $ip 216 | */ 217 | public function setIp($ip) 218 | { 219 | $this->ip = $ip; 220 | } 221 | 222 | /** 223 | * @return Number 224 | */ 225 | public function getPort() 226 | { 227 | return $this->port; 228 | } 229 | 230 | /** 231 | * @param Number $port 232 | */ 233 | public function setPort($port) 234 | { 235 | $this->port = $port; 236 | } 237 | 238 | /** 239 | * @return string 240 | */ 241 | public function getEndpoint() 242 | { 243 | return $this->endpoint; 244 | } 245 | 246 | /** 247 | * @param string $endpoint 248 | */ 249 | public function setEndpoint($endpoint) 250 | { 251 | $this->endpoint = $endpoint; 252 | } 253 | 254 | /** 255 | * @return array 256 | */ 257 | public function getHeaders() 258 | { 259 | return $this->headers; 260 | } 261 | 262 | /** 263 | * @param array $headers 264 | */ 265 | public function setHeaders(array $headers) 266 | { 267 | $this->headers = $headers; 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryExecutor/JSONDruidNodeDruidQueryExecutor.php: -------------------------------------------------------------------------------- 1 | ip = $ip; 55 | $this->port = $port; 56 | $this->endpoint = $endpoint; 57 | $this->protocol = $protocol; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function getBaseUrl() 64 | { 65 | $baseUrl = $this->protocol . '://' . $this->ip . ':' . $this->port; 66 | $url = $baseUrl . $this->endpoint; 67 | return $url; 68 | } 69 | 70 | /** 71 | * @param $query 72 | * 73 | * @return \Guzzle\Http\Message\RequestInterface 74 | */ 75 | public function createRequest($query) 76 | { 77 | $client = new Client(); 78 | 79 | $request = $client->post($this->getBaseUrl(), array("content-type" => "application/json"), json_encode($query)); 80 | 81 | return $request; 82 | } 83 | 84 | 85 | public function executeQuery(IDruidQueryGenerator $queryGenerator, IDruidQueryParameters $params, IDruidQueryResponseHandler $responseHandler) 86 | { 87 | $params->validate(); 88 | 89 | $generatedQuery = $queryGenerator->generateQuery($params); 90 | 91 | // Create a POST request 92 | $request = $this->createRequest($generatedQuery); 93 | 94 | // Send the request and parse the JSON response into an array 95 | try 96 | { 97 | $response = $request->send(); 98 | } 99 | catch(CurlException $curlException) 100 | { 101 | throw new $curlException; 102 | } 103 | 104 | $data = $this->parseResponse($response); 105 | 106 | $formattedResponse = $responseHandler->handleResponse($data); 107 | 108 | return $formattedResponse; 109 | } 110 | 111 | /** 112 | * @param Response $rawResponse 113 | * 114 | * @return mixed 115 | */ 116 | protected function parseResponse($rawResponse) 117 | { 118 | $formattedResponse = $rawResponse->json(); 119 | 120 | return $formattedResponse; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryGenerator/GroupByQueryGenerator.php: -------------------------------------------------------------------------------- 1 | validate(); 36 | $query = $params->getJSONString(); 37 | return $query; 38 | } 39 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryGenerator/SegmentMetadataDruidQueryGenerator.php: -------------------------------------------------------------------------------- 1 | validate(); 42 | 43 | $responseObj = array( 44 | 'queryType' => 'segmentMetadata', 45 | "dataSource" => $params->dataSource, 46 | "intervals" => $params->intervals->__toString() 47 | ); 48 | 49 | $responseString = json_encode( $responseObj ); 50 | 51 | return $responseString; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryGenerator/SimpleGroupByDruidQueryGenerator.php: -------------------------------------------------------------------------------- 1 | validate(); 53 | 54 | $query = $this->queryTemplate; 55 | 56 | // Assemble the query instead 57 | $queryKeys = array(); 58 | 59 | // We always have these keys 60 | $queryKeys[] = '"queryType": "{QUERYTYPE}"'; 61 | $queryKeys[] = '"dataSource": "{DATASOURCE}"'; 62 | 63 | if ( is_array( $params->granularity ) && count( $params->granularity ) > 0 ) { 64 | $queryKeys[] = '"granularity": {GRANULARITYSPEC.GRAN}'; 65 | } else { 66 | $queryKeys[] = '"granularity": "{GRANULARITYSPEC.GRAN}"'; 67 | } 68 | 69 | $queryKeys[] = '"dimensions": [ "{NON_TIME_DIMENSIONS}" ]'; 70 | 71 | if ( count( $params->filters ) > 0 ) { 72 | $queryKeys[] = '"filter": {FILTERS}'; 73 | } 74 | 75 | if ( count( $params->aggregators ) > 0 ) { 76 | $queryKeys[] = '"aggregations": [{AGGREGATORS}]'; 77 | } 78 | 79 | if ( count( $params->postAggregators ) > 0 ) { 80 | $queryKeys[] = '"postAggregations": [{POSTAGGREGATORS}]'; 81 | } 82 | 83 | $queryKeys[] = '"intervals": ["{INTERVALS}"]'; 84 | 85 | $query = "{" . join(",\n", $queryKeys) . "}"; 86 | 87 | 88 | $query = str_replace('{QUERYTYPE}', $params->queryType, $query); 89 | 90 | $query = str_replace('{DATASOURCE}', $params->dataSource, $query); 91 | $query = str_replace('{INTERVALS}', $params->intervals, $query); 92 | 93 | 94 | if ( is_array( $params->granularity ) && count( $params->granularity ) > 0 ) { 95 | $query = str_replace('{GRANULARITYSPEC.GRAN}', json_encode( $params->granularity ), $query); 96 | } else { 97 | $query = str_replace('{GRANULARITYSPEC.GRAN}', $params->granularity, $query); 98 | } 99 | 100 | $query = str_replace('{NON_TIME_DIMENSIONS}', join('","', $params->dimensions), $query); 101 | $query = str_replace('{FILTERS}', join(",", $params->filters), $query); 102 | $query = str_replace('{AGGREGATORS}', join(",", $params->aggregators), $query); 103 | $query = str_replace('{POSTAGGREGATORS}', join(",", $params->postAggregators), $query); 104 | 105 | return $query; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryGenerator/TimeBoundaryDruidQueryGenerator.php: -------------------------------------------------------------------------------- 1 | validate(); 32 | 33 | $responseObj = array( 34 | 'queryType' => 'timeBoundary', 35 | "dataSource" => $params->dataSource 36 | ); 37 | 38 | $responseString = json_encode( $responseObj ); 39 | 40 | return $responseString; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryParameters/GroupByQueryParameters.php: -------------------------------------------------------------------------------- 1 | queryType = 'groupBy'; 119 | } 120 | 121 | /** 122 | * Class constructor 123 | */ 124 | public function __construct() 125 | { 126 | $this->initialize(); 127 | } 128 | 129 | /** 130 | * Validates the query has the necessary parameters 131 | * 132 | * @throws MissingParametersException 133 | */ 134 | public function validate() 135 | { 136 | $flag = true; 137 | foreach($this->requiredParams as $param) 138 | { 139 | if(!isset($this->$param)) 140 | { 141 | $this->missingParameters[] = $param; 142 | $flag = false; 143 | } 144 | else 145 | { 146 | $val = $this->$param; 147 | if(!is_array($val)) 148 | { 149 | $val = trim($val); 150 | } 151 | if(empty($val)) 152 | { 153 | $this->emptyParameters[] = $param; 154 | $flag = false; 155 | } 156 | } 157 | } 158 | if(count($this->missingParameters) > 0) 159 | { 160 | throw new MissingParametersException($this->missingParameters); 161 | } 162 | if(count($this->emptyParameters) > 0) 163 | { 164 | throw new EmptyParametersException($this->emptyParameters); 165 | } 166 | return $flag; 167 | } 168 | 169 | /** 170 | * Converts the current object into a JSON representation to be used as a query 171 | * @return mixed|string 172 | */ 173 | public function getJSONString() 174 | { 175 | $retString = '{[DATA]}'; 176 | $buffStringArray = array(); 177 | foreach($this->allParameters as $param) 178 | { 179 | if(isset($this->$param)) 180 | { 181 | $buffStringArray[] = "\"{$param}\":" . json_encode($this->$param); 182 | } 183 | } 184 | $buffString = implode(',', $buffStringArray); 185 | $retString = str_replace('[DATA]', $buffString, $retString); 186 | return $retString; 187 | } 188 | 189 | /** 190 | * Returns the aggregations 191 | * 192 | * @return array 193 | */ 194 | public function getAggregations() 195 | { 196 | return $this->aggregations; 197 | } 198 | 199 | /** 200 | * Sets the aggregations 201 | * 202 | * @param array $aggregations 203 | * 204 | * @return $this 205 | */ 206 | public function setAggregations(array $aggregations) 207 | { 208 | foreach($aggregations as $aggregator) 209 | { 210 | $this->addAggregator($aggregator); 211 | } 212 | return $this; 213 | } 214 | 215 | /** 216 | * Adds a new aggregator to the aggregators array 217 | * 218 | * @param stdClass $aggregator 219 | * 220 | * @return $this 221 | */ 222 | public function addAggregator($aggregator) 223 | { 224 | if(is_object($aggregator)) 225 | { 226 | $this->aggregations[] = $aggregator; 227 | } 228 | return $this; 229 | } 230 | 231 | /** 232 | * Returns the context 233 | * 234 | * @return array 235 | */ 236 | public function getContext() 237 | { 238 | return $this->context; 239 | } 240 | 241 | /** 242 | * Sets the context 243 | * 244 | * @param array $contexts 245 | * 246 | * @return $this 247 | */ 248 | public function setContext(array $contexts) 249 | { 250 | foreach($contexts as $context) 251 | { 252 | $this->addContext($context); 253 | } 254 | return $this; 255 | } 256 | 257 | /** 258 | * Adds a new context to the contexts array 259 | * 260 | * @param string $context 261 | * 262 | * @return $this 263 | */ 264 | public function addContext($context) 265 | { 266 | $this->context[] = $context; 267 | return $this; 268 | } 269 | 270 | /** 271 | * Returns the dataSource 272 | * 273 | * @return string 274 | */ 275 | public function getDataSource() 276 | { 277 | return $this->dataSource; 278 | } 279 | 280 | /** 281 | * Sets the dataSource 282 | * 283 | * @param string $dataSource 284 | * 285 | * @return $this 286 | */ 287 | public function setDataSource($dataSource) 288 | { 289 | $this->dataSource = $dataSource; 290 | return $this; 291 | } 292 | 293 | /** 294 | * Returns the dimensions 295 | * 296 | * @return array 297 | */ 298 | public function getDimensions() 299 | { 300 | return $this->dimensions; 301 | } 302 | 303 | /** 304 | * Sets the dimensions 305 | * 306 | * @param array $dimensions 307 | * 308 | * @return $this 309 | */ 310 | public function setDimensions(array $dimensions) 311 | { 312 | $this->dimensions = $dimensions; 313 | return $this; 314 | } 315 | 316 | /** 317 | * Adds a new dimension to the dimensions array 318 | * 319 | * @param string $dimension 320 | * 321 | * @return $this 322 | */ 323 | public function addDimension($dimension) 324 | { 325 | $this->dimensions[] = $dimension; 326 | return $this; 327 | } 328 | 329 | /** 330 | * Returns the filter 331 | * 332 | * @return stdClass 333 | */ 334 | public function getFilter() 335 | { 336 | return $this->filter; 337 | } 338 | 339 | /** 340 | * Sets the filter 341 | * 342 | * @param stdClass $filter 343 | * 344 | * @return $this 345 | */ 346 | public function setFilter($filter) 347 | { 348 | if(is_object($filter)) 349 | { 350 | $this->filter = $filter; 351 | } 352 | return $this; 353 | } 354 | 355 | /** 356 | * Returns the granularity 357 | * 358 | * @return string 359 | */ 360 | public function getGranularity() 361 | { 362 | return $this->granularity; 363 | } 364 | 365 | /** 366 | * Sets the granularity 367 | * 368 | * @param string $granularity 369 | * 370 | * @return $this 371 | */ 372 | public function setGranularity($granularity) 373 | { 374 | $this->granularity = $granularity; 375 | return $this; 376 | } 377 | 378 | /** 379 | * Returns the having 380 | * 381 | * @return stdClass 382 | */ 383 | public function getHaving() 384 | { 385 | return $this->having; 386 | } 387 | 388 | /** 389 | * Sets the having 390 | * 391 | * @param stdClass $having 392 | * 393 | * @return $this 394 | */ 395 | public function setHaving($having) 396 | { 397 | if(is_object($having)) 398 | { 399 | $this->having = $having; 400 | } 401 | return $this; 402 | } 403 | 404 | /** 405 | * Returns the intervals 406 | * 407 | * @return array 408 | */ 409 | public function getIntervals() 410 | { 411 | return $this->intervals; 412 | } 413 | 414 | /** 415 | * Sets the intervals 416 | * 417 | * @param array $intervals 418 | * 419 | * @return $this 420 | */ 421 | public function setIntervals(array $intervals) 422 | { 423 | foreach($intervals as $interval) 424 | { 425 | $this->addInterval($interval); 426 | } 427 | return $this; 428 | } 429 | 430 | /** 431 | * Adds a new interval to the intervals array 432 | * 433 | * @param string $interval 434 | * 435 | * @return $this 436 | */ 437 | public function addInterval($interval) 438 | { 439 | $this->intervals[] = $interval; 440 | return $this; 441 | } 442 | 443 | /** 444 | * Returns the limitSpec 445 | * 446 | * @return stdClass 447 | */ 448 | public function getLimitSpec() 449 | { 450 | return $this->limitSpec; 451 | } 452 | 453 | /** 454 | * Sets the limitSpec 455 | * 456 | * @param stdClass $limitSpec 457 | * 458 | * @return $this 459 | */ 460 | public function setLimitSpec($limitSpec) 461 | { 462 | $this->limitSpec = $limitSpec; 463 | return $this; 464 | } 465 | 466 | /** 467 | * Returns the postAggregations 468 | * 469 | * @return array 470 | */ 471 | public function getPostAggregations() 472 | { 473 | return $this->postAggregations; 474 | } 475 | 476 | /** 477 | * Sets the postAggregations 478 | * 479 | * @param array $postAggregations 480 | * 481 | * @return $this 482 | */ 483 | public function setPostAggregations(array $postAggregations) 484 | { 485 | foreach($postAggregations as $aggregator) 486 | { 487 | $this->addPostAggregator($aggregator); 488 | } 489 | return $this; 490 | } 491 | 492 | /** 493 | * Adds a new postaggregator to the postaggregators array 494 | * 495 | * @param stdClass $aggregator 496 | * 497 | * @return $this 498 | */ 499 | public function addPostAggregator($aggregator) 500 | { 501 | if(is_object($aggregator)) 502 | { 503 | $this->postAggregations[] = $aggregator; 504 | } 505 | return $this; 506 | } 507 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryParameters/SegmentMetadataQueryParameters.php: -------------------------------------------------------------------------------- 1 | dataSource = $dataSource; 29 | $this->intervals = new Interval($intervalStart, $intervalEnd); 30 | } 31 | 32 | 33 | /** 34 | * @throws MissingParametersException 35 | */ 36 | public function validate() 37 | { 38 | $missingParams = array(); 39 | 40 | if(!isset($this->dataSource)) 41 | { 42 | $missingParams[] = 'dataSource'; 43 | } 44 | if(!isset($this->intervals)) 45 | { 46 | $missingParams[] = 'interval'; 47 | } 48 | 49 | if(count($missingParams) > 0) 50 | { 51 | throw new MissingParametersException($missingParams); 52 | } 53 | 54 | if(!$this->intervals instanceof Interval) 55 | { 56 | throw new UnexpectedTypeException($this->intervals, 'DruidFamiliar\Interval', 'For parameter intervals.'); 57 | } 58 | 59 | return true; 60 | } 61 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryParameters/SimpleGroupByQueryParameters.php: -------------------------------------------------------------------------------- 1 | setFilters(...). 71 | * 72 | * @var array 73 | */ 74 | public $filters = array(); 75 | 76 | /** 77 | * Array of json encoded strings 78 | * 79 | * Intended to be set through $this->setAggregators(...). 80 | * 81 | * @var array 82 | */ 83 | public $aggregators = array(); 84 | 85 | 86 | /** 87 | * Array of json encoded strings 88 | * 89 | * Intended to be set through $this->setPostAggregators(...). 90 | * 91 | * @var array 92 | */ 93 | public $postAggregators = array(); 94 | 95 | 96 | public function setFilePath($path) { 97 | $fileInfo = pathinfo($path); 98 | $this->baseDir = $fileInfo['dirname']; 99 | $this->filePath = $fileInfo['basename']; 100 | } 101 | 102 | /** 103 | * Configure the aggregators for this request. 104 | * 105 | * @param $aggregatorsArray array PHP Array of aggregators 106 | */ 107 | public function setAggregators($aggregatorsArray) 108 | { 109 | $this->aggregators = array(); 110 | 111 | foreach( $aggregatorsArray as $aggregator) 112 | { 113 | $this->aggregators[] = json_encode( $aggregator ); 114 | } 115 | 116 | } 117 | 118 | /** 119 | * Configure the filters for this request. 120 | * 121 | * @param $filtersArray PHP Array of aggregators 122 | */ 123 | public function setFilters($filtersArray) 124 | { 125 | $this->filters = array(); 126 | 127 | foreach( $filtersArray as $filter) 128 | { 129 | $this->filters[] = json_encode( $filter ); 130 | } 131 | 132 | } 133 | 134 | /** 135 | * Configure the post aggregators for this request. 136 | * 137 | * @param $postAggregatorsArray array PHP Array of post aggregators 138 | */ 139 | public function setPostAggregators($postAggregatorsArray) 140 | { 141 | $this->postAggregators = array(); 142 | 143 | foreach( $postAggregatorsArray as $postAggregator) 144 | { 145 | $this->postAggregators[] = json_encode( $postAggregator ); 146 | } 147 | 148 | } 149 | 150 | /** 151 | * @throws MissingParametersException 152 | */ 153 | public function validate() 154 | { 155 | $this->validateForMissingParameters(); 156 | 157 | $this->validateForEmptyParameters(); 158 | } 159 | 160 | /** 161 | * @throws MissingParametersException 162 | */ 163 | protected function validateForMissingParameters() 164 | { 165 | // Validate missing params 166 | $missingParams = array(); 167 | 168 | $requiredParams = array( 169 | 'queryType', 170 | 'dataSource', 171 | 'intervals', 172 | 'granularity', 173 | 'dimensions', 174 | 'aggregators', 175 | 'filters', 176 | 'postAggregators' 177 | ); 178 | 179 | foreach ($requiredParams as $requiredParam) { 180 | if ( !isset( $this->$requiredParam ) ) { 181 | $missingParams[] = $requiredParam; 182 | } 183 | } 184 | 185 | if ( count($missingParams) > 0 ) { 186 | throw new \DruidFamiliar\Exception\MissingParametersException($missingParams); 187 | } 188 | } 189 | 190 | /** 191 | * @throws MissingParametersException 192 | */ 193 | protected function validateForEmptyParameters() 194 | { 195 | // Validate empty params 196 | $emptyParams = array(); 197 | 198 | $requiredNonEmptyParams = array( 199 | 'queryType', 200 | 'dataSource' 201 | ); 202 | 203 | foreach ($requiredNonEmptyParams as $requiredNonEmptyParam) { 204 | if ( !isset( $this->$requiredNonEmptyParam ) ) { 205 | $emptyParams[] = $requiredNonEmptyParam; 206 | } 207 | } 208 | 209 | if ( count($emptyParams) > 0 ) { 210 | throw new \DruidFamiliar\Exception\MissingParametersException($emptyParams); 211 | } 212 | } 213 | 214 | /** 215 | * @return Interval 216 | */ 217 | public function getIntervals() 218 | { 219 | return $this->intervals; 220 | } 221 | 222 | /** 223 | * @param Interval $intervals 224 | */ 225 | public function setIntervals(Interval $intervals) 226 | { 227 | $this->intervals = $intervals; 228 | } 229 | 230 | /** 231 | * @param string|\DateTime|DruidTime $intervalStart 232 | * @param string|\DateTime|DruidTime $intervalEnd 233 | */ 234 | public function setIntervalByStartAndEnd($intervalStart, $intervalEnd) 235 | { 236 | $this->setIntervals(new Interval($intervalStart, $intervalEnd)); 237 | } 238 | 239 | /** 240 | * Adjusts the datetime to make the interval "exclusive" of the datetime. 241 | * e.g., given 242 | * startDateTime=2014-06-18T12:30:01Z and 243 | * endDateTime=2014-06-18T01:00:00Z 244 | * return and Interval containing 245 | * startDateTime=2014-06-18T12:30:00Z and 246 | * endDateTime=2014-06-18T01:00:01Z 247 | * 248 | * @param $startDateTime 249 | * @param $endDateTime 250 | * @return void 251 | */ 252 | public function setIntervalForQueryingUsingExclusiveTimes($startDateTime, $endDateTime) 253 | { 254 | $adjustedEndDateTime = new \DateTime($endDateTime); 255 | $adjustedEndDateTime->add(new \DateInterval('PT1S')); 256 | 257 | $this->setIntervals(new Interval($startDateTime, $adjustedEndDateTime)); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/DruidFamiliar/QueryParameters/TimeBoundaryQueryParameters.php: -------------------------------------------------------------------------------- 1 | dataSource = $dataSource; 20 | } 21 | 22 | /** 23 | * @throws MissingParametersException 24 | */ 25 | public function validate() 26 | { 27 | $missingParams = array(); 28 | 29 | if(!isset($this->dataSource)) 30 | { 31 | $missingParams[] = 'dataSource'; 32 | } 33 | 34 | if(count($missingParams) > 0) 35 | { 36 | throw new MissingParametersException($missingParams); 37 | } 38 | 39 | return true; 40 | } 41 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/Response/GroupByResponse.php: -------------------------------------------------------------------------------- 1 | data; 37 | } 38 | 39 | /** 40 | * Sets the data 41 | * 42 | * @param array $data 43 | * 44 | * @return $this 45 | */ 46 | public function setData(array $data) 47 | { 48 | foreach($data as $anObject) 49 | { 50 | $this->addData($anObject); 51 | } 52 | return $this; 53 | } 54 | 55 | /** 56 | * Adds an element to the data array 57 | * 58 | * @param stdClass $data 59 | * 60 | * @return $this 61 | */ 62 | public function addData($data) 63 | { 64 | if(is_object($data)) 65 | { 66 | $complete = true; 67 | foreach($this->requiredObjectFields as $property) 68 | { 69 | if(!isset($data->$property)) 70 | { 71 | $complete = false; 72 | break; 73 | } 74 | } 75 | if($complete) 76 | { 77 | $this->data[] = $data; 78 | } 79 | } 80 | else 81 | { 82 | if(is_array($data)) 83 | { 84 | $complete = true; 85 | foreach($this->requiredObjectFields as $property) 86 | { 87 | if(!isset($data[$property])) 88 | { 89 | $complete = false; 90 | break; 91 | } 92 | } 93 | if($complete) 94 | { 95 | $this->data[] = $data; 96 | } 97 | } 98 | } 99 | return $this; 100 | } 101 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/Response/TimeBoundaryResponse.php: -------------------------------------------------------------------------------- 1 | json(); 31 | 32 | $responseObj = new GroupByResponse(); 33 | $responseObj->setData($response); 34 | 35 | return $responseObj; 36 | } 37 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/ResponseHandler/JsonFormattingResponseHandler.php: -------------------------------------------------------------------------------- 1 | json(); 27 | return $response; 28 | } 29 | } -------------------------------------------------------------------------------- /src/DruidFamiliar/ResponseHandler/TimeBoundaryResponseHandler.php: -------------------------------------------------------------------------------- 1 | json(); 30 | 31 | if(empty($response)) 32 | { 33 | throw new Exception('Unknown data source.'); 34 | } 35 | 36 | if(!isset ($response[0]['result'])) 37 | { 38 | throw new Exception('Unexpected response format.'); 39 | } 40 | if(!isset ($response[0]['result']['minTime'])) 41 | { 42 | throw new Exception('Unexpected response format - response did not include minTime.'); 43 | } 44 | if(!isset ($response[0]['result']['maxTime'])) 45 | { 46 | throw new Exception('Unexpected response format - response did not include maxTime.'); 47 | } 48 | 49 | $responseObj = new TimeBoundaryResponse(); 50 | 51 | $responseObj->minTime = $response[0]['result']['minTime']; 52 | $responseObj->maxTime = $response[0]['result']['maxTime']; 53 | 54 | return $responseObj; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/Abstracts/AbstractTaskParametersTest.php: -------------------------------------------------------------------------------- 1 | getMockForAbstractClass('DruidFamiliar\Abstracts\AbstractTaskParameters'); 17 | $stub->expects($this->once())->method('validate')->will($this->returnValue(true)); 18 | 19 | $isValid = $stub->isValid(); 20 | 21 | $this->assertTrue($isValid); 22 | } 23 | 24 | public function testIsNotValid() 25 | { 26 | /** 27 | * @var AbstractTaskParameters $stub 28 | */ 29 | $stub = $this->getMockForAbstractClass('DruidFamiliar\Abstracts\AbstractTaskParameters'); 30 | $stub->expects($this->once())->method('validate')->will($this->throwException(new MissingParametersException(array('a_missing_parameter_name')))); 31 | 32 | $isValid = $stub->isValid(); 33 | 34 | $this->assertFalse($isValid); 35 | } 36 | } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/DruidTimeTest.php: -------------------------------------------------------------------------------- 1 | getMock('DruidFamiliar\DruidTime', array('formatTimeForDruid'), array(new \DateTime('2014-01-01T04:20'))); 21 | 22 | $mockInterval 23 | ->expects($this->once()) 24 | ->method('formatTimeForDruid'); 25 | 26 | /** 27 | * @var Interval $mockInterval 28 | */ 29 | $mockInterval->__toString(); 30 | } 31 | 32 | public function testFormatTimeForDruid() 33 | { 34 | $interval = new DruidTime(new DateTime('2014-01-01')); 35 | $this->assertEquals('2014-01-01T00:00:00Z', $interval->formatTimeForDruid()); 36 | 37 | $interval = new DruidTime(new DateTime('2014-01-01 04:20')); 38 | $this->assertEquals('2014-01-01T04:20:00Z', $interval->formatTimeForDruid()); 39 | 40 | $interval = new DruidTime(new DateTime('2014-01-01T04:20')); 41 | $this->assertEquals('2014-01-01T04:20:00Z', $interval->formatTimeForDruid()); 42 | } 43 | 44 | /** 45 | * Standard date time strings will default to local time zone. 46 | * 47 | * We want UTC time only. At least the output should not be modified. 48 | */ 49 | public function testCanUseStandardDateTimeStrings() 50 | { 51 | $interval = new DruidTime(new DateTime('2014-01-01 00:00')); 52 | 53 | $this->assertEquals('2014-01-01T00:00:00Z', $interval->formatTimeForDruid()); 54 | } 55 | 56 | /** 57 | * Common ISO style seen on many web services, including Druid. 58 | * 59 | * Take ISO, replace time zone offsets with 'Z'. We always work in Zulu/UTC/GMT time here. 60 | * 61 | * Y-m-d\TH:i:s\Z 62 | * 63 | * eBay and Amazon also use this style. 64 | * 65 | */ 66 | public function testCanUseDruidDateTimeStrings() 67 | { 68 | $interval = new DruidTime(new DateTime('2014-01-01T00:00:10Z')); 69 | 70 | $this->assertEquals('2014-01-01T00:00:10Z', $interval->formatTimeForDruid()); 71 | } 72 | 73 | /** 74 | * Actual ISO 8601 standard spec includes time zone offsets. 75 | * 76 | * Y-m-d\TH:i:sO 77 | * 78 | */ 79 | public function testCanUseISO8601DateTimeStrings() 80 | { 81 | $interval = new DruidTime(new DateTime('2014-01-01T00:20:00-0000')); 82 | 83 | $this->assertEquals('2014-01-01T00:20:00Z', $interval->formatTimeForDruid()); 84 | } 85 | 86 | public function testCanSetWithString() 87 | { 88 | $interval = new DruidTime( '2014-01-01T00:20' ); 89 | 90 | $this->assertEquals('2014-01-01T00:20:00Z', $interval->formatTimeForDruid()); 91 | } 92 | 93 | public function testCanSetWithDateTime() 94 | { 95 | $interval = new DruidTime( new DateTime( '2014-01-01T00:20' ) ); 96 | 97 | $this->assertEquals('2014-01-01T00:20:00Z', $interval->formatTimeForDruid()); 98 | } 99 | 100 | public function testCanSetWithAnotherDruidTime() 101 | { 102 | $dtA = new DruidTime( '2014-01-02T00:20' ); 103 | $interval = new DruidTime( $dtA ); 104 | 105 | $this->assertEquals('2014-01-02T00:20:00Z', $interval->formatTimeForDruid()); 106 | 107 | } 108 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/Exception/EmptyParametersExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage(); 26 | 27 | $this->assertContains('Empty parameters', $msg, '', true); // Case insensitive 28 | $this->assertContains('param1', $msg, '', false); // Case sensitive (they are parameters!) 29 | $this->assertContains('param2', $msg, '', false); // Case sensitive (they are parameters!) 30 | } 31 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/Exception/MissingParametersExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage(); 15 | 16 | $this->assertContains('Missing parameters', $msg, '', true); // Case insensitive 17 | $this->assertContains('param1', $msg, '', false); // Case sensitive (they are parameters!) 18 | $this->assertContains('param2', $msg, '', false); // Case sensitive (they are parameters!) 19 | } 20 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/IntervalTest.php: -------------------------------------------------------------------------------- 1 | getMock('DruidFamiliar\Interval', array('getIntervalsString'), array('2014-01-01T04:20', '2015-01-01T4:20')); 18 | 19 | $mockInterval->expects($this->once())->method('getIntervalsString'); 20 | 21 | /** 22 | * @var Interval $mockInterval 23 | */ 24 | $mockInterval->__toString(); 25 | 26 | } 27 | 28 | public function testGetIntervalsString() 29 | { 30 | $interval = new Interval('2014-01-01', '2015-01-01'); 31 | $this->assertEquals('2014-01-01T00:00:00Z/2015-01-01T00:00:00Z', $interval->getIntervalsString()); 32 | 33 | $interval = new Interval('2014-01-01 04:20', '2015-01-01 4:20'); 34 | $this->assertEquals('2014-01-01T04:20:00Z/2015-01-01T04:20:00Z', $interval->getIntervalsString()); 35 | 36 | $interval = new Interval('2014-01-01T04:20', '2015-01-01T4:20'); 37 | $this->assertEquals('2014-01-01T04:20:00Z/2015-01-01T04:20:00Z', $interval->getIntervalsString()); 38 | } 39 | 40 | /** 41 | * Standard date time strings will default to local time zone. 42 | * 43 | * We want UTC time only. At least the output should not be modified. 44 | */ 45 | public function testCanUseStandardDateTimeStrings() 46 | { 47 | $interval = new Interval('2014-01-01 00:00', '2015-01-01 04:20'); 48 | 49 | $this->assertEquals('2014-01-01T00:00:00Z/2015-01-01T04:20:00Z', $interval->getIntervalsString()); 50 | } 51 | 52 | /** 53 | * Common ISO style seen on many web services, including Druid. 54 | * 55 | * Take ISO, replace time zone offsets with 'Z'. We always work in Zulu/UTC/GMT time here. 56 | * 57 | * Y-m-d\TH:i:s\Z 58 | * 59 | * eBay and Amazon also use this style. 60 | * 61 | */ 62 | public function testCanUseDruidDateTimeStrings() 63 | { 64 | $interval = new Interval('2014-01-01T00:00:00Z', '2015-01-01T04:20:00Z'); 65 | 66 | $this->assertEquals('2014-01-01T00:00:00Z/2015-01-01T04:20:00Z', $interval->getIntervalsString()); 67 | } 68 | 69 | /** 70 | * Actual ISO 8601 standard spec includes time zone offsets. 71 | * 72 | * Y-m-d\TH:i:sO 73 | * 74 | */ 75 | public function testCanUseISO8601DateTimeStrings() 76 | { 77 | $interval = new Interval('2014-01-01T00:00:00-0000', '2015-01-01T04:20:00-0700'); 78 | 79 | $this->assertEquals('2014-01-01T00:00:00Z/2015-01-01T04:20:00Z', $interval->getIntervalsString()); 80 | } 81 | 82 | public function testGetIntervalsStringWithMissingData() 83 | { 84 | $interval = new Interval('2014-01-01T00:00:00-0000', '2015-01-01T04:20:00-0700'); 85 | $interval->intervalStart = NULL; 86 | $this->setExpectedException('DruidFamiliar\Exception\MissingParametersException', "intervalStart"); 87 | $interval->getIntervalsString(); 88 | } 89 | 90 | public function testGetIntervalsStringWithInvalidData() 91 | { 92 | $interval = new Interval('2014-01-01T00:00:00-0000', '2015-01-01T04:20:00-0700'); 93 | $interval->intervalStart = '2014-01-01T00:00:00-0000'; 94 | $this->setExpectedException('DruidFamiliar\Exception\UnexpectedTypeException', "intervalStart"); 95 | $interval->getIntervalsString(); 96 | } 97 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryExecutor/DruidNodeDruidQueryExecutorTest.php: -------------------------------------------------------------------------------- 1 | getBaseUrl(); 25 | $this->assertEquals('https://1.2.3.4:1234/home/', $b); 26 | } 27 | 28 | /** 29 | * @depends testGetBaseUrlAssemblesCorrectEndpoint 30 | */ 31 | public function testDefaultsToDruidV2Endpoint() 32 | { 33 | $c = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 34 | $b = $c->getBaseUrl(); 35 | $this->assertEquals('http://1.2.3.4:1234/druid/v2/', $b); 36 | } 37 | 38 | /** 39 | * @depends testGetBaseUrlAssemblesCorrectEndpoint 40 | */ 41 | public function testDefaultsToHttp() 42 | { 43 | $c = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234', '/home/'); 44 | $b = $c->getBaseUrl(); 45 | $h = array("content-type" => "application/json;charset=utf-8"); 46 | $this->assertEquals('http://1.2.3.4:1234/home/', $b); 47 | $this->assertEquals($h, $c->getHeaders()); 48 | } 49 | 50 | public function testCreateRequest() 51 | { 52 | $c = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234', '/mypath/'); 53 | $queryGenerator = new TimeBoundaryDruidQueryGenerator(); 54 | $params = new TimeBoundaryQueryParameters('some-datasource'); 55 | $query = $queryGenerator->generateQuery($params); 56 | 57 | $req = $c->createRequest( $query ); 58 | 59 | $this->assertEquals('1.2.3.4', $req->getHost() ); 60 | $this->assertEquals('POST', $req->getMethod() ); 61 | $this->assertEquals('/mypath/', $req->getPath() ); 62 | $this->assertEquals('1234', $req->getPort() ); 63 | } 64 | 65 | public function testExecuteQueryPassesResponseToHandleResponse() 66 | { 67 | // Create fake json response 68 | $mockResponse = new Response(200); 69 | 70 | // Create fake request 71 | $mockRequest = $this->getMockBuilder('MockRequest') 72 | ->setMethods(array('send')) 73 | ->getMock(); 74 | $mockRequest->expects($this->once()) 75 | ->method('send') 76 | ->willReturn($mockResponse); 77 | 78 | // Create fake connection 79 | $mockDruidQueryExecutor = $this->getMockBuilder('\DruidFamiliar\QueryExecutor\DruidNodeDruidQueryExecutor') 80 | ->setConstructorArgs(array('1.2.3.4', '1234')) 81 | ->setMethods(array('createRequest')) 82 | ->getMock(); 83 | $mockDruidQueryExecutor->expects($this->once()) 84 | ->method('createRequest') 85 | ->willReturn( $mockRequest ); 86 | /** 87 | * @var \DruidFamiliar\QueryExecutor\DruidNodeDruidQueryExecutor $mockDruidQueryExecutor 88 | */ 89 | 90 | // Create fake query params 91 | $mockQueryParams = $this->getMockBuilder('DruidFamiliar\Interfaces\IDruidQueryParameters') 92 | ->getMock(); 93 | 94 | // Create fake query generator 95 | $mockGeneratedQuery = '{"hey":123}'; 96 | $mockQueryGenerator = $this->getMockBuilder('DruidFamiliar\Interfaces\IDruidQueryGenerator') 97 | ->setMethods(array('generateQuery')) 98 | ->getMock(); 99 | // Expect it to be called with given query params and return the json body 100 | $mockQueryGenerator->expects($this->once()) 101 | ->method('generateQuery') 102 | ->with($mockQueryParams) 103 | ->willReturn($mockGeneratedQuery); 104 | 105 | // Create fake response handler to verify it is called with the returned json body 106 | $mockResponseHandler = $this->getMock('DruidFamiliar\Interfaces\IDruidQueryResponseHandler'); 107 | $mockResponseHandler->expects($this->once()) 108 | ->method('handleResponse') 109 | ->with($mockResponse); 110 | 111 | $mockDruidQueryExecutor->executeQuery($mockQueryGenerator, $mockQueryParams, $mockResponseHandler); 112 | } 113 | 114 | public function testDefaultsToPostMethod() 115 | { 116 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 117 | $this->assertEquals('POST', $a->getHttpMethod()); 118 | } 119 | 120 | public function testSendingUsingPostMethod() 121 | { 122 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 123 | $a->setHttpMethod('POST'); 124 | $request = $a->createRequest('{"hey":123}'); 125 | $this->assertEquals( 'POST', $request->getMethod() ); 126 | } 127 | 128 | public function testSendingUsingPostMethodIsCaseInsensitive() 129 | { 130 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 131 | $a->setHttpMethod('post'); 132 | $request = $a->createRequest('{"hey":123}'); 133 | $this->assertEquals( 'POST', $request->getMethod() ); 134 | } 135 | 136 | public function testProvidesBodyWhenPosting() 137 | { 138 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 139 | $a->setHttpMethod('POST'); 140 | $request = $a->createRequest('{"hey":123}'); 141 | $this->assertEquals( 'POST', $request->getMethod() ); 142 | 143 | $this->assertContains( 'hey', $request->getBody()->__toString() ); 144 | $this->assertContains( '123', $request->getBody()->__toString() ); 145 | } 146 | 147 | public function testSendingUsingGetMethod() 148 | { 149 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 150 | $a->setHttpMethod('GET'); 151 | $request = $a->createRequest('{"hey":123}'); 152 | $this->assertEquals( 'GET', $request->getMethod() ); 153 | } 154 | 155 | public function testSendingUsingGetMethodIsCaseInsensitive() 156 | { 157 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 158 | $a->setHttpMethod('get'); 159 | $request = $a->createRequest('{"hey":123}'); 160 | $this->assertEquals( 'GET', $request->getMethod() ); 161 | 162 | } 163 | 164 | public function testSendingUsingGetMethodPutsRequestInQueryParameters() 165 | { 166 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 167 | $a->setHttpMethod('GET'); 168 | $a->setEndPoint('/somewhere/some/place'); 169 | $request = $a->createRequest('{"hey":123}'); 170 | 171 | $query = $request->getQuery()->getAll(); 172 | $this->assertArrayHasKey('hey', $query ); 173 | $this->assertEquals( '123', $query['hey'] ); 174 | } 175 | 176 | public function testSendingUsingGetMethodMaintainsQueryParameters() 177 | { 178 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 179 | $a->setHttpMethod('GET'); 180 | $a->setEndPoint('/somewhere/some/place?query=test&stuff=works'); 181 | $request = $a->createRequest('{"hey":123}'); 182 | 183 | $query = $request->getQuery()->getAll(); 184 | $this->assertArrayHasKey('query', $query ); 185 | $this->assertEquals( 'test', $query['query'] ); 186 | $this->assertArrayHasKey('stuff', $query ); 187 | $this->assertEquals( 'works', $query['stuff'] ); 188 | 189 | // Does not stomp the query params from request 190 | $this->assertArrayHasKey('hey', $query ); 191 | $this->assertEquals( '123', $query['hey'] ); 192 | } 193 | 194 | public function testSendingUsingGetMethodPrefersRequestOverQueryParameters() 195 | { 196 | $a = new DruidNodeDruidQueryExecutor('1.2.3.4', '1234'); 197 | $a->setHttpMethod('GET'); 198 | $a->setEndPoint('/somewhere/some/place?hey=abc'); 199 | $request = $a->createRequest('{"hey":123}'); 200 | 201 | $query = $request->getQuery()->getAll(); 202 | // Does not stomp the query params from request 203 | $this->assertArrayHasKey('hey', $query ); 204 | $this->assertEquals( '123', $query['hey'] ); 205 | $this->assertNotEquals( 'abc', $query['hey'] ); 206 | } 207 | } 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryGenerator/GroupByQueryGeneratorTest.php: -------------------------------------------------------------------------------- 1 | setDataSource('referral-visit-old-format'); 28 | $parametersInstance->setGranularity('all'); 29 | $parametersInstance->setDimensions(array('facility_id', 'referral_id', 'group')); 30 | 31 | $filter = new stdClass(); 32 | $filter->type = 'selector'; 33 | $filter->dimension = 'company_id'; 34 | $filter->value = 1; 35 | $parametersInstance->setFilter($filter); 36 | 37 | $anAggregation = new stdClass(); 38 | $anAggregation->type = 'longSum'; 39 | $anAggregation->name = 'quantity'; 40 | $anAggregation->fieldName = 'count'; 41 | 42 | $parametersInstance->setAggregations(array($anAggregation)); 43 | $parametersInstance->setIntervals(array('2008-01-01T00:00:00.000/2012-01-03T00:00:00.000')); 44 | 45 | $queryInstance = new GroupByQueryGenerator(); 46 | $expectedQuery = '{"queryType":"groupBy","dataSource":"referral-visit-old-format","dimensions":["facility_id","referral_id","group"],"granularity":"all","filter":{"type":"selector","dimension":"company_id","value":1},"aggregations":[{"type":"longSum","name":"quantity","fieldName":"count"}],"intervals":["2008-01-01T00:00:00.000\/2012-01-03T00:00:00.000"]}'; 47 | 48 | $query = $queryInstance->generateQuery($parametersInstance); 49 | $this->assertEquals($expectedQuery, $query); 50 | } 51 | 52 | /** 53 | * @expectedException Exception 54 | */ 55 | public function testGenerateQueryInstanceofMismatch() 56 | { 57 | $paramsInstance = $this->getMock('DruidFamiliar\Interfaces\IDruidQueryParameters'); 58 | 59 | $queryInstance = new GroupByQueryGenerator(); 60 | $queryInstance->generateQuery($paramsInstance); 61 | } 62 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryGenerator/SegmentMetadataDruidQueryGeneratorTest.php: -------------------------------------------------------------------------------- 1 | generateQuery($p); 20 | 21 | $query = json_decode( $query, true ); 22 | 23 | $this->assertArrayHasKey('queryType', $query); 24 | $this->assertArrayHasKey('intervals', $query); 25 | $this->assertArrayHasKey('dataSource', $query, "Generated query must include required parameters."); 26 | 27 | $this->assertEquals( 'segmentMetadata', $query['queryType'], "Generated query must have segmentMetadata for its queryType."); 28 | $this->assertEquals( $mockDataSourceName, $query['dataSource'], "Generated query must use provided dataSource."); 29 | $this->assertEquals('1970-01-01T01:30:00Z/3030-01-01T01:30:00Z', $query['intervals'] ); 30 | } 31 | 32 | } 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryGenerator/SimpleGroupByDruidQueryGeneratorTest.php: -------------------------------------------------------------------------------- 1 | setIntervalByStartAndEnd('1981-01-01T4:20', '2012-03-01T3:00'); 19 | $params->granularityType = 'uniform'; 20 | $params->granularity = 'DAY'; 21 | $params->dataSource = $this->mockDataSourceName; 22 | $params->format = 'json'; 23 | $params->timeDimension = 'date_dim'; 24 | $params->dimensions = array('one_dim', 'two_dim'); 25 | 26 | $params->setFilePath('/another/file/path/to/a/file.bebop'); 27 | 28 | $params->setFilters(array( 29 | array('type' => 'or', 'fields' => array( 30 | array('type' => 'selector', 'dimension' => 'one_dim', 'value' => '10'), 31 | array('type' => 'selector', 'dimension' => 'one_dim', 'value' => '11') 32 | )) 33 | )); 34 | 35 | $params->setAggregators(array( 36 | array('type' => 'count', 'name' => 'count'), 37 | array('type' => 'longSum', 'name' => 'total_referral_count', 'fieldName' => 'referral_count') 38 | )); 39 | 40 | $params->setPostAggregators(array( 41 | array( 42 | 'type' => 'arithmetic', 43 | 'name' => 'inactive_patients', 44 | 'fn' => '-', // Subtraction operator 45 | 'fields' => array( 46 | array('type' => 'fieldAccess', 'fieldName' => 'referral_count'), 47 | array('type' => 'fieldAccess', 'fieldName' => 'active_patients'), 48 | ), 49 | ), 50 | array( 51 | 'type' => 'javascript', 52 | 'name' => 'shrinkage', 53 | 'fieldNames' => array("referral_count", "discharged_patients"), 54 | 'function' => 'function(total, discharge) { return 100 * (total /w discharge); }' 55 | ), 56 | )); 57 | 58 | return $params; 59 | } 60 | 61 | public function testGenerateQueryReturnsJSONString() 62 | { 63 | $params = $this->getMockSimpleGroupByQueryParameters(); 64 | 65 | $q = new \DruidFamiliar\QueryGenerator\SimpleGroupByDruidQueryGenerator(); 66 | 67 | $query = $q->generateQuery($params); 68 | 69 | $this->assertJson( $query ); 70 | return $query; 71 | } 72 | 73 | /** 74 | * @depends testGenerateQueryReturnsJSONString 75 | */ 76 | public function testGenerateQueryIncludesDataSource($jsonString) 77 | { 78 | $query = json_decode( $jsonString, true ); 79 | 80 | $this->assertArrayHasKey('queryType', $query); 81 | $this->assertArrayHasKey('dataSource', $query, "Generated query must include required parameters."); 82 | } 83 | 84 | /** 85 | * @depends testGenerateQueryReturnsJSONString 86 | */ 87 | public function testGenerateQueryIncludesAggregations($jsonString) 88 | { 89 | 90 | $query = json_decode( $jsonString, true ); 91 | 92 | $this->assertArrayHasKey('aggregations', $query); 93 | $this->assertCount( 2, $query['aggregations'] ); 94 | 95 | $aggs = $query['aggregations']; 96 | 97 | $firstAgg = $aggs[0]; 98 | $secondAgg = $aggs[1]; 99 | 100 | $this->assertEquals( "count", $firstAgg['type'] ); 101 | $this->assertEquals( "count", $firstAgg['name'] ); 102 | $this->assertEquals( "longSum", $secondAgg['type'] ); 103 | $this->assertEquals( "total_referral_count", $secondAgg['name'] ); 104 | $this->assertEquals( "referral_count", $secondAgg['fieldName'] ); 105 | } 106 | 107 | /** 108 | * @depends testGenerateQueryReturnsJSONString 109 | */ 110 | public function testGenerateQueryIncludesFilters($jsonString) 111 | { 112 | 113 | $query = json_decode( $jsonString, true ); 114 | 115 | $this->assertArrayHasKey('filter', $query); 116 | $firstFilter = $query['filter']; 117 | 118 | $this->assertEquals( "or", $firstFilter['type'] ); 119 | $this->assertArrayHasKey('fields', $firstFilter ); 120 | $this->assertCount( 2, $firstFilter['fields'] ); 121 | 122 | $orFilters = $firstFilter['fields']; 123 | $this->assertCount( 2, $orFilters ); 124 | 125 | $firstOrFilter = $orFilters[0]; 126 | $secondOrFilter = $orFilters[1]; 127 | 128 | $this->assertEquals( "selector", $firstOrFilter['type'] ); 129 | $this->assertEquals( "one_dim", $firstOrFilter['dimension'] ); 130 | $this->assertEquals( "10", $firstOrFilter['value'] ); 131 | $this->assertEquals( "selector", $secondOrFilter['type'] ); 132 | $this->assertEquals( "one_dim", $secondOrFilter['dimension'] ); 133 | $this->assertEquals( "11", $secondOrFilter['value'] ); 134 | 135 | } 136 | 137 | /** 138 | * @depends testGenerateQueryReturnsJSONString 139 | */ 140 | public function testGenerateQueryIncludesPostAggregations($jsonString) 141 | { 142 | 143 | $query = json_decode( $jsonString, true ); 144 | 145 | $this->assertArrayHasKey('postAggregations', $query); 146 | $this->assertCount( 2, $query['postAggregations'] ); 147 | 148 | $postAggs = $query['postAggregations']; 149 | 150 | $firstPostAgg = $postAggs[0]; 151 | $secondPostAgg = $postAggs[1]; 152 | 153 | $this->assertEquals( "arithmetic", $firstPostAgg['type'] ); 154 | $this->assertEquals( "inactive_patients", $firstPostAgg['name'] ); 155 | $this->assertEquals( "-", $firstPostAgg['fn'] ); 156 | $this->assertEquals( "fieldAccess", $firstPostAgg['fields'][0]['type'] ); 157 | $this->assertEquals( "referral_count", $firstPostAgg['fields'][0]['fieldName'] ); 158 | $this->assertEquals( "fieldAccess", $firstPostAgg['fields'][1]['type'] ); 159 | $this->assertEquals( "active_patients", $firstPostAgg['fields'][1]['fieldName'] ); 160 | 161 | $this->assertEquals( "javascript", $secondPostAgg['type'] ); 162 | $this->assertEquals( "shrinkage", $secondPostAgg['name'] ); 163 | $this->assertCount( 2, $secondPostAgg['fieldNames'] ); 164 | $this->assertEquals( "referral_count", $secondPostAgg['fieldNames'][0] ); 165 | $this->assertEquals( "discharged_patients", $secondPostAgg['fieldNames'][1] ); 166 | $this->assertEquals( 167 | "function(total, discharge) { return 100 * (total /w discharge); }", 168 | $secondPostAgg['function'] 169 | ); 170 | 171 | } 172 | 173 | public function testGenerateQueryRequiresDataSource() 174 | { 175 | try { 176 | $params = $this->getMockSimpleGroupByQueryParameters(); 177 | $params->dataSource = NULL; 178 | 179 | $q = new \DruidFamiliar\QueryGenerator\SimpleGroupByDruidQueryGenerator(); 180 | $query = $q->generateQuery($params); 181 | } catch (MissingParametersException $e) { 182 | $this->assertContains('dataSource', $e->missingParameters, "Returned missing parameters: " . join(',', $e->missingParameters)); 183 | return; 184 | } 185 | 186 | $this->fail('An expected exception was not raised'); 187 | } 188 | 189 | public function testGenerateQueryRequiresQueryType() 190 | { 191 | try { 192 | $params = $this->getMockSimpleGroupByQueryParameters(); 193 | $params->queryType = NULL; 194 | 195 | $q = new \DruidFamiliar\QueryGenerator\SimpleGroupByDruidQueryGenerator($params); 196 | $query = $q->generateQuery($params); 197 | } catch (MissingParametersException $e) { 198 | $this->assertContains('queryType', $e->missingParameters); 199 | return; 200 | } 201 | 202 | $this->fail('An expected exception was not raised'); 203 | } 204 | 205 | public function testGenerateQueryRequiresIntervals() 206 | { 207 | $this->setExpectedException('\DruidFamiliar\Exception\MissingParametersException'); 208 | 209 | $params = new SimpleGroupByQueryParameters(); 210 | 211 | $params = $this->getMockSimpleGroupByQueryParameters(); 212 | $params->intervals = NULL; 213 | 214 | $q = new \DruidFamiliar\QueryGenerator\SimpleGroupByDruidQueryGenerator($params); 215 | $q->generateQuery($params); 216 | } 217 | 218 | 219 | public function testGenerateQueryHandlesNotHavingAggregations() 220 | { 221 | $params = $this->getMockSimpleGroupByQueryParameters(); 222 | $params->setAggregators(array()); 223 | 224 | $q = new \DruidFamiliar\QueryGenerator\SimpleGroupByDruidQueryGenerator($params); 225 | 226 | $query = $q->generateQuery($params); 227 | 228 | $this->assertJson( $query ); 229 | 230 | $query = json_decode( $query, true ); 231 | 232 | $this->assertArrayNotHasKey('aggregations', $query); 233 | } 234 | 235 | 236 | public function testGenerateQueryHandlesNotHavingPostAggregations() 237 | { 238 | $params = $this->getMockSimpleGroupByQueryParameters(); 239 | $params->setPostAggregators(array()); 240 | 241 | $q = new \DruidFamiliar\QueryGenerator\SimpleGroupByDruidQueryGenerator($params); 242 | 243 | $query = $q->generateQuery($params); 244 | 245 | $this->assertJson( $query ); 246 | 247 | $query = json_decode( $query, true ); 248 | 249 | $this->assertArrayNotHasKey('postAggregations', $query); 250 | } 251 | 252 | public function testGenerateQueryHandlesStringGranularities() 253 | { 254 | $params = $this->getMockSimpleGroupByQueryParameters(); 255 | $params->granularity = 'DAY'; 256 | 257 | $q = new \DruidFamiliar\QueryGenerator\SimpleGroupByDruidQueryGenerator(); 258 | 259 | $query = $q->generateQuery($params); 260 | 261 | 262 | $jsonString = json_decode( $query, true ); 263 | 264 | $this->assertArrayHasKey('granularity', $jsonString); 265 | $this->assertEquals( 'DAY', $jsonString['granularity'] ); 266 | } 267 | 268 | public function testGenerateQueryHandlesObjectGranularities() 269 | { 270 | $params = $this->getMockSimpleGroupByQueryParameters(); 271 | $params->granularity = array('type'=> "period", "period"=>"P3M"); 272 | 273 | $q = new \DruidFamiliar\QueryGenerator\SimpleGroupByDruidQueryGenerator(); 274 | 275 | $query = $q->generateQuery($params); 276 | 277 | 278 | $jsonString = json_decode( $query, true ); 279 | 280 | $this->assertArrayHasKey('granularity', $jsonString); 281 | $this->assertCount( 2, $jsonString['granularity'] ); 282 | $this->assertArrayHasKey('type', $jsonString['granularity']); 283 | $this->assertArrayHasKey('period', $jsonString['granularity']); 284 | 285 | $this->assertEquals( 'period', $jsonString['granularity']['type'] ); 286 | $this->assertEquals( 'P3M', $jsonString['granularity']['period'] ); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryGenerator/TimeBoundaryDruidQueryGeneratorTest.php: -------------------------------------------------------------------------------- 1 | generateQuery($p); 18 | 19 | $query = json_decode( $query, true ); 20 | 21 | $this->assertArrayHasKey('queryType', $query); 22 | $this->assertArrayHasKey('dataSource', $query, "Generated query must include required parameters."); 23 | 24 | $this->assertEquals('timeBoundary', $query['queryType'], "Generated query must have timeBoundary for its queryType."); 25 | $this->assertEquals($mockDataSourceName, $query['dataSource'], "Generated query must use provided dataSource."); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryParameters/GroupByQueryParametersTest.php: -------------------------------------------------------------------------------- 1 | setDataSource('referral-visit-old-format'); 26 | $parametersInstance->setGranularity('all'); 27 | $parametersInstance->setDimensions(array('facility_id', 'referral_id', 'group')); 28 | $filter = new stdClass(); 29 | $filter->type = 'selector'; 30 | $filter->dimension = 'company_id'; 31 | $filter->value = 1; 32 | $parametersInstance->setFilter($filter); 33 | $anAggregation = new stdClass(); 34 | $anAggregation->type = 'longSum'; 35 | $anAggregation->name = 'quantity'; 36 | $anAggregation->fieldName = 'count'; 37 | $parametersInstance->setAggregations(array($anAggregation)); 38 | $parametersInstance->setIntervals(array('2008-01-01T00:00:00.000/2012-01-03T00:00:00.000')); 39 | $valid = $parametersInstance->validate(); 40 | $this->assertTrue($valid); 41 | } 42 | 43 | /** 44 | * Missing a required parameter (filter) 45 | * 46 | * @expectedException \DruidFamiliar\Exception\MissingParametersException 47 | */ 48 | public function testMissingParametersException() 49 | { 50 | $parametersInstance = new GroupByQueryParameters(); 51 | $parametersInstance->setDataSource('referral-visit-old-format'); 52 | $parametersInstance->setDimensions(array('facility_id', 'referral_id', 'group')); 53 | $filter = new stdClass(); 54 | $filter->type = 'selector'; 55 | $filter->dimension = 'company_id'; 56 | $filter->value = 1; 57 | $parametersInstance->setFilter($filter); 58 | $anAggregation = new stdClass(); 59 | $anAggregation->type = 'longSum'; 60 | $anAggregation->name = 'quantity'; 61 | $anAggregation->fieldName = 'count'; 62 | $parametersInstance->setAggregations(array($anAggregation)); 63 | $parametersInstance->setIntervals(array('2008-01-01T00:00:00.000/2012-01-03T00:00:00.000')); 64 | $valid = $parametersInstance->validate(); 65 | } 66 | 67 | /** 68 | * A non empty parameter (granularity) 69 | * 70 | * @expectedException \DruidFamiliar\Exception\EmptyParametersException 71 | */ 72 | public function testEmptyParametersException() 73 | { 74 | $parametersInstance = new GroupByQueryParameters(); 75 | $parametersInstance->setDataSource('referral-visit-old-format'); 76 | $parametersInstance->setGranularity(''); 77 | $parametersInstance->setDimensions(array('facility_id', 'referral_id', 'group')); 78 | $filter = new stdClass(); 79 | $filter->type = 'selector'; 80 | $filter->dimension = 'company_id'; 81 | $filter->value = 1; 82 | $parametersInstance->setFilter($filter); 83 | $anAggregation = new stdClass(); 84 | $anAggregation->type = 'longSum'; 85 | $anAggregation->name = 'quantity'; 86 | $anAggregation->fieldName = 'count'; 87 | $parametersInstance->setAggregations(array($anAggregation)); 88 | $parametersInstance->setIntervals(array('2008-01-01T00:00:00.000/2012-01-03T00:00:00.000')); 89 | $valid = $parametersInstance->validate(); 90 | } 91 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryParameters/SegmentMetadataQueryParametersTest.php: -------------------------------------------------------------------------------- 1 | expectedIntervalString = $this->mockStartTimeString . ':00Z/' . $this->mockEndTimeString . 'T00:00:00Z'; 20 | 21 | $this->parametersInstance = new SegmentMetadataQueryParameters($this->mockDataSource, $this->mockStartTimeString, $this->mockEndTimeString); 22 | } 23 | 24 | public function testValidate() 25 | { 26 | /** 27 | * @var SegmentMetadataQueryParameters $parametersInstance 28 | */ 29 | $parametersInstance = $this->parametersInstance; 30 | 31 | $parametersInstance->validate(); 32 | 33 | $this->assertEquals($this->mockDataSource, $parametersInstance->dataSource); 34 | $this->assertEquals($this->expectedIntervalString, $parametersInstance->intervals); 35 | 36 | return $parametersInstance; 37 | } 38 | 39 | /** 40 | * @depends testValidate 41 | */ 42 | public function testIntervalsValue() 43 | { 44 | /** 45 | * @var SegmentMetadataQueryParameters $parametersInstance 46 | */ 47 | $parametersInstance = $this->parametersInstance; 48 | 49 | $this->assertInstanceOf('DruidFamiliar\Interval', $parametersInstance->intervals); 50 | $this->assertEquals($this->expectedIntervalString, $parametersInstance->intervals); 51 | 52 | return $parametersInstance; 53 | } 54 | 55 | /** 56 | * @depends testValidate 57 | */ 58 | public function testValidateWithMissingDataSource() 59 | { 60 | /** 61 | * @var SegmentMetadataQueryParameters $parametersInstance 62 | */ 63 | $parametersInstance = $this->parametersInstance; 64 | 65 | $parametersInstance->dataSource = NULL; 66 | 67 | $this->setExpectedException('DruidFamiliar\Exception\MissingParametersException'); 68 | 69 | $parametersInstance->validate(); 70 | } 71 | 72 | /** 73 | * @depends testValidate 74 | */ 75 | public function testValidateWithMissingInterval() 76 | { 77 | /** 78 | * @var SegmentMetadataQueryParameters $parametersInstance 79 | */ 80 | $parametersInstance = $this->parametersInstance; 81 | 82 | $parametersInstance->intervals = NULL; 83 | 84 | $this->setExpectedException('DruidFamiliar\Exception\MissingParametersException'); 85 | 86 | $parametersInstance->validate(); 87 | } 88 | 89 | /** 90 | * @depends testValidate 91 | */ 92 | public function testValidateWithMalformedInterval() 93 | { 94 | /** 95 | * @var SegmentMetadataQueryParameters $parametersInstance 96 | */ 97 | $parametersInstance = $this->parametersInstance; 98 | 99 | $parametersInstance->intervals = 1; 100 | 101 | $this->setExpectedException('DruidFamiliar\Exception\UnexpectedTypeException'); 102 | 103 | $parametersInstance->validate(); 104 | } 105 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryParameters/SimpleGroupByQueryParametersTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('DruidFamiliar\QueryParameters\SimpleGroupByQueryParameters') 14 | ->setMethods(null) 15 | ->getMock(); 16 | 17 | $origStartDateTime = '2014-06-18T12:30:01Z'; 18 | $origEndDateTime = '2014-06-18T01:00:00Z'; 19 | 20 | $expectedResults = new Interval('2014-06-18T12:30:01Z', '2014-06-18T01:00:01Z'); 21 | 22 | $query->setIntervalForQueryingUsingExclusiveTimes($origStartDateTime, $origEndDateTime); 23 | $results = $query->getIntervals(); 24 | $this->assertEquals($expectedResults, $results); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/QueryParameters/TimeBoundaryQueryParametersTest.php: -------------------------------------------------------------------------------- 1 | mockDataSource); 21 | 22 | $parametersInstance->validate(); 23 | 24 | $this->assertEquals($this->mockDataSource, $parametersInstance->dataSource); 25 | 26 | return $parametersInstance; 27 | } 28 | 29 | /** 30 | * @depends testValidate 31 | */ 32 | public function testValidateWithMissingDataSource() 33 | { 34 | $parametersInstance = new TimeBoundaryQueryParameters($this->mockDataSource); 35 | 36 | $parametersInstance->dataSource = NULL; 37 | 38 | $this->setExpectedException('DruidFamiliar\Exception\MissingParametersException'); 39 | 40 | $parametersInstance->validate(); 41 | } 42 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/ResponseHandler/DoNothingResponseHandlerTest.php: -------------------------------------------------------------------------------- 1 | handleResponse($mockedResponse); 16 | $this->assertEquals($mockedResponse, $handledResponse, "Does not try to encode or decode"); 17 | 18 | $mockedResponse = "a"; 19 | $handledResponse = $responseHandler->handleResponse($mockedResponse); 20 | $this->assertEquals($mockedResponse, $handledResponse, "Does not try to encode or decode"); 21 | 22 | $mockedResponse = '{"a":1, "b": "c"}'; 23 | $handledResponse = $responseHandler->handleResponse($mockedResponse); 24 | $this->assertEquals($mockedResponse, $handledResponse); 25 | 26 | $mockedResponse = "text\nwith\nnewlines"; 27 | $handledResponse = $responseHandler->handleResponse($mockedResponse); 28 | $this->assertEquals($mockedResponse, $handledResponse, "Does not strip newlines"); 29 | } 30 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/ResponseHandler/GroupByResponseHandlerTest.php: -------------------------------------------------------------------------------- 1 | responseHandler = new GroupByResponseHandler(); 32 | } 33 | 34 | /** 35 | * Tests that a good formed response is properly handled 36 | */ 37 | public function testTranslatesGoodResponseIntoGroupByResponse() 38 | { 39 | $goodResponse = <<< 'JSONRESPONSE' 40 | [{"version":"v1","timestamp":"2008-01-01T00:00:00.000Z","event":{"referral_id":"10","quantity":1,"group":"2","facility_id":"1"}},{"version":"v1","timestamp":"2008-01-01T00:00:00.000Z","event":{"referral_id":"1002","quantity":1,"group":"2","facility_id":"1"}},{"version":"v1","timestamp":"2008-01-01T00:00:00.000Z","event":{"referral_id":"1011","quantity":1,"group":"2","facility_id":"1"}}] 41 | 42 | JSONRESPONSE; 43 | $mockedResponse = new Response(200, array(), $goodResponse); 44 | $handledResponse = $this->responseHandler->handleResponse($mockedResponse); 45 | $data = $handledResponse->getData(); 46 | $record = $data[1]; 47 | $responseCount = count($data); 48 | $this->assertEquals(3, $responseCount); //Response has 3 records?? 49 | $this->assertTrue(isset($record['event'])); //Response has an event key?? 50 | $this->assertEquals('v1', $record['version']); //Response version is v1??? 51 | $this->assertEquals('1002', $record['event']['referral_id']); //Is the referral id the same as in the JSON object??? 52 | $this->assertEquals('2', $record['event']['group']); //Is the group the same as in the JSON object??? 53 | } 54 | 55 | /** 56 | * Tests that a good formed response is properly handled 57 | */ 58 | public function testTranslatesGoodResponseWithBadRecordIntoGroupByResponse() 59 | { 60 | $goodResponseWithBadRecord = <<< 'JSONRESPONSE' 61 | [{"version":"v1","timestamp":"2008-01-01T00:00:00.000Z","event":{"referral_id":"10","quantity":1,"group":"3","facility_id":"1"}},{"fakeObject":"true","willThisAffectBehaviour":"NO"},{"version":"v1","timestamp":"2008-01-01T00:00:00.000Z","event":{"referral_id":"1002","quantity":1,"group":"2","facility_id":"1"}},{"version":"v1","timestamp":"2008-01-01T00:00:00.000Z","event":{"referral_id":"1011","quantity":1,"group":"2","facility_id":"1"}}] 62 | 63 | JSONRESPONSE; 64 | $mockedResponse = new Response(200, array(), $goodResponseWithBadRecord); 65 | $handledResponse = $this->responseHandler->handleResponse($mockedResponse); 66 | $data = $handledResponse->getData(); 67 | $record = $data[0]; 68 | $responseCount = count($data); 69 | $this->assertEquals(3, $responseCount); //Response has 3 records?? It should because it will strip the bad data and just consume good records 70 | $this->assertTrue(isset($record['event'])); //Response has an event key?? 71 | $this->assertEquals('v1', $record['version']); //Response version is v1??? 72 | $this->assertEquals('10', $record['event']['referral_id']); //Is the referral id the same as in the JSON object??? 73 | $this->assertEquals('3', $record['event']['group']); //Is the group the same as in the JSON object??? 74 | } 75 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/ResponseHandler/TimeBoundaryResponseHandlerTest.php: -------------------------------------------------------------------------------- 1 | responseHandler = new TimeBoundaryResponseHandler(); 17 | } 18 | 19 | public function tearDown() 20 | { 21 | unset($this->responseHandler); 22 | } 23 | 24 | public function testHandlesNonDruidResponse() 25 | { 26 | try 27 | { 28 | $mockedResponse = new Response(200, array(), '1'); 29 | $handledResponse = $this->responseHandler->handleResponse($mockedResponse); 30 | $this->assertEquals($mockedResponse, $handledResponse); 31 | } 32 | catch(\Exception $e) 33 | { 34 | if($e->getMessage() === "Unexpected response format.") 35 | { 36 | return; 37 | } 38 | } 39 | 40 | $this->fail('Did not hit an expected exception'); 41 | } 42 | 43 | public function testHandlesEmptyResponse() 44 | { 45 | try 46 | { 47 | $mockedResponse = new Response(200); 48 | $handledResponse = $this->responseHandler->handleResponse($mockedResponse); 49 | $this->assertEquals($mockedResponse, $handledResponse); 50 | } 51 | catch(\Exception $e) 52 | { 53 | if($e->getMessage() === "Unknown data source.") 54 | { 55 | return; 56 | } 57 | } 58 | 59 | $this->fail('Did not hit an expected exception. Expected Exception with message "Unknown data source."'); 60 | } 61 | 62 | public function testTranslatesIntoTimeBoundaryResponse() 63 | { 64 | $jsonResponse = <<responseHandler->handleResponse($mockedResponse); 76 | 77 | $this->assertInstanceOf('DruidFamiliar\Response\TimeBoundaryResponse', $handledResponse); 78 | $this->assertEquals("2013-05-09T18:24:00.000Z", $handledResponse->minTime); 79 | $this->assertEquals("2013-05-09T18:37:00.000Z", $handledResponse->maxTime); 80 | } 81 | } -------------------------------------------------------------------------------- /tests/DruidFamiliar/Test/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------