Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Builds a query.
19
 *
20
 * @package    block_dash
21
 * @copyright  2019 bdecent gmbh <https://bdecent.de>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace block_dash\local\dash_framework\query_builder;
26
 
27
use coding_exception;
28
use dml_exception;
29
 
30
/**
31
 * Builds a query.
32
 *
33
 * @package block_dash
34
 */
35
class builder {
36
 
37
    /**
38
     * @var string
39
     */
40
    private $table;
41
 
42
    /**
43
     * @var string
44
     */
45
    private $tablealias;
46
 
47
    /**
48
     * @var string[]
49
     */
50
    private $selects = [];
51
 
52
    /**
53
     * @var array
54
     */
55
    private $wheres = [];
56
 
57
    /**
58
     * @var array
59
     */
60
    private $rawwhere;
61
 
62
    /**
63
     * @var array
64
     */
65
    private $rawwhereparameters = [];
66
 
67
    /**
68
     * @var int Return a subset of records, starting at this point (optional).
69
     */
70
    private $limitfrom = 0;
71
 
72
    /**
73
     * @var int Return a subset comprising this many records in total (optional, required if $limitfrom is set).
74
     */
75
    private $limitnum = 0;
76
 
77
    /**
78
     * @var array ['field1' => 'ASC', 'field2' => 'DESC', ...]
79
     */
80
    private $orderby = [];
81
 
82
    /**
83
     * @var join[]
84
     */
85
    private $joins = [];
86
 
87
    /**
88
     * @var array ['field1', 'field2', ...]
89
     */
90
    private $groupby = [];
91
 
92
    /**
93
     * Extra conditions to be added in WHERE clause.
94
     *
95
     * @var array
96
     */
97
    private $rawconditions = [];
98
 
99
    /**
100
     * @var array
101
     */
102
    private $rawconditionparameters = [];
103
 
104
    /**
105
     * @var array
106
     */
107
    private $rawjoins = [];
108
 
109
    /**
110
     * @var array
111
     */
112
    private $rawjoinsparameters = [];
113
 
114
    /**
115
     * Fields to retried from sql query. Sql select field.
116
     * @param string $field
117
     * @param string $alias
118
     * @return builder
119
     */
120
    public function select(string $field, string $alias): builder {
121
        $this->selects[$alias] = $field;
122
        return $this;
123
    }
124
 
125
    /**
126
     * Set all selects on builder.
127
     *
128
     * @param array $selects [alias => field, ...]
129
     * @return $this
130
     */
131
    public function set_selects(array $selects): builder {
132
        $this->selects = [];
133
        foreach ($selects as $alias => $select) {
134
            $this->selects[$alias] = $select;
135
        }
136
        return $this;
137
    }
138
 
139
    /**
140
     * Set main table of query.
141
     *
142
     * @param string $table
143
     * @param string $alias
144
     * @return builder
145
     */
146
    public function from(string $table, string $alias): builder {
147
        $this->table = $table;
148
        $this->tablealias = $alias;
149
        return $this;
150
    }
151
 
152
    /**
153
     * Join table in query.
154
     *
155
     * @param string $table Table name of joined table.
156
     * @param string $alias Joined table alias.
157
     * @param string $jointablefield Field of joined table to reference in join condition.
158
     * @param string $origintablefield Field of origin table to join to.
159
     * @param string $jointype SQL join type. See self::TYPE_*
160
     * @param array $extraparameters Extra parameters used in join SQL.
161
     * @return $this
162
     */
163
    public function join(string $table, string $alias, string $jointablefield, string $origintablefield,
164
                         $jointype = join::TYPE_INNER_JOIN, array $extraparameters = []): builder {
165
        $this->joins[] = new join($table, $alias, $jointablefield, $origintablefield, $jointype, $extraparameters);
166
        return $this;
167
    }
168
 
169
    /**
170
     * Join raw in query.
171
     *
172
     * @param string $joinsql SQL join type. See self::TYPE_*
173
     * @param array $parameters Extra parameters used in join SQL.
174
     * @return $this
175
     */
176
    public function join_raw(string $joinsql, array $parameters = []): builder {
177
        $this->rawjoins[] = [$joinsql, $parameters];
178
        return $this;
179
    }
180
 
181
    /**
182
     * Add additional join condition to existing join.
183
     *
184
     * @param string $alias
185
     * @param string $condition
186
     * @return $this
187
     * @throws coding_exception
188
     */
189
    public function join_condition(string $alias, string $condition): builder {
190
        $added = false;
191
        foreach ($this->joins as $join) {
192
            if ($join->get_alias() == $alias) {
193
                $join->add_join_condition($condition);
194
                $added = true;
195
                break;
196
            }
197
        }
198
 
199
        if (!$added) {
200
            throw new coding_exception('Table alias not found: ' . $alias);
201
        }
202
 
203
        return $this;
204
    }
205
 
206
    /**
207
     * Add where clause to query.
208
     *
209
     * @param string $selector Field or alias of where clause.
210
     * @param array $values Values that where clause will compare to.
211
     * @param string $operator Equals, greater than, in, etc etc. See where::OPERATOR_*
212
     * @param string $conjunctive AND, OR etc etc. See where::CONJUCTIVE_OPERATOR_*
213
     *
214
     * @return where
215
     */
216
    public function where(string $selector, array $values, string $operator = where::OPERATOR_EQUAL,
217
        string $conjunctive = where::CONJUNCTIVE_OPERATOR_AND): where {
218
        $where = new where($selector, $values, $operator, $conjunctive);
219
        $this->wheres[] = $where;
220
        return $where;
221
    }
222
 
223
    /**
224
     * Add where (in subquery) clause to query.
225
     *
226
     * @param string $selector Field or alias of where clause.
227
     * @param string $query Subquery of WHERE IN (subquery) clause.
228
     * @param array $params Any extra parameters used in subquery.
229
     * @return where
230
     */
231
    public function where_in_query(string $selector, string $query, array $params = []): where {
232
        $where = new where($selector, []);
233
        $where->set_query($query, $params);
234
        $this->wheres[] = $where;
235
        return $where;
236
    }
237
 
238
    /**
239
     * Add where clause to query.
240
     *
241
     * @param string $wheresql
242
     * @param array $parameters
243
     * @return builder
244
     */
245
    public function where_raw(string $wheresql, array $parameters = []): builder {
246
        $this->rawwhere[] = $wheresql;
247
        $this->rawwhereparameters = array_merge($this->rawwhereparameters, $parameters);
248
 
249
        return $this;
250
    }
251
 
252
    /**
253
     * Order by a field.
254
     *
255
     * @param string $field Field or alias to order by.
256
     * @param string $direction 'ASC' or 'DESC'
257
     * @return builder
258
     * @throws coding_exception
259
     */
260
    public function orderby(string $field, string $direction): builder {
261
        if (!in_array(strtolower($direction), ['asc', 'desc'])) {
262
            throw new coding_exception('Invalid order by direction ' . $direction);
263
        }
264
 
265
        $this->orderby[$field] = $direction;
266
        return $this;
267
    }
268
 
269
    /**
270
     * Remove order by conditions.
271
     *
272
     * @return builder
273
     */
274
    public function remove_orderby(): builder {
275
        $this->orderby = [];
276
        return $this;
277
    }
278
 
279
    /**
280
     * Group by field for aggregations.
281
     *
282
     * @param string $field
283
     * @return builder
284
     */
285
    public function groupby(string $field): builder {
286
        $this->groupby[] = $field;
287
        return $this;
288
    }
289
 
290
    /**
291
     * Add raw condition to builder.
292
     *
293
     * @param string $condition
294
     * @param array $parameters
295
     * @return builder
296
     */
297
    public function rawcondition(string $condition, array $parameters = []): builder {
298
        $this->rawconditions[] = $condition;
299
        $this->rawconditionparameters = $parameters;
300
        return $this;
301
    }
302
 
303
    /**
304
     * Get the query where conditions.
305
     * @return where[]
306
     */
307
    public function get_wheres(): array {
308
        return $this->wheres;
309
    }
310
 
311
    /**
312
     * Get the query limit from.
313
     *
314
     * @return int
315
     */
316
    public function get_limitfrom(): int {
317
        return $this->limitfrom;
318
    }
319
 
320
    /**
321
     * Set the query limit from.
322
     *
323
     * @param int $limitfrom
324
     * @return $this
325
     */
326
    public function limitfrom(int $limitfrom): builder {
327
        $this->limitfrom = $limitfrom;
328
        return $this;
329
    }
330
 
331
    /**
332
     * Get the query limit number.
333
     *
334
     * @return int
335
     */
336
    public function get_limitnum(): int {
337
        return $this->limitnum;
338
    }
339
 
340
    /**
341
     * Set the query limit number.
342
     *
343
     * @param int $limitnum
344
     * @return $this
345
     */
346
    public function limitnum(int $limitnum): builder {
347
        $this->limitnum = $limitnum;
348
        return $this;
349
    }
350
 
351
    /**
352
     * Build and return complete query select SQL.
353
     *
354
     * @return string
355
     */
356
    protected function build_select(): string {
357
        $selects = [];
358
        foreach ($this->selects as $alias => $select) {
359
            $selects[] = $select . ' AS ' . $alias;
360
        }
361
 
362
        return implode(',', $selects);
363
    }
364
 
365
    /**
366
     * Get the query where condition and it parameters.
367
     * @return array
368
     * @throws exception\invalid_operator_exception
369
     */
370
    protected function get_where_sql_and_params(): array {
371
        $wheresql = [];
372
        $params = [];
373
        $wsql = ''; // Where builder queryies.
374
        foreach ($this->get_wheres() as $where) {
375
            [$sql, $wparams] = $where->get_sql_and_params();
376
            $conjunc = $where->get_conjunctive_operator() ?: 'AND';
377
            $wsql .= !empty($wsql) ? sprintf(' %s %s ', $conjunc, $sql) : $sql;
378
            $params = array_merge($params, $wparams);
379
        }
380
 
381
        $wheresql[] = $wsql;
382
 
383
        if ($this->rawwhere) {
384
            foreach ($this->rawwhere as $where) {
385
                $wheresql[] = $where;
386
            }
387
            $params = array_merge($params, $this->rawwhereparameters);
388
        }
389
 
390
        return [implode(' AND ', array_filter($wheresql)), $params];
391
    }
392
 
393
    /**
394
     * Get the query and required parameters.
395
     *
396
     * @return array<string, array>
397
     * @throws exception\invalid_operator_exception
398
     */
399
    final public function get_sql_and_params(): array {
400
        $sql = 'SELECT DISTINCT ' . $this->build_select() . ' FROM {' . $this->table . '} ' . $this->tablealias;
401
        $params = [];
402
 
403
        foreach ($this->joins as $join) {
404
            [$jsql, $jparams] = $join->get_sql_and_params();
405
            $sql .= ' ' . $jsql . ' ';
406
            $params = array_merge($params, $jparams);
407
        }
408
 
409
        foreach ($this->rawjoins as $join) {
410
            [$jsql, $jparams] = $join;
411
            $sql .= ' ' . $jsql . ' ';
412
            $params = array_merge($params, $jparams);
413
        }
414
 
415
        [$wsql, $wparams] = $this->get_where_sql_and_params();
416
 
417
        if ($wsql) {
418
            $sql .= ' WHERE ' . $wsql;
419
            $params = array_merge($params, $wparams);
420
        }
421
 
422
        if (count($this->groupby) > 0) {
423
            $sql .= ' GROUP BY ' . implode(', ', $this->groupby);
424
        }
425
 
426
        if ($this->orderby) {
427
            $orderbys = [];
428
            foreach ($this->orderby as $field => $direction) {
429
                $orderbys[] = sprintf('%s %s', $field, $direction);
430
            }
431
 
432
            $sql .= ' ORDER BY ' . implode(', ', $orderbys);
433
        }
434
 
435
        return [$sql, $params];
436
    }
437
 
438
    /**
439
     * Execute query and return results.
440
     *
441
     * @return array
442
     * @throws dml_exception
443
     * @throws exception\invalid_operator_exception
444
     */
445
    public function query() {
446
        global $DB;
447
 
448
        [$sql, $params] = $this->get_sql_and_params();
449
 
450
        return $DB->get_records_sql($sql, $params, $this->get_limitfrom(), $this->get_limitnum());
451
    }
452
 
453
    /**
454
     * Get number of records this query will return.
455
     *
456
     * @return int
457
     * @throws dml_exception
458
     * @throws exception\invalid_operator_exception
459
     */
460
    public function count(): int {
461
        $builder = clone $this;
462
        $builder->set_selects(['count' => 'COUNT(DISTINCT ' . $this->tablealias . '.id)']);
463
        $builder->limitfrom(0)->limitnum(0)->remove_orderby();
464
        if (!$records = $builder->query()) {
465
            return 0;
466
        }
467
        return array_values($records)[0]->count;
468
    }
469
}