Преобразование поискового запроса Google в PostgreSQL "tsquery" - PullRequest
4 голосов
/ 16 октября 2008

Как я могу преобразовать поисковый запрос Google во что-то, что я могу кормить to_tsquery () PostgreSQL?

Если там нет существующей библиотеки, как мне разобраться с поисковым запросом Google на таком языке, как PHP?

Например, я бы хотел взять следующий поисковый запрос Google:

("used cars" OR "new cars") -ford -mistubishi

И превратить его в дружественную строку to_tsquery ():

('used cars' | 'new cars') & !ford & !mistubishi

Я могу выдумать это с помощью регулярных выражений, но это лучшее, что я могу сделать. Есть ли надежный метод лексического анализа? Я также хотел бы иметь возможность поддерживать расширенные операторы поиска (например, сайт Google: и intitle :), которые будут применяться к различным полям базы данных и, следовательно, должны быть отделены от строки tsquery.

ОБНОВЛЕНИЕ: я понимаю, что со специальными операторами это становится преобразованием предложения WHERE из Google в SQL, а не из преобразования Google в tsquery. Но предложение WHERE может содержать один или несколько запросов.

Например, запрос в стиле Google:

((color:blue OR "4x4") OR style:coupe) -color:red used

Должен выдать SQL-предложение WHERE наподобие этого:

WHERE to_tsvector(description) MATCH to_tsquery('used')
  AND color <> 'red'
  AND ( (color = 'blue' OR to_tsvector(description) MATCH to_tsquery('4x4') )
    OR style = 'coupe'
  );

Я не уверен, возможно ли вышеизложенное с помощью регулярных выражений?

1 Ответ

3 голосов
/ 16 октября 2008

Честно, я думаю, что регулярные выражения - это способ сделать что-то подобное. Точно так же это было забавное упражнение. Код ниже очень прототипный - на самом деле вы увидите, что я даже не реализовал сам лексер - я просто подделал вывод. Я бы хотел продолжить, но у меня просто нет больше свободного времени сегодня.

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

По сути, идея заключается в том, что определенный тип запроса подвергается лексическому анализу, а затем анализируется в общий формат (в данном случае, экземпляр QueryExpression), который затем отображается обратно как запрос другого типа.

<?php

ini_set( "display_errors", "on" );
error_reporting( E_ALL );

interface ILexer
{
    public function execute( $str );
    public function getTokens();
}

interface IParser
{
    public function __construct( iLexer $lexer );
    public function parse( $input );
    public function addToken( $token );
}

class GoogleQueryLexer implements ILexer
{
    private $tokenStack = array();

    public function execute( $str )
    {
        $chars = str_split( $str );
        foreach ( $chars as $char )
        {
            //  add to self::$tokenStack per your rules
        }

        //'("used cars" OR "new cars") -ford -mistubishi'
        $this->tokenStack = array(
                '('
            ,   'used cars'
            ,   'or new cars'
            ,   ')'
            ,   '-ford'
            ,   '-mitsubishi'
        );
    }

    public function getTokens()
    {
        return $this->tokenStack;
    }
}

class GoogleQueryParser implements IParser
{
    protected $lexer;

    public function __construct( iLexer $lexer )
    {
        $this->lexer = $lexer;
    }

    public function addToken( $token )
    {
        $this->tokenStack[] = $token;
    }

    public function parse( $input )
    {
        $this->lexer->execute( $input );
        $tokens = $this->lexer->getTokens();

        $expression = new QueryExpression();

        foreach ( $tokens as $token )
        {
            $expression = $this->processToken( $token, $expression );
        }

        return $expression;
    }

