diff --git a/LibreNMS/Alerting/QueryBuilderParser.php b/LibreNMS/Alerting/QueryBuilderParser.php index d786505c99..8e8ac64571 100644 --- a/LibreNMS/Alerting/QueryBuilderParser.php +++ b/LibreNMS/Alerting/QueryBuilderParser.php @@ -47,20 +47,37 @@ class QueryBuilderParser implements \JsonSerializable 'less_or_equal' => "<=", 'greater' => ">", 'greater_or_equal' => ">=", - 'begins_with' => "LIKE (\"%?\")", - 'not_begins_with' => "NOT LIKE (\"%?\")", - 'contains' => "LIKE (\"%?%\")", - 'not_contains' => "NOT LIKE (\"%?%\")", - 'ends_with' => "LIKE (\"?%\")", - 'not_ends_with' => "NOT LIKE (\"?%\")", - 'is_empty' => "=", // value will be empty - 'is_not_empty' => "!=", // value will be empty + 'between' => 'BETWEEN', + 'not_between' => 'NOT BETWEEN', + 'begins_with' => "LIKE", + 'not_begins_with' => "NOT LIKE", + 'contains' => "LIKE", + 'not_contains' => "NOT LIKE", + 'ends_with' => "LIKE", + 'not_ends_with' => "NOT LIKE", + 'is_empty' => "=", + 'is_not_empty' => "!=", 'is_null' => "IS NULL", 'is_not_null' => "IS NOT NULL", 'regex' => 'REGEXP', 'not_regex' => 'NOT REGEXP', ]; + private static $values = [ + 'between' => "? AND ?", + 'not_between' => "? AND ?", + 'begins_with' => "'?%'", + 'not_begins_with' => "'?%'", + 'contains' => "'%?%'", + 'not_contains' => "'%?%'", + 'ends_with' => "'%?'", + 'not_ends_with' => "'%?'", + 'is_null' => "", + 'is_not_null' => "", + 'is_empty' => "''", + 'is_not_empty' => "''", + ]; + private $builder; private $schema; @@ -263,31 +280,33 @@ class QueryBuilderParser implements \JsonSerializable */ private function parseRule($rule, $expand = false) { - $op = self::$operators[$rule['operator']]; + $field = $rule['field']; + $builder_op = $rule['operator']; + $op = self::$operators[$builder_op]; $value = $rule['value']; - if (starts_with($value, '`') && ends_with($value, '`')) { + if (is_string($value) && starts_with($value, '`') && ends_with($value, '`')) { // pass through value such as field $value = trim($value, '`'); - $value = $this->expandMacro($value); // check for macros - } elseif ($rule['type'] != 'integer' && !str_contains($op, '?')) { + if ($expand) { + $value = $this->expandMacro($value); + } + } elseif (isset(self::$values[$builder_op])) { + // wrap values as needed (is null values don't contain ? so '' is returned) + $values = (array) $value; + $value = preg_replace_callback('/\?/', function ($matches) use (&$values) { + return array_shift($values); + }, self::$values[$builder_op]); + } elseif (!is_numeric($value)) { + // wrap quotes around non-numeric values $value = "\"$value\""; } - $field = $rule['field']; if ($expand) { $field = $this->expandMacro($field); } - if (str_contains($op, '?')) { - // op with value inside it aka IN and NOT IN - $sql = "$field " . str_replace('?', $value, $op); - } else { - $sql = "$field $op $value"; - } - - - return $sql; + return trim("$field $op $value"); } /** diff --git a/tests/data/misc/querybuilder.json b/tests/data/misc/querybuilder.json index cd98f86a1e..8661e7c7ec 100644 --- a/tests/data/misc/querybuilder.json +++ b/tests/data/misc/querybuilder.json @@ -1,4 +1,76 @@ [ + [ + "", + {"condition":"OR","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"begins_with","value":"begin"}],"valid":true}, + "devices.hostname LIKE 'begin%'", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE 'begin%'" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"not_begins_with","value":"notbegin"}],"valid":true}, + "devices.hostname NOT LIKE 'notbegin%'", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE 'notbegin%'" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"contains","value":"contains"}],"valid":true}, + "devices.hostname LIKE '%contains%'", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE '%contains%'" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"not_contains","value":"notcontains"}],"valid":true}, + "devices.hostname NOT LIKE '%notcontains%'", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE '%notcontains%'" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"ends_with","value":"ends"}],"valid":true}, + "devices.hostname LIKE '%ends'", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE '%ends'" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"not_ends_with","value":"notends"}],"valid":true}, + "devices.hostname NOT LIKE '%notends'", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE '%notends'" + ], + [ + "", + {"condition":"AND","rules":[{"id":"ports.ifDescr","field":"ports.ifDescr","type":"string","input":"text","operator":"is_null","value":""}],"valid":true}, + "ports.ifDescr IS NULL", + "SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND ports.ifDescr IS NULL" + ], + [ + "", + {"condition":"AND","rules":[{"id":"ports.ifDescr","field":"ports.ifDescr","type":"string","input":"text","operator":"is_not_null","value":""}],"valid":true}, + "ports.ifDescr IS NOT NULL", + "SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND ports.ifDescr IS NOT NULL" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.hardware","field":"devices.hardware","type":"string","input":"text","operator":"is_empty","value":null}],"valid":true}, + "devices.hardware = ''", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hardware = ''" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.hardware","field":"devices.hardware","type":"string","input":"text","operator":"is_not_empty","value":null}],"valid":true}, + "devices.hardware != ''", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hardware != ''" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.device_id","field":"devices.device_id","type":"integer","input":"number","operator":"between","value":["3","99"]}],"valid":true}, + "devices.device_id BETWEEN 3 AND 99", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.device_id BETWEEN 3 AND 99" + ], + [ + "", + {"condition":"AND","rules":[{"id":"devices.device_id","field":"devices.device_id","type":"integer","input":"number","operator":"not_between","value":["2","4"]}],"valid":true}, + "devices.device_id NOT BETWEEN 2 AND 4", + "SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.device_id NOT BETWEEN 2 AND 4" + ], [ "%macros.device_up = 1", {"condition":"AND","rules":[{"id":"macros.device_up","field":"macros.device_up","type":"integer","input":"radio","operator":"equal","value":"1"}],"valid":true}, diff --git a/tests/data/moxa-etherdevice_edsp510a8poe.json b/tests/data/moxa-etherdevice_edsp510a8poe.json index 97bc135098..ed5ac48cfa 100644 --- a/tests/data/moxa-etherdevice_edsp510a8poe.json +++ b/tests/data/moxa-etherdevice_edsp510a8poe.json @@ -2,29 +2,6 @@ "sensors": { "discovery": { "sensors": [ - { - "sensor_deleted": "0", - "sensor_class": "dbm", - "poller_type": "snmp", - "sensor_oid": ".1.3.6.1.4.1.8691.7.86.1.10.7.1.5.9", - "sensor_index": "sfpTxPower.9", - "sensor_type": "moxa-etherdevice", - "sensor_descr": "Ethernet Port G1 SFP module Transmit Power", - "sensor_divisor": "1", - "sensor_multiplier": "1", - "sensor_current": "-6.1", - "sensor_limit": "-5.795", - "sensor_limit_warn": null, - "sensor_limit_low": "-6.405", - "sensor_limit_low_warn": null, - "sensor_alert": "1", - "sensor_custom": "No", - "entPhysicalIndex": null, - "entPhysicalIndex_measured": null, - "sensor_prev": null, - "user_func": null, - "state_name": null - }, { "sensor_deleted": "0", "sensor_class": "dbm", @@ -48,6 +25,29 @@ "user_func": null, "state_name": null }, + { + "sensor_deleted": "0", + "sensor_class": "dbm", + "poller_type": "snmp", + "sensor_oid": ".1.3.6.1.4.1.8691.7.86.1.10.7.1.5.9", + "sensor_index": "sfpTxPower.9", + "sensor_type": "moxa-etherdevice", + "sensor_descr": "Ethernet Port G1 SFP module Transmit Power", + "sensor_divisor": "1", + "sensor_multiplier": "1", + "sensor_current": "-6.1", + "sensor_limit": "-5.795", + "sensor_limit_warn": null, + "sensor_limit_low": "-6.405", + "sensor_limit_low_warn": null, + "sensor_alert": "1", + "sensor_custom": "No", + "entPhysicalIndex": null, + "entPhysicalIndex_measured": null, + "sensor_prev": null, + "user_func": null, + "state_name": null + }, { "sensor_deleted": "0", "sensor_class": "power",