Одиночная кавычка при индексации и в запросах

Здравствуйте. Объясните, пожалуйста момент с одиночными кавычками в словах.
Как заставить, если это возможно, добавляться слова с одиночной кавычкой. Я реализую функцию “Did you mean”. И у меня возникают проблемы со словами с одинарной кавычкой. Такие слова не добавляются в словарь целиком, а разбиваются на два слова. Возможно ли этого избежать?
Так же столкнулся ещё с одним моментом, это передача такого слова в CALL KEYWORDS.
В режиме SQL обратный слэш отлично работает для экранирования одинарной кавычки.


Но вот в клиенте PHP никак не могу добиться работы с такими словами. Постоянно получаю ошибку sphinxql: syntax error, unexpected identifier, expecting ')' near 's','tmweb_column',1 AS stats)'

На данный момент я обрабатываю строку со словами для поиска, путём замены кавычки на пробел.
Помогите разобраться с данным вопросом, это я что-то упускаю или всё же слова с кавычкой не токенизируются.

Конфиг индекса 'dict' => 'keywords', 'charset_table'=>'non_cjk', 'index_exact_words' => '1', 'min_word_len' => '2', 'rt_mem_limit' => '526M', 'min_infix_len' => '2'

Вопрос с токенизацией слов с одиночной кавычкой решил с помощью опции blend_chars.
В режиме SQL CALL KEYWORDS прекрасно работает при экранировании кавычки обратным слэшем.
Остался не решён вопрос как передать слова с кавычкой в PHP клиенте. Как только не извращался, постоянно ошибка синтаксиса sphinxql: syntax error, unexpected identifier, expecting ')' near 's','tmweb_column',1 AS stats)

Вроде разобрался и со вторым вопросом. Как оказалось экранировать одиночную кавычку мешает метод escape.
Пока как решение нашёл только использование sql

$params = [
    'mode' => 'raw',
    'body' => [
        'query' => "CALL KEYWORDS('let\'s go', '" . $this->indexName . "', 1 as stats)"
    ]
];
$words = $this->client->sql($params);

Было бы здорово добавить в метод escape возможность экранировать одиночную кавычку, что-то вроде

public static function escape($string): string
{
    $from = ['\\', '\'', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<'];
    $to = ['\\\\', "\\'", '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<'];

    return str_replace($from, $to, $string);
}

Как оказалось экранировать одиночную кавычку мешает метод escape .

Можно пример использования?

Вот пример:

➜  ~ cat test2.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Manticoresearch\Utils;

$config = ['host'=>'127.0.0.1','port'=>9308];
$client = new \Manticoresearch\Client($config);
$index = $client->index('t');

$params = [
   'index' => 't',
   'body' => ['silent'=>true ]
];
$client->indices()->drop($params);

$index->create([
    'f'=>['type'=>'text']
], [
    'blend_chars' => "'"
]);

$index->addDocuments([
        ['id'=>1,'f'=>"abc'def"],
    ]);

$results = $index->search("abc'def")->get();

print_r($results);

В данном случае я вижу недоработку в том, что значение в

    'blend_chars' => "'"

не эскейпится автоматически и нужно делать

    'blend_chars' => "\\'"

Если вы именно это имели в виду, то подтвердите. Иначе, скажите где ещё есть недоработки. Будем фиксить.

Я добавил blend_chars с использованием другой кодировки, это как бы не проблема

'blend_chars' => 'U+27'

Проблема вот где. Вот метод формирующий запрос для SUGGEST, аналогичный и для KEYWORDS

public function setBody($params = null)
    {
        if (isset($this->index)) {
            $binds =[];
            $binds[] = "'" . self::escape($params['query']) . "'";
            $binds[] = "'" . $this->index . "'";
            if (count($params['options']) > 0) {
                foreach ($params['options'] as $name => $value) {
                    $binds[] = "$value AS $name";
                }
            }
            return parent::setBody(['query' => "CALL SUGGEST(" . implode(",", $binds) . ")"]);
        }
        throw new RuntimeException('Index name is missing.');
    }

Срока слов обрабатывается методом escape

public static function escape($string): string
    {
        $from = ['\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<'];
        $to = ['\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<'];
        return str_replace($from, $to, $string);
    }

Для того, чтобы передать в слове одиночную кавычку, нам нужно экранировать её обратным слешем, чтобы финальная строка запроса выглядела так abc\'def. Достичь этого мы можем таким образом

$suggest = $index->suggest("abc\\'def", []);

Но как мы видим строка "abc\\'def" обработается методом escape и заменит \\ на \\\\.
Поскольку результирующая строка обёрнута в одинарные кавычки

$binds[] = "'" . self::escape($params['query']) . "'";

нам нужно чтобы медод escape вернул уже “правильную” строку, но из-за теперешней обработки двойного обратного слеша это невозможно.

Поэтому я и предложил добавить в метод escape добавить обработку обратного слеша, за которым стоит одиночная кавычка.

public static function escape($string): string
    {
        $from = ['\\', '\'', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<'];
        $to = ['\\\\', "\\'", '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<'];
        return str_replace($from, $to, $string);
    }

Pull request не хотите сделать?

1 Like

Ок, сегодня сделаю.