    protected function processToken( $token, QueryExpression $expression )
    {
        switch ( $token )
        {
            case '(':
                return $expression->initiateSubExpression();
                break;
            case ')':
                return $expression->getParentExpression();
                break;
            default:
                $modifier   = $token[0];
                $phrase     = substr( $token, 1 );
                switch ( $modifier )
                {
                    case '-':
                        $expression->addExclusionPhrase( $phrase );
                        break;
                    case '+':
                        $expression->addPhrase( $phrase );
                        break;
                    default:
                        $operator   = trim( substr( $token, 0, strpos( $token, ' ' ) ) );
                        $phrase     = trim( substr( $token, strpos( $token, ' ' ) ) );
                        switch ( strtolower( $operator ) )
                        {
                            case 'and':
                                $expression->addAndPhrase( $phrase );
                                break;
                            case 'or':
                                $expression->addOrPhrase( $phrase );
                                break;
                            default:
                                $expression->addPhrase( $token );
                        }
                }
        }
        return $expression;
    }
}

class QueryExpression
{
    protected $phrases = array();
    protected $subExpressions = array();
    protected $parent;

    public function __construct( $parent=null )
    {
        $this->parent = $parent;
    }

    public function initiateSubExpression()
    {
        $expression = new self( $this );
        $this->subExpressions[] = $expression;
        return $expression;
    }

    public function getPhrases()
    {
        return $this->phrases;
    }

    public function getSubExpressions()
    {
        return $this->subExpressions;
    }

    public function getParentExpression()
    {
        return $this->parent;
    }

    protected function addQueryPhrase( QueryPhrase $phrase )
    {
        $this->phrases[] = $phrase;
    }

    public function addPhrase( $input )
    {
        $this->addQueryPhrase( new QueryPhrase( $input ) );
    }

    public function addOrPhrase( $input )
    {
        $this->addQueryPhrase( new QueryPhrase( $input, QueryPhrase::MODE_OR ) );
    }

    public function addAndPhrase( $input )
    {
        $this->addQueryPhrase( new QueryPhrase( $input, QueryPhrase::MODE_AND ) );
    }

    public function addExclusionPhrase( $input )
    {
        $this->addQueryPhrase( new QueryPhrase( $input, QueryPhrase::MODE_EXCLUDE ) );
    }
}

class QueryPhrase
{
    const MODE_DEFAULT = 1;
    const MODE_OR = 2;
    const MODE_AND = 3;
    const MODE_EXCLUDE = 4;

    protected $phrase;
    protected $mode;

    public function __construct( $input, $mode=self::MODE_DEFAULT )
    {
        $this->phrase = $input;
        $this->mode = $mode;
    }

    public function getMode()
    {
        return $this->mode;
    }

    public function __toString()
    {
        return $this->phrase;
    }
}

class TsqueryBuilder
{
    protected $expression;
    protected $query;

    public function __construct( QueryExpression $expression )
    {
        $this->query = trim( $this->processExpression( $expression ), ' &|' );
    }

    public function getResult()
    {
        return $this->query;
    }

    protected function processExpression( QueryExpression $expression )
    {
        $query = '';
        $phrases = $expression->getPhrases();
        $subExpressions = $expression->getSubExpressions();

        foreach ( $phrases as $phrase )
        {
            $format = "'%s' ";
            switch ( $phrase->getMode() )
            {
                case QueryPhrase::MODE_AND :
                    $format = "& '%s' ";
                    break;
                case QueryPhrase::MODE_OR :
                    $format = "| '%s' ";
                    break;
                case QueryPhrase::MODE_EXCLUDE :
                    $format = "& !'%s' ";
                    break;
            }
            $query .= sprintf( $format, str_replace( "'", "\\'", $phrase ) );
        }

        foreach ( $subExpressions as $subExpression )
        {
            $query .= "& (" . $this->processExpression( $subExpression ) . ")";
        }
        return $query;
    }
}

$parser = new GoogleQueryParser( new GoogleQueryLexer() );

$queryBuilder = new TsqueryBuilder( $parser->parse( '("used cars" OR "new cars") -ford -mistubishi' ) );

echo $queryBuilder->getResult();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...