| 199 |
lars |
1 |
<?php namespace Clockwork\Storage;
|
|
|
2 |
|
|
|
3 |
use Clockwork\Request\Request;
|
|
|
4 |
|
|
|
5 |
use PDO;
|
|
|
6 |
|
|
|
7 |
// Rules for searching requests using SQL storage, builds the SQL query conditions
|
|
|
8 |
class SqlSearch extends Search
|
|
|
9 |
{
|
|
|
10 |
// Generated SQL query and bindings
|
|
|
11 |
public $query;
|
|
|
12 |
public $bindings;
|
|
|
13 |
|
|
|
14 |
// Internal representation of the SQL where conditions
|
|
|
15 |
protected $conditions;
|
|
|
16 |
|
|
|
17 |
// PDO instance
|
|
|
18 |
protected $pdo;
|
|
|
19 |
|
|
|
20 |
// Create a new instance, takes search parameters
|
|
|
21 |
public function __construct($search = [], PDO $pdo = null)
|
|
|
22 |
{
|
|
|
23 |
parent::__construct($search);
|
|
|
24 |
|
|
|
25 |
$this->pdo = $pdo;
|
|
|
26 |
|
|
|
27 |
list($this->conditions, $this->bindings) = $this->resolveConditions();
|
|
|
28 |
|
|
|
29 |
$this->buildQuery();
|
|
|
30 |
}
|
|
|
31 |
|
|
|
32 |
// Creates a new instance from a base Search class instance
|
|
|
33 |
public static function fromBase(Search $search = null, PDO $pdo = null)
|
|
|
34 |
{
|
|
|
35 |
return new static((array) $search, $pdo);
|
|
|
36 |
}
|
|
|
37 |
|
|
|
38 |
// Add an additional where condition, takes the SQL condition and array of bindings
|
|
|
39 |
public function addCondition($condition, $bindings = [])
|
|
|
40 |
{
|
|
|
41 |
$this->conditions = array_merge([ $condition ], $this->conditions);
|
|
|
42 |
$this->bindings = array_merge($bindings, $this->bindings);
|
|
|
43 |
$this->buildQuery();
|
|
|
44 |
|
|
|
45 |
return $this;
|
|
|
46 |
}
|
|
|
47 |
|
|
|
48 |
// Resolve SQL conditions and bindings based on the search parameters
|
|
|
49 |
protected function resolveConditions()
|
|
|
50 |
{
|
|
|
51 |
if ($this->isEmpty()) return [ [], [] ];
|
|
|
52 |
|
|
|
53 |
$conditions = array_filter([
|
|
|
54 |
$this->resolveStringCondition([ 'type' ], $this->type),
|
|
|
55 |
$this->resolveStringCondition([ 'uri', 'commandName', 'jobName', 'testName' ], array_merge($this->uri, $this->name)),
|
|
|
56 |
$this->resolveStringCondition([ 'controller' ], $this->controller),
|
|
|
57 |
$this->resolveExactCondition([ 'method' ], $this->method),
|
|
|
58 |
$this->resolveNumberCondition([ 'responseStatus', 'commandExitCode', 'jobStatus', 'testStatus' ], $this->status),
|
|
|
59 |
$this->resolveNumberCondition([ 'responseDuration' ], $this->time),
|
|
|
60 |
$this->resolveDateCondition([ 'time' ], $this->received)
|
|
|
61 |
]);
|
|
|
62 |
|
|
|
63 |
$sql = array_map(function ($condition) { return $condition[0]; }, $conditions);
|
|
|
64 |
$bindings = array_reduce($conditions, function ($bindings, $condition) {
|
|
|
65 |
return array_merge($bindings, $condition[1]);
|
|
|
66 |
}, []);
|
|
|
67 |
|
|
|
68 |
return [ $sql, $bindings ];
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
// Resolve a date type condition and bindings
|
|
|
72 |
protected function resolveDateCondition($fields, $inputs)
|
|
|
73 |
{
|
|
|
74 |
if (! count($inputs)) return null;
|
|
|
75 |
|
|
|
76 |
$bindings = [];
|
|
|
77 |
$conditions = implode(' OR ', array_map(function ($field) use ($inputs, &$bindings) {
|
|
|
78 |
return implode(' OR ', array_map(function ($input, $index) use ($field, &$bindings) {
|
|
|
79 |
if (preg_match('/^<(.+)$/', $input, $match)) {
|
|
|
80 |
$bindings["{$field}{$index}"] = $match[1];
|
|
|
81 |
return $this->quote($field) . " < :{$field}{$index}";
|
|
|
82 |
} elseif (preg_match('/^>(.+)$/', $input, $match)) {
|
|
|
83 |
$bindings["{$field}{$index}"] = $match[1];
|
|
|
84 |
return $this->quote($field). " > :{$field}{$index}";
|
|
|
85 |
}
|
|
|
86 |
}, $inputs, array_keys($inputs)));
|
|
|
87 |
}, $fields));
|
|
|
88 |
|
|
|
89 |
return [ "({$conditions})", $bindings ];
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
// Resolve an exact type condition and bindings
|
|
|
93 |
protected function resolveExactCondition($fields, $inputs)
|
|
|
94 |
{
|
|
|
95 |
if (! count($inputs)) return null;
|
|
|
96 |
|
|
|
97 |
$bindings = [];
|
|
|
98 |
$values = implode(' OR ', array_map(function ($field) use ($inputs, &$bindings) {
|
|
|
99 |
return implode(', ', array_map(function ($input, $index) use ($field, &$bindings) {
|
|
|
100 |
$bindings["{$field}{$index}"] = $input;
|
|
|
101 |
return ":{$field}{$index}";
|
|
|
102 |
}, $inputs, array_keys($inputs)));
|
|
|
103 |
}, $fields));
|
|
|
104 |
|
|
|
105 |
return [ $this->quote($field) . " IN ({$values})", $bindings ];
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
// Resolve a number type condition and bindings
|
|
|
109 |
protected function resolveNumberCondition($fields, $inputs)
|
|
|
110 |
{
|
|
|
111 |
if (! count($inputs)) return null;
|
|
|
112 |
|
|
|
113 |
$bindings = [];
|
|
|
114 |
$conditions = implode(' OR ', array_map(function ($field) use ($inputs, &$bindings) {
|
|
|
115 |
return implode(' OR ', array_map(function ($input, $index) use ($field, &$bindings) {
|
|
|
116 |
if (preg_match('/^<(\d+(?:\.\d+)?)$/', $input, $match)) {
|
|
|
117 |
$bindings["{$field}{$index}"] = $match[1];
|
|
|
118 |
return $this->quote($field) . " < :{$field}{$index}";
|
|
|
119 |
} elseif (preg_match('/^>(\d+(?:\.\d+)?)$/', $input, $match)) {
|
|
|
120 |
$bindings["{$field}{$index}"] = $match[1];
|
|
|
121 |
return $this->quote($field) . " > :{$field}{$index}";
|
|
|
122 |
} elseif (preg_match('/^(\d+(?:\.\d+)?)-(\d+(?:\.\d+)?)$/', $input, $match)) {
|
|
|
123 |
$bindings["{$field}{$index}from"] = $match[1];
|
|
|
124 |
$bindings["{$field}{$index}to"] = $match[2];
|
|
|
125 |
$quotedField = $this->quote($field);
|
|
|
126 |
return "({$quotedField} > :{$field}{$index}from AND {$quotedField} < :{$field}{$index}to)";
|
|
|
127 |
} else {
|
|
|
128 |
$bindings["{$field}{$index}"] = $input;
|
|
|
129 |
return $this->quote($field) . " = :{$field}{$index}";
|
|
|
130 |
}
|
|
|
131 |
}, $inputs, array_keys($inputs)));
|
|
|
132 |
}, $fields));
|
|
|
133 |
|
|
|
134 |
return [ "({$conditions})", $bindings ];
|
|
|
135 |
}
|
|
|
136 |
|
|
|
137 |
// Resolve a string type condition and bindings
|
|
|
138 |
protected function resolveStringCondition($fields, $inputs)
|
|
|
139 |
{
|
|
|
140 |
if (! count($inputs)) return null;
|
|
|
141 |
|
|
|
142 |
$bindings = [];
|
|
|
143 |
$conditions = implode(' OR ', array_map(function ($field) use ($inputs, &$bindings) {
|
|
|
144 |
return implode(' OR ', array_map(function ($input, $index) use ($field, &$bindings) {
|
|
|
145 |
$bindings["{$field}{$index}"] = $input;
|
|
|
146 |
return $this->quote($field) . " LIKE :{$field}{$index}";
|
|
|
147 |
}, $inputs, array_keys($inputs)));
|
|
|
148 |
}, $fields));
|
|
|
149 |
|
|
|
150 |
return [ "({$conditions})", $bindings ];
|
|
|
151 |
}
|
|
|
152 |
|
|
|
153 |
// Build the where part of the SQL query
|
|
|
154 |
protected function buildQuery()
|
|
|
155 |
{
|
|
|
156 |
$this->query = count($this->conditions) ? 'WHERE ' . implode(' AND ', $this->conditions) : '';
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
// Quotes SQL identifier name properly for the current database
|
|
|
160 |
protected function quote($identifier)
|
|
|
161 |
{
|
|
|
162 |
return $this->pdo && $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql' ? "`{$identifier}`" : "\"{$identifier}\"";
|
|
|
163 |
}
|
|
|
164 |
}
|