1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace POData\Providers\Query; |
4
|
|
|
|
5
|
|
|
use POData\Providers\Expression\MySQLExpressionProvider; |
6
|
|
|
use POData\Providers\Metadata\ResourceProperty; |
7
|
|
|
use POData\Providers\Metadata\ResourceSet; |
8
|
|
|
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo; |
9
|
|
|
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo; |
10
|
|
|
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor; |
11
|
|
|
|
12
|
|
|
abstract class SimpleQueryProvider implements IQueryProvider |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* @var Connection |
16
|
|
|
*/ |
17
|
|
|
protected $db; |
18
|
|
|
|
19
|
|
|
public function __construct($db) |
20
|
|
|
{ |
21
|
|
|
$this->db = $db; |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Query all data from DB. |
26
|
|
|
* |
27
|
|
|
* @param string $sql SQL query |
28
|
|
|
* @param array|null $parameters Parameters for SQL query |
29
|
|
|
* |
30
|
|
|
* @return array[]|null Array of associated arrays (column name => column value) |
31
|
|
|
*/ |
32
|
|
|
abstract protected function queryAll($sql, $parameters = null); |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Query one value from DB. |
36
|
|
|
* |
37
|
|
|
* @param string $sql SQL query |
38
|
|
|
* @param array|null $parameters Parameters for SQL query |
39
|
|
|
* |
40
|
|
|
* @return mixed Value |
41
|
|
|
*/ |
42
|
|
|
abstract protected function queryScalar($sql, $parameters = null); |
43
|
|
|
|
44
|
|
|
/* Stubbed Implementaiton Here */ |
45
|
|
|
public function getQueryProvider() |
46
|
|
|
{ |
47
|
|
|
return new QueryProvider(); |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
public function handlesOrderedPaging() |
51
|
|
|
{ |
52
|
|
|
return true; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
public function getExpressionProvider() |
56
|
|
|
{ |
57
|
|
|
return new MySQLExpressionProvider(); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Get entity name by class name. |
62
|
|
|
* |
63
|
|
|
* @param string $entityClassName Class name |
64
|
|
|
* |
65
|
|
|
* @return string Entity name |
66
|
|
|
*/ |
67
|
|
|
protected function getEntityName($entityClassName) |
68
|
|
|
{ |
69
|
|
|
preg_match_all('/\\\([a-zA-Z]+)/', $entityClassName, $matches); |
70
|
|
|
if (!empty($matches[1])) { |
71
|
|
|
return $matches[1][count($matches[1]) - 1]; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
return $entityClassName; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Get table name by entity name. |
79
|
|
|
* |
80
|
|
|
* @param string $entityName Entity name |
81
|
|
|
* |
82
|
|
|
* @return string Table name |
83
|
|
|
*/ |
84
|
|
|
protected function getTableName($entityName) |
85
|
|
|
{ |
86
|
|
|
$tableName = $entityName; |
87
|
|
|
preg_match_all('/[A-Z][a-z]+/', $entityName, $matches); |
88
|
|
|
if (!empty($matches[0])) { |
89
|
|
|
$tableName = implode('_', $matches[0]); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
return strtolower($tableName); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Get part of SQL query with ORDER BY condition. |
97
|
|
|
* |
98
|
|
|
* @param InternalOrderByInfo $orderBy Order by condition |
99
|
|
|
* |
100
|
|
|
* @return string ORDER BY condition |
101
|
|
|
*/ |
102
|
|
|
protected function getOrderByExpressionAsString(InternalOrderByInfo $orderBy) |
103
|
|
|
{ |
104
|
|
|
$result = ''; |
105
|
|
|
foreach ($orderBy->getOrderByInfo()->getOrderByPathSegments() as $order) { |
106
|
|
|
foreach ($order->getSubPathSegments() as $subOrder) { |
107
|
|
|
$result .= $result ? ', ' : ''; |
108
|
|
|
$result .= $subOrder->getName(); |
109
|
|
|
$result .= $order->isAscending() ? ' ASC' : ' DESC'; |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
return $result; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Common method for getResourceFromRelatedResourceSet() and getResourceFromResourceSet(). |
118
|
|
|
* |
119
|
|
|
* @param KeyDescriptor|null $keyDescriptor |
120
|
|
|
*/ |
121
|
|
|
protected function getResource( |
122
|
|
|
ResourceSet $resourceSet, |
123
|
|
|
$keyDescriptor, |
124
|
|
|
array $whereCondition = [] |
125
|
|
|
) { |
126
|
|
|
$where = ''; |
127
|
|
|
$parameters = []; |
128
|
|
|
$index = 0; |
129
|
|
|
if ($keyDescriptor) { |
130
|
|
|
foreach ($keyDescriptor->getValidatedNamedValues() as $key => $value) { |
131
|
|
|
++$index; |
132
|
|
|
//Keys have already been validated, so this is not a SQL injection surface |
133
|
|
|
$where .= $where ? ' AND ' : ''; |
134
|
|
|
$where .= $key . ' = :param' . $index; |
135
|
|
|
$parameters[':param' . $index] = $value[0]; |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
foreach ($whereCondition as $fieldName => $fieldValue) { |
139
|
|
|
++$index; |
140
|
|
|
$where .= $where ? ' AND ' : ''; |
141
|
|
|
$where .= $fieldName . ' = :param' . $index; |
142
|
|
|
$parameters[':param' . $index] = $fieldValue; |
143
|
|
|
} |
144
|
|
|
$where = $where ? ' WHERE ' . $where : ''; |
145
|
|
|
$entityClassName = $resourceSet->getResourceType()->getInstanceType()->name; |
146
|
|
|
$entityName = $this->getEntityName($entityClassName); |
147
|
|
|
$sql = 'SELECT * FROM ' . $this->getTableName($entityName) . $where . ' LIMIT 1'; |
148
|
|
|
$result = $this->queryAll($sql, $parameters); |
149
|
|
|
if ($result) { |
150
|
|
|
$result = $result[0]; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return $entityClassName::fromRecord($result); |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* For queries like http://localhost/NorthWind.svc/Customers. |
158
|
|
|
*/ |
159
|
|
|
public function getResourceSet( |
160
|
|
|
QueryType $queryType, |
161
|
|
|
ResourceSet $resourceSet, |
162
|
|
|
$filterInfo = null, |
163
|
|
|
$orderBy = null, |
164
|
|
|
$top = null, |
165
|
|
|
$skip = null |
166
|
|
|
) { |
167
|
|
|
$result = new QueryResult(); |
168
|
|
|
$entityClassName = $resourceSet->getResourceType()->getInstanceType()->name; |
169
|
|
|
$entityName = $this->getEntityName($entityClassName); |
170
|
|
|
$tableName = $this->getTableName($entityName); |
171
|
|
|
$option = null; |
172
|
|
|
if ($queryType == QueryType::ENTITIES_WITH_COUNT()) { |
|
|
|
|
173
|
|
|
//tell mysql we want to know the count prior to the LIMIT |
174
|
|
|
//$option = 'SQL_CALC_FOUND_ROWS'; |
|
|
|
|
175
|
|
|
} |
176
|
|
|
$where = $filterInfo ? ' WHERE ' . $filterInfo->getExpressionAsString() : ''; |
177
|
|
|
$order = $orderBy ? ' ORDER BY ' . $this->getOrderByExpressionAsString($orderBy) : ''; |
178
|
|
|
$sqlCount = 'SELECT COUNT(*) FROM ' . $tableName . $where; |
179
|
|
|
if ($queryType == QueryType::ENTITIES() || $queryType == QueryType::ENTITIES_WITH_COUNT()) { |
180
|
|
|
$sql = 'SELECT ' . $option . ' * FROM ' . $tableName . $where . $order |
181
|
|
|
.($top ? ' LIMIT ' . $top : '') . ($skip ? ' OFFSET ' . $skip : ''); |
182
|
|
|
$data = $this->queryAll($sql); |
183
|
|
|
|
184
|
|
|
if ($queryType == QueryType::ENTITIES_WITH_COUNT()) { |
185
|
|
|
//get those found rows |
186
|
|
|
//$result->count = $this->queryScalar('SELECT FOUND_ROWS()'); |
|
|
|
|
187
|
|
|
$result->count = $this->queryScalar($sqlCount); |
188
|
|
|
} |
189
|
|
|
$result->results = array_map($entityClassName . '::fromRecord', $data); |
190
|
|
|
} elseif ($queryType == QueryType::COUNT()) { |
191
|
|
|
$top = null !== $top ? intval($top) : $top; |
192
|
|
|
$skip = null !== $skip ? intval($skip) : $skip; |
193
|
|
|
$result->count = QueryResult::adjustCountForPaging( |
|
|
|
|
194
|
|
|
$this->queryScalar($sqlCount), |
195
|
|
|
$top, |
196
|
|
|
$skip |
197
|
|
|
); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
return $result; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* For queries like http://localhost/NorthWind.svc/Customers(‘ALFKI’). |
205
|
|
|
*/ |
206
|
|
|
public function getResourceFromResourceSet( |
207
|
|
|
ResourceSet $resourceSet, |
208
|
|
|
KeyDescriptor $keyDescriptor |
209
|
|
|
) { |
210
|
|
|
return $this->getResource($resourceSet, $keyDescriptor); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* For queries like http://localhost/NorthWind.svc/Customers(‘ALFKI’)/Orders. |
215
|
|
|
*/ |
216
|
|
|
public function getRelatedResourceSet( |
217
|
|
|
QueryType $queryType, |
218
|
|
|
ResourceSet $sourceResourceSet, |
219
|
|
|
$sourceEntityInstance, |
220
|
|
|
ResourceSet $targetResourceSet, |
221
|
|
|
ResourceProperty $targetProperty, |
222
|
|
|
$filterInfo = null, |
223
|
|
|
$orderBy = null, |
224
|
|
|
$top = null, |
225
|
|
|
$skip = null |
226
|
|
|
) { |
227
|
|
|
// Correct filter |
228
|
|
|
$srcClass = get_class($sourceEntityInstance); |
229
|
|
|
$filterFieldName = $this->getTableName($this->getEntityName($srcClass)) . '_id'; |
230
|
|
|
$navigationPropertiesUsedInTheFilterClause = null; |
231
|
|
|
$filterExpAsDataSourceExp = ''; |
232
|
|
|
if ($filterInfo) { |
233
|
|
|
$navigationPropertiesUsedInTheFilterClause = $filterInfo->getNavigationPropertiesUsed(); |
234
|
|
|
$filterExpAsDataSourceExp = $filterInfo->getExpressionAsString(); |
235
|
|
|
} |
236
|
|
|
$filterExpAsDataSourceExp .= $filterExpAsDataSourceExp ? ' AND ' : ''; |
237
|
|
|
$filterExpAsDataSourceExp .= $filterFieldName . ' = ' . $sourceEntityInstance->id; |
238
|
|
|
$completeFilterInfo = new FilterInfo($navigationPropertiesUsedInTheFilterClause, $filterExpAsDataSourceExp); |
|
|
|
|
239
|
|
|
|
240
|
|
|
return $this->getResourceSet($queryType, $targetResourceSet, $completeFilterInfo, $orderBy, $top, $skip); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* For queries like http://localhost/NorthWind.svc/Customers(‘ALFKI’)/Orders(10643). |
245
|
|
|
*/ |
246
|
|
View Code Duplication |
public function getResourceFromRelatedResourceSet( |
|
|
|
|
247
|
|
|
ResourceSet $sourceResourceSet, |
248
|
|
|
$sourceEntityInstance, |
249
|
|
|
ResourceSet $targetResourceSet, |
250
|
|
|
ResourceProperty $targetProperty, |
251
|
|
|
KeyDescriptor $keyDescriptor |
252
|
|
|
) { |
253
|
|
|
$entityClassName = $sourceResourceSet->getResourceType()->getInstanceType()->name; |
254
|
|
|
$entityName = $this->getEntityName($entityClassName); |
255
|
|
|
$fieldName = $this->getTableName($entityName) . '_id'; |
256
|
|
|
|
257
|
|
|
return $this->getResource($targetResourceSet, $keyDescriptor, [ |
258
|
|
|
$fieldName => $sourceEntityInstance->id, |
259
|
|
|
]); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* For queries like http://localhost/NorthWind.svc/Orders(10643)/Customer. |
264
|
|
|
*/ |
265
|
|
View Code Duplication |
public function getRelatedResourceReference( |
|
|
|
|
266
|
|
|
ResourceSet $sourceResourceSet, |
267
|
|
|
$sourceEntityInstance, |
268
|
|
|
ResourceSet $targetResourceSet, |
269
|
|
|
ResourceProperty $targetProperty |
270
|
|
|
) { |
271
|
|
|
$entityClassName = $targetResourceSet->getResourceType()->getInstanceType()->name; |
272
|
|
|
$entityName = $this->getEntityName($entityClassName); |
273
|
|
|
$fieldName = $this->getTableName($entityName) . '_id'; |
274
|
|
|
|
275
|
|
|
return $this->getResource($targetResourceSet, null, [ |
276
|
|
|
'id' => $sourceEntityInstance->$fieldName, |
277
|
|
|
]); |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
This check looks for the bodies of
if
statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
if
bodies can be removed. If you have an empty if but statements in theelse
branch, consider inverting the condition.could be turned into
This is much more concise to read.