From 3e81c3871f1ae16c739ce501e184a8fc8dc1a4af Mon Sep 17 00:00:00 2001 From: Peter Knut Date: Sat, 12 Oct 2024 01:33:25 +0200 Subject: [PATCH] Elasticsearch: New condition operators as the combination of query type and match type - Support for regexp conditions. - Proper formatting of boolean values. --- adminer/include/adminer.inc.php | 7 ++++ plugins/drivers/elastic.php | 71 +++++++++++++++++++-------------- plugins/drivers/elastic5.php | 68 ++++++++++++++++++------------- 3 files changed, 89 insertions(+), 57 deletions(-) diff --git a/adminer/include/adminer.inc.php b/adminer/include/adminer.inc.php index 2dbafbc7..d1193e9c 100644 --- a/adminer/include/adminer.inc.php +++ b/adminer/include/adminer.inc.php @@ -318,6 +318,11 @@ class Adminer { * @return string */ function editVal($val, $field) { + // Format Elasticsearch boolean value, but do not touch PostgreSQL boolean that use string value 't' or 'f'. + if ($field["type"] == "boolean" && is_bool($val)) { + return $val ? "true" : "false"; + } + return $val; } @@ -627,6 +632,8 @@ class Adminer { && (preg_match('~^[-\d.' . (preg_match('~IN$~', $op) ? ',' : '') . ']+$~', $val) || !preg_match('~' . number_type() . '|bit~', $field["type"])) && (!preg_match("~[\x80-\xFF]~", $val) || preg_match('~char|text|enum|set~', $field["type"])) && (!preg_match('~date|timestamp~', $field["type"]) || preg_match('~^\d+-\d+-\d+~', $val)) + && (!preg_match('~^elastic~', DRIVER) || $field["type"] != "boolean" || preg_match('~true|false~', $val)) // Elasticsearch needs boolean value properly formatted. + && (!preg_match('~^elastic~', DRIVER) || strpos($op, "regexp") === false || preg_match('~text|keyword~', $field["type"])) // Elasticsearch can use regexp only on text and keyword fields. ) { $cols[] = $prefix . $driver->convertSearch(idf_escape($name), $where, $field) . $cond; } diff --git a/plugins/drivers/elastic.php b/plugins/drivers/elastic.php index 824b2dea..b593c773 100644 --- a/plugins/drivers/elastic.php +++ b/plugins/drivers/elastic.php @@ -155,28 +155,12 @@ if (isset($_GET["elastic"])) { foreach ($where as $val) { if (preg_match('~^\((.+ OR .+)\)$~', $val, $matches)) { $parts = explode(" OR ", $matches[1]); - $terms = array(); - foreach ($parts as $part) { - list($col, $op, $val) = explode(" ", $part, 3); - $term = array($col => $val); - if ($op == "=") { - $terms[] = array("term" => $term); - } elseif (in_array($op, array("must", "should", "must_not"))) { - $data["query"]["bool"][$op][]["match"] = $term; - } - } - if (!empty($terms)) { - $data["query"]["bool"]["filter"][]["bool"]["should"] = $terms; + foreach ($parts as $part) { + $this->addQueryCondition($part, $data); } } else { - list($col, $op, $val) = explode(" ", $val, 3); - $term = array($col => $val); - if ($op == "=") { - $data["query"]["bool"]["filter"][] = array("term" => $term); - } elseif (in_array($op, array("must", "should", "must_not"))) { - $data["query"]["bool"][$op][]["match"] = $term; - } + $this->addQueryCondition($val, $data); } } @@ -216,6 +200,33 @@ if (isset($_GET["elastic"])) { return new Min_Result($return); } + private function addQueryCondition($val, &$data) + { + list($col, $op, $val) = explode(" ", $val, 3); + + if (!preg_match('~^([^(]+)\(([^)]+)\)$~', $op, $matches)) { + return; + } + $queryType = $matches[1]; // must, should, must_not + $matchType = $matches[2]; // term, match, regexp + + if ($matchType == "regexp") { + $data["query"]["bool"][$queryType][] = [ + "regexp" => [ + $col => [ + "value" => $val, + "flags" => "ALL", + "case_insensitive" => true, + ] + ] + ]; + } else { + $data["query"]["bool"][$queryType][] = [ + $matchType => [$col => $val] + ]; + } + } + function update($type, $record, $queryWhere, $limit = 0, $separator = "\n") { //! use $limit $parts = preg_split('~ *= *~', $queryWhere); @@ -448,18 +459,13 @@ if (isset($_GET["elastic"])) { $result = array( "_id" => array( "field" => "_id", - "full_type" => "text", - "type" => "text", + "full_type" => "_id", + "type" => "_id", "privileges" => array("insert" => 1, "select" => 1, "where" => 1, "order" => 1), ) ); foreach ($mappings as $name => $field) { - $has_index = !isset($field["index"]) || $field["index"]; - - // TODO: privileges: where => $has_index - // TODO: privileges: sort => $field["type"] != "text" - $result[$name] = array( "field" => $name, "full_type" => $field["type"], @@ -564,9 +570,9 @@ if (isset($_GET["elastic"])) { $structured_types = array(); foreach (array( - lang('Numbers') => array("long" => 3, "integer" => 5, "short" => 8, "byte" => 10, "double" => 20, "float" => 66, "half_float" => 12, "scaled_float" => 21), + lang('Numbers') => array("long" => 3, "integer" => 5, "short" => 8, "byte" => 10, "double" => 20, "float" => 66, "half_float" => 12, "scaled_float" => 21, "boolean" => 1), lang('Date and time') => array("date" => 10), - lang('Strings') => array("string" => 65535, "text" => 65535), + lang('Strings') => array("string" => 65535, "text" => 65535, "keyword" => 65535), lang('Binary') => array("binary" => 255), ) as $key => $val) { $types += $val; @@ -576,8 +582,13 @@ if (isset($_GET["elastic"])) { return array( 'possible_drivers' => array("json + allow_url_fopen"), 'jush' => "elastic", - 'operators' => array("=", "must", "should", "must_not"), - 'operator_like' => "should", + 'operators' => array( + "must(term)", "must(match)", "must(regexp)", + "should(term)", "should(match)", "should(regexp)", + "must_not(term)", "must_not(match)", "must_not(regexp)", + ), + 'operator_like' => "should(match)", + 'operator_regexp' => "should(regexp)", 'functions' => array(), 'grouping' => array(), 'edit_functions' => array(array("json")), diff --git a/plugins/drivers/elastic5.php b/plugins/drivers/elastic5.php index e2a8febb..6541ef04 100644 --- a/plugins/drivers/elastic5.php +++ b/plugins/drivers/elastic5.php @@ -154,28 +154,12 @@ if (isset($_GET["elastic5"])) { foreach ($where as $val) { if (preg_match('~^\((.+ OR .+)\)$~', $val, $matches)) { $parts = explode(" OR ", $matches[1]); - $terms = array(); - foreach ($parts as $part) { - list($col, $op, $val) = explode(" ", $part, 3); - $term = array($col => $val); - if ($op == "=") { - $terms[] = array("term" => $term); - } elseif (in_array($op, array("must", "should", "must_not"))) { - $data["query"]["bool"][$op][]["match"] = $term; - } - } - if (!empty($terms)) { - $data["query"]["bool"]["filter"][]["bool"]["should"] = $terms; + foreach ($parts as $part) { + $this->addQueryCondition($part, $data); } } else { - list($col, $op, $val) = explode(" ", $val, 3); - $term = array($col => $val); - if ($op == "=") { - $data["query"]["bool"]["filter"][] = array("term" => $term); - } elseif (in_array($op, array("must", "should", "must_not"))) { - $data["query"]["bool"][$op][]["match"] = $term; - } + $this->addQueryCondition($val, $data); } } @@ -218,6 +202,33 @@ if (isset($_GET["elastic5"])) { return new Min_Result($return); } + private function addQueryCondition($val, &$data) + { + list($col, $op, $val) = explode(" ", $val, 3); + + if (!preg_match('~^([^(]+)\(([^)]+)\)$~', $op, $matches)) { + return; + } + $queryType = $matches[1]; // must, should, must_not + $matchType = $matches[2]; // term, match, regexp + + if ($matchType == "regexp") { + $data["query"]["bool"][$queryType][] = [ + "regexp" => [ + $col => [ + "value" => $val, + "flags" => "ALL", + "case_insensitive" => true, + ] + ] + ]; + } else { + $data["query"]["bool"][$queryType][] = [ + $matchType => [$col => $val] + ]; + } + } + function update($type, $record, $queryWhere, $limit = 0, $separator = "\n") { //! use $limit $parts = preg_split('~ *= *~', $queryWhere); @@ -427,15 +438,13 @@ if (isset($_GET["elastic5"])) { $return = array( "_id" => array( "field" => "_id", - "full_type" => "text", - "type" => "text", + "full_type" => "_id", + "type" => "_id", "privileges" => array("insert" => 1, "select" => 1, "where" => 1, "order" => 1), ) ); foreach ($mappings as $name => $field) { - if (isset($field["index"]) && !$field["index"]) continue; - $return[$name] = array( "field" => $name, "full_type" => $field["type"], @@ -544,9 +553,9 @@ if (isset($_GET["elastic5"])) { $structured_types = array(); foreach (array( - lang('Numbers') => array("long" => 3, "integer" => 5, "short" => 8, "byte" => 10, "double" => 20, "float" => 66, "half_float" => 12, "scaled_float" => 21), + lang('Numbers') => array("long" => 3, "integer" => 5, "short" => 8, "byte" => 10, "double" => 20, "float" => 66, "half_float" => 12, "scaled_float" => 21, "boolean" => 1), lang('Date and time') => array("date" => 10), - lang('Strings') => array("string" => 65535, "text" => 65535), + lang('Strings') => array("string" => 65535, "text" => 65535, "keyword" => 65535), lang('Binary') => array("binary" => 255), ) as $key => $val) { $types += $val; @@ -556,8 +565,13 @@ if (isset($_GET["elastic5"])) { return array( 'possible_drivers' => array("json + allow_url_fopen"), 'jush' => "elastic", - 'operators' => array("=", "must", "should", "must_not"), - 'operator_like' => "should", + 'operators' => array( + "must(term)", "must(match)", "must(regexp)", + "should(term)", "should(match)", "should(regexp)", + "must_not(term)", "must_not(match)", "must_not(regexp)", + ), + 'operator_like' => "should(match)", + 'operator_regexp' => "should(regexp)", 'functions' => array(), 'grouping' => array(), 'edit_functions' => array(array("json")),