QuantityHandler::getBaseTableName()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 3
rs 10
c 1
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Wikibase\QueryEngine\SQLStore\DVHandler;
4
5
use Ask\Language\Description\ValueDescription;
6
use DataValues\DataValue;
7
use DataValues\DecimalValue;
8
use DataValues\QuantityValue;
9
use Doctrine\DBAL\Query\QueryBuilder;
10
use Doctrine\DBAL\Schema\Table;
11
use Doctrine\DBAL\Types\Type;
12
use InvalidArgumentException;
13
use Wikibase\QueryEngine\QueryNotSupportedException;
14
use Wikibase\QueryEngine\SQLStore\DataValueHandler;
15
16
/**
17
 * @since 0.3
18
 *
19
 * @licence GNU GPL v2+
20
 * @author Thiemo Kreuz
21
 */
22
class QuantityHandler extends DataValueHandler {
23
24
	/**
25
	 * @see DataValueHandler::getBaseTableName
26
	 */
27
	protected function getBaseTableName() {
28
		return 'quantity';
29
	}
30
31
	/**
32
	 * @see DataValueHandler::completeTable
33
	 */
34
	protected function completeTable( Table $table ) {
35
		$table->addColumn( 'value_unit', Type::STRING, array( 'length' => 255, 'notnull' => false ) );
36
		$table->addColumn( 'value_actual', Type::FLOAT );
37
		$table->addColumn( 'value_lower_bound', Type::FLOAT );
38
		$table->addColumn( 'value_upper_bound', Type::FLOAT );
39
		$table->addColumn( 'hash', Type::STRING, array( 'length' => 32 ) );
40
41
		$table->addIndex( array( 'value_actual' ) );
42
		// TODO: This index is currently not used. Does it make sense to introduce it anyway?
43
		// I still do not fully understand what MySQL does when a combined index is queried
44
		// with multiple lower/greater than clauses. Maybe separate indexes are better?
45
		$table->addIndex( array( 'value_lower_bound', 'value_upper_bound' ) );
46
	}
47
48
	/**
49
	 * @see DataValueHandler::getInsertValues
50
	 *
51
	 * @param DataValue $value
52
	 *
53
	 * @throws InvalidArgumentException
54
	 * @return array
55
	 */
56
	public function getInsertValues( DataValue $value ) {
57
		if ( !( $value instanceof QuantityValue ) ) {
58
			throw new InvalidArgumentException( 'Value is not a QuantityValue.' );
59
		} elseif ( $value->getUnit() !== '1' ) {
60
			throw new InvalidArgumentException( 'Units other than "1" are not yet supported.' );
61
		}
62
63
		$values = array(
64
			'value_unit' => $this->normalizeUnit( $value->getUnit() ),
65
			'value_actual' => $value->getAmount()->getValue(),
66
			'value_lower_bound' => $value->getLowerBound()->getValue(),
67
			'value_upper_bound' => $value->getUpperBound()->getValue(),
68
69
			'hash' => $this->getEqualityFieldValue( $value ),
70
		);
71
72
		return $values;
73
	}
74
75
	/**
76
	 * @see DataValueHandler::addMatchConditions
77
	 *
78
	 * @param QueryBuilder $builder
79
	 * @param ValueDescription $description
80
	 *
81
	 * @throws InvalidArgumentException
82
	 * @throws QueryNotSupportedException
83
	 */
84
	public function addMatchConditions( QueryBuilder $builder, ValueDescription $description ) {
85
		$value = $description->getValue();
86
87
		if ( !( $value instanceof QuantityValue ) ) {
88
			throw new InvalidArgumentException( 'Value is not a QuantityValue.' );
89
		} elseif ( $value->getUnit() !== '1' ) {
90
			throw new InvalidArgumentException( 'Units other than "1" are not yet supported.' );
91
		}
92
93
		if ( $description->getComparator() === ValueDescription::COMP_EQUAL ) {
94
			$this->addByQuantityConditions( $builder, $value );
95
		} else {
96
			parent::addMatchConditions( $builder, $description );
97
		}
98
	}
99
100
	/**
101
	 * @param QueryBuilder $builder
102
	 * @param QuantityValue $value
103
	 */
104
	private function addByQuantityConditions( QueryBuilder $builder, QuantityValue $value ) {
105
		// Exact search if search range is zero.
106
		if ( $value->getLowerBound()->getValueFloat() >= $value->getUpperBound()->getValueFloat() ) {
107
			$this->addSameValueConditions( $builder, $value->getAmount() );
108
		} else {
109
			$this->addInRangeConditions( $builder, $value );
110
		}
111
	}
112
113
	/**
114
	 * @param QueryBuilder $builder
115
	 * @param DecimalValue $value
116
	 */
117
	private function addSameValueConditions( QueryBuilder $builder, DecimalValue $value ) {
118
		$builder->andWhere( 'value_actual = :actual' );
119
120
		$builder->setParameter( ':actual', $value->getValue() );
121
	}
122
123
	/**
124
	 * We are not asking if the given search value fits in a range, we are searching for
125
	 * values within the given search range.
126
	 *
127
	 * If searching for 1500 m (with default precision +/-1) we don't want to find 1501 m,
128
	 * but want to find 1500.99 m (no matter what the precision is).
129
	 * So the range is ]-precision,+precision[ (exclusive).
130
	 *
131
	 * @param QueryBuilder $builder
132
	 * @param QuantityValue $value
133
	 */
134
	private function addInRangeConditions( QueryBuilder $builder, QuantityValue $value ) {
135
		$builder->andWhere( 'value_actual > :lower_bound' );
136
		$builder->andWhere( 'value_actual < :upper_bound' );
137
138
		$builder->setParameter( ':lower_bound', $value->getLowerBound()->getValue() );
139
		$builder->setParameter( ':upper_bound', $value->getUpperBound()->getValue() );
140
	}
141
142
	/**
143
	 * @param string $unit
144
	 *
145
	 * @return string|null
146
	 */
147
	private function normalizeUnit( $unit ) {
148
		return $unit === '1' ? null : $unit;
149
	}
150
151
}
152