2019-06-19 16:01:53 -05:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* QueryBuilderFluentParser.php
|
|
|
|
*
|
|
|
|
* -Description-
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2021-02-09 00:29:04 +01:00
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2019-06-19 16:01:53 -05:00
|
|
|
*
|
2021-02-09 00:29:04 +01:00
|
|
|
* @link https://www.librenms.org
|
2021-09-10 20:09:53 +02:00
|
|
|
*
|
2019-06-19 16:01:53 -05:00
|
|
|
* @copyright 2019 Tony Murray
|
|
|
|
* @author Tony Murray <murraytony@gmail.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace LibreNMS\Alerting;
|
|
|
|
|
|
|
|
use DB;
|
|
|
|
use Illuminate\Database\Query\Builder;
|
2020-04-17 17:37:56 -05:00
|
|
|
use Illuminate\Support\Str;
|
2019-06-19 16:01:53 -05:00
|
|
|
use Log;
|
|
|
|
|
|
|
|
class QueryBuilderFluentParser extends QueryBuilderParser
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Convert the query builder rules to a Laravel Fluent builder
|
|
|
|
*
|
2021-04-01 01:06:07 +02:00
|
|
|
* @return Builder|null
|
2019-06-19 16:01:53 -05:00
|
|
|
*/
|
|
|
|
public function toQuery()
|
|
|
|
{
|
2020-09-21 14:54:51 +02:00
|
|
|
if (empty($this->builder) || ! array_key_exists('condition', $this->builder)) {
|
2019-06-19 16:01:53 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$query = DB::table('devices');
|
|
|
|
|
|
|
|
$this->joinTables($query);
|
|
|
|
|
|
|
|
$this->parseGroupToQuery($query, $this->builder);
|
|
|
|
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-09-08 23:35:56 +02:00
|
|
|
* @param Builder $query
|
|
|
|
* @param array $rule
|
|
|
|
* @param string $parent_condition AND or OR (for root, this should be null)
|
2019-06-19 16:01:53 -05:00
|
|
|
* @return Builder
|
|
|
|
*/
|
|
|
|
protected function parseGroupToQuery($query, $rule, $parent_condition = null)
|
|
|
|
{
|
2020-09-21 14:54:51 +02:00
|
|
|
return $query->where(function ($query) use ($rule) {
|
2019-06-19 16:01:53 -05:00
|
|
|
foreach ($rule['rules'] as $group_rule) {
|
|
|
|
if (array_key_exists('condition', $group_rule)) {
|
|
|
|
$this->parseGroupToQuery($query, $group_rule, $rule['condition']);
|
|
|
|
} else {
|
|
|
|
$this->parseRuleToQuery($query, $group_rule, $rule['condition']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, null, null, $parent_condition ?? $rule['condition']);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-09-08 23:35:56 +02:00
|
|
|
* @param Builder $query
|
|
|
|
* @param array $rule
|
|
|
|
* @param string $condition AND or OR
|
2019-06-19 16:01:53 -05:00
|
|
|
* @return Builder
|
|
|
|
*/
|
|
|
|
protected function parseRuleToQuery($query, $rule, $condition)
|
|
|
|
{
|
2020-09-21 14:54:51 +02:00
|
|
|
[$field, $op, $value] = $this->expandRule($rule);
|
2019-06-19 16:01:53 -05:00
|
|
|
|
|
|
|
switch ($op) {
|
|
|
|
case 'equal':
|
|
|
|
case 'not_equal':
|
|
|
|
case 'less':
|
|
|
|
case 'less_or_equal':
|
|
|
|
case 'greater':
|
|
|
|
case 'greater_or_equal':
|
|
|
|
case 'regex':
|
|
|
|
case 'not_regex':
|
|
|
|
return $query->where($field, self::$operators[$op], $value, $condition);
|
|
|
|
case 'contains':
|
|
|
|
case 'not_contains':
|
|
|
|
return $query->where($field, self::$operators[$op], "%$value%", $condition);
|
|
|
|
case 'begins_with':
|
|
|
|
case 'not_begins_with':
|
|
|
|
return $query->where($field, self::$operators[$op], "$value%", $condition);
|
|
|
|
case 'ends_with':
|
|
|
|
case 'not_ends_with':
|
|
|
|
return $query->where($field, self::$operators[$op], "%$value", $condition);
|
|
|
|
case 'is_empty':
|
|
|
|
case 'is_not_empty':
|
|
|
|
return $query->where($field, self::$operators[$op], '');
|
|
|
|
case 'is_null':
|
|
|
|
case 'is_not_null':
|
|
|
|
return $query->whereNull($field, $condition, $op == 'is_not_null');
|
|
|
|
case 'between':
|
|
|
|
case 'not_between':
|
|
|
|
return $query->whereBetween($field, $value, $condition, $op == 'not_between');
|
|
|
|
case 'in':
|
|
|
|
case 'not_in':
|
|
|
|
$values = preg_split('/[, ]/', $value);
|
|
|
|
if ($values !== false) {
|
|
|
|
return $query->whereIn($field, $values, $condition, $op == 'not_in');
|
|
|
|
}
|
|
|
|
Log::error('Could not parse in values, use comma or space delimiters');
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Log::error('Unhandled QueryBuilderFluentParser operation: ' . $op);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract field, operator and value from the rule and expand macros and raw values
|
|
|
|
*
|
2021-09-08 23:35:56 +02:00
|
|
|
* @param array $rule
|
2019-06-19 16:01:53 -05:00
|
|
|
* @return array [field, operator, value]
|
|
|
|
*/
|
|
|
|
protected function expandRule($rule)
|
|
|
|
{
|
|
|
|
$field = $rule['field'];
|
2020-04-17 17:37:56 -05:00
|
|
|
if (Str::startsWith($field, 'macros.')) {
|
2019-06-19 16:01:53 -05:00
|
|
|
$field = DB::raw($this->expandMacro($field));
|
|
|
|
}
|
|
|
|
|
|
|
|
$op = $rule['operator'];
|
|
|
|
|
|
|
|
$value = $rule['value'];
|
2020-09-21 14:54:51 +02:00
|
|
|
if (! is_array($value) && Str::startsWith($value, '`') && Str::endsWith($value, '`')) {
|
2019-06-19 16:01:53 -05:00
|
|
|
$value = DB::raw($this->expandMacro(trim($value, '`')));
|
|
|
|
}
|
|
|
|
|
|
|
|
return [$field, $op, $value];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-09-08 23:35:56 +02:00
|
|
|
* @param Builder $query
|
2019-06-19 16:01:53 -05:00
|
|
|
* @return Builder
|
|
|
|
*/
|
|
|
|
protected function joinTables($query)
|
|
|
|
{
|
2020-09-21 14:54:51 +02:00
|
|
|
if (! isset($this->builder['joins'])) {
|
2019-06-19 16:01:53 -05:00
|
|
|
$this->generateJoins();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->builder['joins'] as $join) {
|
2020-09-21 14:54:51 +02:00
|
|
|
[$rightTable, $left, $right] = $join;
|
2019-06-19 16:01:53 -05:00
|
|
|
$query->leftJoin($rightTable, $left, $right);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the joins for this rule and store them in the rule.
|
|
|
|
* This is an expensive operation.
|
|
|
|
*
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function generateJoins()
|
|
|
|
{
|
|
|
|
$joins = [];
|
|
|
|
foreach ($this->generateGlue() as $glue) {
|
2020-09-21 14:54:51 +02:00
|
|
|
[$left, $right] = explode(' = ', $glue, 2);
|
2020-04-17 17:37:56 -05:00
|
|
|
if (Str::contains($right, '.')) { // last line is devices.device_id = ? for alerting... ignore it
|
2020-09-21 14:54:51 +02:00
|
|
|
[$leftTable, $leftKey] = explode('.', $left);
|
|
|
|
[$rightTable, $rightKey] = explode('.', $right);
|
2019-06-21 06:53:48 -05:00
|
|
|
$target_table = ($rightTable != 'devices' ? $rightTable : $leftTable); // don't try to join devices
|
|
|
|
|
|
|
|
$joins[] = [$target_table, $left, $right];
|
2019-06-19 16:01:53 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->builder['joins'] = $joins;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
}
|