Google 검색 쿼리를 PostgreSQL "TSQuery"로 변환
-
03-07-2019 - |
문제
Google 검색 쿼리를 PostgreSQL의 TO_TSQUERY ()를 공급할 수있는 것으로 어떻게 변환 할 수 있습니까?
기존 라이브러리가 없다면 PHP와 같은 언어로 Google 검색 쿼리를 어떻게 구문 분석해야합니까?
예를 들어 다음 Google-ish 검색 쿼리를 사용하고 싶습니다.
("used cars" OR "new cars") -ford -mistubishi
그리고 그것을 to_tsquery ()-친절한 문자열로 바꿉니다.
('used cars' | 'new cars') & !ford & !mistubishi
나는 이것을 Regexes로 퍼 뜨릴 수 있지만, 그것이 내가 할 수있는 최선입니다. 이것에 대한 강력한 어휘 분석 방법이 있습니까? 다른 데이터베이스 필드에 적용되는 확장 검색 연산자 (Google 사이트 : 및 Intitle :)도 TSQuery 문자열과 분리되어야합니다.
업데이트 : 특수 운영자를 사용하면 Google에서 TSQuery 변환으로 Google에서 Clause 전환이 발생합니다. 그러나 WERE 절에는 하나 이상의 TSQUERIE가 포함될 수 있습니다.
예를 들어 Google 스타일 쿼리 :
((color:blue OR "4x4") OR style:coupe) -color:red used
다음과 같은 클라스에서 SQL을 생성해야합니다.
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'
);
REGEX에서 위의 가능 여부가 확실하지 않습니까?
해결책
솔직히 말해서, 정기적 인 표현은 이런 식으로 갈 수있는 방법이라고 생각합니다. 똑같이, 이것은 재미있는 운동이었습니다. 아래 코드는 매우 프로토 타입입니다. 실제로 Lexer 자체를 구현하지 않았다는 것을 알 수 있습니다. 출력을 가짜로 만들었습니다. 계속하고 싶지만 오늘은 더 많은 여가 시간이 없습니다.
또한 다른 유형의 검색 연산자 등을 지원하는 관점에서 수행해야 할 작업이 훨씬 더 많습니다.
기본적으로, 아이디어는 특정 유형의 쿼리가 Lexed로 공통 형식 (이 경우 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();