Commit 64354f5b authored by Bas de Nooijer's avatar Bas de Nooijer

Lots of changes for supporting the Solr PHPS response writer format for all querytypes

parent d9ae2147
......@@ -46,6 +46,9 @@ use Solarium\Core\Configurable;
abstract class Query extends Configurable implements QueryInterface
{
const WT_JSON = 'json';
const WT_PHPS = 'phps';
/**
* Helper instance
*
......@@ -187,12 +190,11 @@ abstract class Query extends Configurable implements QueryInterface
/**
* Get responsewriter option
*
* Defaults to json for BC and full query support.
* Defaults to json for backwards compatibility and security.
*
* If you can fully trust the Solr responses (phps has a security risk from untrusted sources) you might consider
* setting the responsewriter to 'phps' (serialized php). This can give a performance advantage,
* especially with big resultsets. Phps response parsing has not been extensively tested for all querytypes, so it's
* still experimental. It works for standard updates and select queries, but be sure to test your queries.
* especially with big resultsets.
*
* @return string
*/
......@@ -200,7 +202,7 @@ abstract class Query extends Configurable implements QueryInterface
{
$responseWriter = $this->getOption('responsewriter');
if ($responseWriter === null ) {
$responseWriter = 'json';
$responseWriter = self::WT_JSON;
}
return $responseWriter;
......
<?php
/**
* Copyright 2011 Bas de Nooijer. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this listof conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the copyright holder.
*
* @copyright Copyright 2011 Bas de Nooijer <solarium@raspberry.nl>
* @license http://github.com/basdenooijer/solarium/raw/master/COPYING
* @link http://www.solarium-project.org/
*/
/**
* @namespace
*/
namespace Solarium\Core\Query;
/**
* Abstract class for response parsers
*
* Base class with shared functionality for querytype responseparser implementations
*/
abstract class ResponseParser
{
/**
* Converts a flat key-value array (alternating rows) as used in Solr JSON results to a real key value array
*
* @param $data
* @return array
*/
public function convertToKeyValueArray($data)
{
$result = array();
for ($i = 0; $i < count($data); $i += 2) {
$result[$data[$i]] = $data[$i+1];
}
return $result;
}
}
......@@ -143,10 +143,10 @@ class Result implements ResultInterface
if (null == $this->data) {
switch ($this->query->getResponseWriter()) {
case 'phps':
case Query::WT_PHPS:
$this->data = unserialize($this->response->getBody());
break;
case 'json':
case Query::WT_JSON:
$this->data = json_decode($this->response->getBody(), true);
break;
default:
......
......@@ -48,14 +48,15 @@ class Document extends Field
/**
* Parse implementation
*
* @param Result $result
* @param array $data
* @return array
*/
protected function parseAnalysis($data)
protected function parseAnalysis($result, $data)
{
$documents = array();
foreach ($data as $documentKey => $documentData) {
$fields = $this->parseTypes($documentData);
$fields = $this->parseTypes($result, $documentData);
$documents[] = new AnalysisResult\ResultList($documentKey, $fields);
}
......
......@@ -39,12 +39,13 @@
namespace Solarium\QueryType\Analysis\ResponseParser;
use Solarium\Core\Query\Result\Result;
use Solarium\QueryType\Analysis\Result as AnalysisResult;
use Solarium\Core\Query\ResponseParserInterface;
use Solarium\Core\Query\ResponseParser as ResponseParserAbstract;
use Solarium\Core\Query\ResponseParserInterface as ResponseParserInterface;
/**
* Parse document analysis response data
*/
class Field implements ResponseParserInterface
class Field extends ResponseParserAbstract implements ResponseParserInterface
{
/**
......@@ -58,14 +59,21 @@ class Field implements ResponseParserInterface
$data = $result->getData();
if (isset($data['analysis'])) {
$items = $this->parseAnalysis($data['analysis']);
$items = $this->parseAnalysis($result, $data['analysis']);
} else {
$items = array();
}
$status = null;
$queryTime = null;
if (isset($data['responseHeader'])) {
$status = $data['responseHeader']['status'];
$queryTime = $data['responseHeader']['QTime'];
}
return array(
'status' => $data['responseHeader']['status'],
'queryTime' => $data['responseHeader']['QTime'],
'status' => $status,
'queryTime' => $queryTime,
'items' => $items
);
}
......@@ -73,14 +81,15 @@ class Field implements ResponseParserInterface
/**
* Parser
*
* @param Result $result
* @param array $data
* @return array
*/
protected function parseAnalysis($data)
protected function parseAnalysis($result, $data)
{
$types = array();
foreach ($data as $documentKey => $documentData) {
$fields = $this->parseTypes($documentData);
$fields = $this->parseTypes($result, $documentData);
$types[] = new AnalysisResult\ResultList($documentKey, $fields);
}
......@@ -90,28 +99,32 @@ class Field implements ResponseParserInterface
/**
* Parse analysis types and items
*
* @param Result $result
* @param array $typeData
* @return array
*/
protected function parseTypes($typeData)
protected function parseTypes($result, $typeData)
{
$query = $result->getQuery();
$results = array();
foreach ($typeData as $fieldKey => $fieldData) {
$types = array();
foreach ($fieldData as $typeKey => $typeData) {
// fix for extra level for key fields
if (count($typeData) == 1) {
$typeData = current($typeData);
if ($query->getResponseWriter() == $query::WT_JSON) {
// fix for extra level for key fields
if (count($typeData) == 1) {
$typeData = current($typeData);
}
$typeData = $this->convertToKeyValueArray($typeData);
}
$counter = 0;
$classes = array();
while (isset($typeData[$counter]) && isset($typeData[$counter+1])) {
$class = $typeData[$counter];
$analysis = $typeData[$counter+1];
foreach ($typeData as $class => $analysis) {
if (is_string($analysis)) {
$item = new AnalysisResult\Item(
......@@ -136,8 +149,6 @@ class Field implements ResponseParserInterface
$classes[] = new AnalysisResult\ResultList($class, $items);
}
$counter += 2;
}
$types[] = new AnalysisResult\ResultList($typeKey, $classes);
......
......@@ -182,7 +182,11 @@ class Item
*/
public function getPositionHistory()
{
return $this->positionHistory;
if (is_array($this->positionHistory)) {
return $this->positionHistory;
} else {
return array();
}
}
/**
......
......@@ -61,11 +61,9 @@ class ResponseParser extends SelectResponseParser
if (isset($data['interestingTerms']) && 'none' != $query->getInterestingTerms()) {
$terms = $data['interestingTerms'];
if ('details' == $query->getInterestingTerms()) {
$tempTerms = array();
for ($i = 0; $i < count($terms); $i += 2) {
$tempTerms[$terms[$i]] = $terms[$i + 1];
if ($query->getResponseWriter() == $query::WT_JSON) {
$terms = $this->convertToKeyValueArray($terms);
}
$terms = $tempTerms;
}
$parseResult['interestingTerms'] = $terms;
}
......
......@@ -43,11 +43,12 @@ use Solarium\QueryType\Select\Query\Component\Facet as QueryFacet;
use Solarium\QueryType\Select\Result\FacetSet as ResultFacetSet;
use Solarium\QueryType\Select\Result\Facet as ResultFacet;
use Solarium\Exception\RuntimeException;
use Solarium\Core\Query\ResponseParser as ResponseParserAbstract;
/**
* Parse select component FacetSet result from the data
*/
class FacetSet
class FacetSet extends ResponseParserAbstract
{
/**
......@@ -65,7 +66,7 @@ class FacetSet
foreach ($facetSet->getFacets() as $key => $facet) {
switch ($facet->getType()) {
case QueryFacetSet::FACET_FIELD:
$result = $this->facetField($facet, $data);
$result = $this->facetField($query, $facet, $data);
break;
case QueryFacetSet::FACET_QUERY:
$result = $this->facetQuery($facet, $data);
......@@ -74,7 +75,7 @@ class FacetSet
$result = $this->facetMultiQuery($facet, $data);
break;
case QueryFacetSet::FACET_RANGE:
$result = $this->facetRange($facet, $data);
$result = $this->facetRange($query, $facet, $data);
break;
default:
throw new RuntimeException('Unknown facet type');
......@@ -102,26 +103,21 @@ class FacetSet
/**
* Add a facet result for a field facet
*
* @param Query $query
* @param QueryFacet\Field $facet
* @param array $data
* @return ResultFacet\Field
*/
protected function facetField($facet, $data)
protected function facetField($query, $facet, $data)
{
$key = $facet->getKey();
if (isset($data['facet_counts']['facet_fields'][$key])) {
$values = array_chunk(
$data['facet_counts']['facet_fields'][$key],
2
);
$facetValues = array();
foreach ($values as $value) {
$facetValues[$value[0]] = $value[1];
if ($query->getResponseWriter() == $query::WT_JSON) {
$data['facet_counts']['facet_fields'][$key] = $this->convertToKeyValueArray($data['facet_counts']['facet_fields'][$key]);
}
return new ResultFacet\Field($facetValues);
return new ResultFacet\Field($data['facet_counts']['facet_fields'][$key]);
}
}
......@@ -169,11 +165,12 @@ class FacetSet
/**
* Add a facet result for a range facet
*
* @param Query $query
* @param QueryFacet\Range $facet
* @param array $data
* @return ResultFacet\Range
*/
protected function facetRange($facet, $data)
protected function facetRange($query, $facet, $data)
{
$key = $facet->getKey();
if (isset($data['facet_counts']['facet_ranges'][$key])) {
......@@ -183,14 +180,11 @@ class FacetSet
$after = (isset($data['after'])) ? $data['after'] : null;
$between = (isset($data['between'])) ? $data['between'] : null;
$offset = 0;
$counts = array();
while (isset($data['counts'][$offset]) && isset($data['counts'][$offset+1])) {
$counts[$data['counts'][$offset]] = $data['counts'][$offset+1];
$offset += 2;
if ($query->getResponseWriter() == $query::WT_JSON) {
$data['counts'] = $this->convertToKeyValueArray($data['counts']);
}
return new ResultFacet\Range($counts, $before, $after, $between);
return new ResultFacet\Range($data['counts'], $before, $after, $between);
}
}
......
......@@ -37,14 +37,15 @@
* @namespace
*/
namespace Solarium\QueryType\Select\ResponseParser;
use Solarium\Core\Query\ResponseParserInterface;
use Solarium\Core\Query\ResponseParser as ResponseParserAbstract;
use Solarium\Core\Query\ResponseParserInterface as ResponseParserInterface;
use Solarium\QueryType\Select\Result\Result;
use Solarium\Exception\RuntimeException;
/**
* Parse select response data
*/
class ResponseParser implements ResponseParserInterface
class ResponseParser extends ResponseParserAbstract implements ResponseParserInterface
{
/**
......
......@@ -38,12 +38,13 @@
* @namespace
*/
namespace Solarium\QueryType\Suggester;
use Solarium\Core\Query\ResponseParserInterface;
use Solarium\Core\Query\ResponseParser as ResponseParserAbstract;
use Solarium\Core\Query\ResponseParserInterface as ResponseParserInterface;
/**
* Parse Suggester response data
*/
class ResponseParser implements ResponseParserInterface
class ResponseParser extends ResponseParserAbstract implements ResponseParserInterface
{
/**
......@@ -70,11 +71,13 @@ class ResponseParser implements ResponseParserInterface
if (isset($data['spellcheck']['suggestions']) && is_array($data['spellcheck']['suggestions'])) {
$suggestResults = $data['spellcheck']['suggestions'];
$termClass = $query->getOption('termclass');
for ($i = 0; $i < count($suggestResults); $i += 2) {
$term = $suggestResults[$i];
$termData = $suggestResults[$i+1];
if ($term == 'collation'&& $i == count($suggestResults)-2) {
if ($query->getResponseWriter() == $query::WT_JSON) {
$suggestResults = $this->convertToKeyValueArray($suggestResults);
}
foreach ($suggestResults as $term => $termData) {
if ($term == 'collation') {
$collation = $termData;
} else {
$suggestions[$term] = new $termClass(
......@@ -95,4 +98,6 @@ class ResponseParser implements ResponseParserInterface
);
}
}
......@@ -38,12 +38,13 @@
* @namespace
*/
namespace Solarium\QueryType\Terms;
use Solarium\Core\Query\ResponseParserInterface;
use Solarium\Core\Query\ResponseParser as ResponseParserAbstract;
use Solarium\Core\Query\ResponseParserInterface as ResponseParserInterface;
/**
* Parse MoreLikeThis response data
*/
class ResponseParser implements ResponseParserInterface
class ResponseParser extends ResponseParserAbstract implements ResponseParserInterface
{
/**
......@@ -57,15 +58,17 @@ class ResponseParser implements ResponseParserInterface
$termResults = array();
$data = $result->getData();
$field = $result->getQuery()->getFields();
$query = $result->getQuery();
$field = $query->getFields();
$fields = explode(',', $field);
foreach ($fields as $field) {
$field = trim($field);
if (isset($data['terms'][$field])) {
$terms = $data['terms'][$field];
for ($i = 0; $i < count($terms); $i += 2) {
$termResults[$field][$terms[$i]] = $terms[$i + 1];
if ($query->getResponseWriter() == $query::WT_JSON) {
$terms = $this->convertToKeyValueArray($terms);
}
$termResults[$field] = $terms;
}
}
......
<?php
/**
* Copyright 2011 Bas de Nooijer. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this listof conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the copyright holder.
*/
namespace Solarium\Tests\Core\Query;
use Solarium\Core\Query\ResponseParser;
class ResponseParserTest extends \PHPUnit_Framework_TestCase
{
/**
* @var TestResponseParser
*/
protected $parser;
public function setup()
{
$this->parser = new TestResponseParser();
}
public function testBuild()
{
$input = array(
'key1',
'value1',
'key2',
'value2',
'key3',
'value3'
);
$expected = array(
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
);
$this->assertEquals(
$expected,
$this->parser->convertToKeyValueArray($input)
);
}
}
/**
* Dummy implementation to test code in abstract class
*/
class TestResponseParser extends ResponseParser
{
public function parse($result)
{
}
}
......@@ -31,6 +31,7 @@
namespace Solarium\Tests\QueryType\Analysis\ResponseParser;
use Solarium\QueryType\Analysis\ResponseParser\Field as FieldParser;
use Solarium\QueryType\Analysis\Query\Field as Query;
class FieldTest extends \PHPUnit_Framework_TestCase
{
......@@ -79,6 +80,9 @@ class FieldTest extends \PHPUnit_Framework_TestCase
$resultStub->expects($this->once())
->method('getData')
->will($this->returnValue($data));
$resultStub->expects($this->once())
->method('getQuery')
->will($this->returnValue(new Query));
$parser = new FieldParser();
$result = $parser->parse($resultStub);
......
......@@ -82,6 +82,14 @@ class ItemTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($this->data['positionHistory'], $this->item->getPositionHistory());
}
public function testGetPositionHistoryFallbackValue()
{
$data = $this->data;
$data['positionHistory'] = '';
$item = new Item($data);
$this->assertEquals(array(), $item->getPositionHistory());
}
public function testGetRawText()
{
$this->assertEquals($this->data['raw_text'], $this->item->getRawText());
......
......@@ -32,11 +32,12 @@
namespace Solarium\Tests\QueryType\Select\ResponseParser\Component;
use Solarium\QueryType\Select\ResponseParser\Component\FacetSet as Parser;
use Solarium\QueryType\Select\Query\Component\FacetSet;
use Solarium\QueryType\Select\Query\Query;
class FacetSetTest extends \PHPUnit_Framework_TestCase
{
protected $parser, $facetSet;
protected $parser, $facetSet, $query;
public function setUp()
{
......@@ -47,6 +48,8 @@ class FacetSetTest extends \PHPUnit_Framework_TestCase
$this->facetSet->createFacet('query', array('key' => 'keyB'));
$this->facetSet->createFacet('multiquery', array('key' => 'keyC', 'query' => array('keyC_A' => array('query' => 'id:1'), 'keyC_B' => array('query' => 'id:2'))));
$this->facetSet->createFacet('range', array('key' => 'keyD'));
$this->query = new Query;
}
public function testParse()
......@@ -84,7 +87,7 @@ class FacetSetTest extends \PHPUnit_Framework_TestCase
)
);
$result = $this->parser->parse(null, $this->facetSet, $data);
$result = $this->parser->parse($this->query, $this->facetSet, $data);
$facets = $result->getFacets();
$this->assertEquals(array('keyA','keyB','keyC','keyD'), array_keys($facets));
......@@ -123,11 +126,13 @@ class FacetSetTest extends \PHPUnit_Framework_TestCase
5,
$facets['keyD']->getAfter()
);
$this->query = new Query;
}
public function testParseNoData()
{
$result = $this->parser->parse(null, $this->facetSet, array());
$result = $this->parser->parse($this->query, $this->facetSet, array());
$this->assertEquals(array(), $result->getFacets());
}
......@@ -144,7 +149,7 @@ class FacetSetTest extends \PHPUnit_Framework_TestCase
$this->facetSet->addFacet($facetStub);
$this->setExpectedException('Solarium\Exception\RuntimeException');
$this->parser->parse(null, $this->facetSet, array());
$this->parser->parse($this->query, $this->facetSet, array());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment