diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..8731e67 --- /dev/null +++ b/.php_cs @@ -0,0 +1,30 @@ +in(__DIR__.'/src') +; + +$rules = [ + '@Symfony' => true, + 'list_syntax' => ['syntax' => 'short'], + 'array_syntax' => ['syntax' => 'short'], + 'linebreak_after_opening_tag' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_imports' => true, + // Risky checks + 'psr4' => true, + 'ereg_to_preg' => true, + 'is_null' => true, + 'non_printable_character' => true, + 'random_api_migration' => true, + 'ternary_to_null_coalescing' => true, +]; + +return PhpCsFixer\Config::create() + ->setRules($rules) + ->setUsingCache(false) + ->setFinder($finder) + ->setRiskyAllowed(true) +; + diff --git a/README.md b/README.md index ae5ac2d..3edfac0 100644 --- a/README.md +++ b/README.md @@ -6,32 +6,33 @@ A simple to use set of database types, and annotations to use postgresql's full ## Installation * Register Doctrine Annotation: - + ```php \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace("VertigoLabs\\DoctrineFullTextPostgres\\ORM\\Mapping\\"); ``` * Register Doctrine Type: - + ```php Type::addType('tsvector',\VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVectorType::class); ``` * Register Doctrine Event Subscriber - + ```php $this->em->getEventManager()->addEventSubscriber(new \VertigoLabs\DoctrineFullTextPostgres\Common\TsVectorSubscriber()); ``` - + * Register Doctrine Functions ```php $doctrineConfig->addCustomStringFunction('tsquery', \VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsQueryFunction::class); + $doctrineConfig->addCustomStringFunction('tsplainquery', \VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsPlainQueryFunction::class); $doctrineConfig->addCustomStringFunction('tsrank', \VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankFunction::class); $doctrineConfig->addCustomStringFunction('tsrankcd', \VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankCDFunction::class); ``` - + ## Symfony installation - + * Add to config - + ```yaml doctrine: dbal: @@ -45,6 +46,7 @@ A simple to use set of database types, and annotations to use postgresql's full dql: string_functions: tsquery: VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsQueryFunction + tsplainquery: VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsPlainQueryFunction tsrank: VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankFunction tsrankcd: VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankCDFunction @@ -52,16 +54,16 @@ services: vertigolabs.doctrinefulltextpostgres.listener: class: VertigoLabs\DoctrineFullTextPostgres\Common\TsVectorSubscriber tags: - - { name: doctrine.event_subscriber, connection: default } + - { name: doctrine.event_subscriber, connection: default } ``` - + ## Usage * Create your entity - + You do not have to create column annotations for your fields that will hold your full text search vectors (tsvector) the columns will be created automatically. A TsVector annotation only requires the ```fields``` parameter. There are optional ```weight``` and ```language``` parameters as well, however they are not used yet. You do not need to set data for your TsVector field, the data will come from the fields specified in the ```fields``` property automatically when the object is flushed to the database - + ```php class Article { @@ -70,19 +72,19 @@ services: * @Column(name="title", type="string", nullable=false) */ private $title; - + /** * @var TsVector * @TsVector(name="title_fts", fields={"title"}) */ private $titleFTS; - + /** * @var string * @Column(name="body", type="text", nullable=true) */ private $body; - + /** * @var TsVector * @TsVector(name="body_fts", fields={"body"}) @@ -90,11 +92,11 @@ services: private $bodyFTS; } ``` - + * Insert some data - + You do not need to worry about setting any data to the fields marked with the TsVector annotation. The data for these fields will be automatically populated when you flush your changes to the database. - + ```php $article = new Article(); $article->setTitle('Baboons Invade Seaworld'); @@ -102,33 +104,33 @@ services: $this->em->persist($article); $this->em->flush(); ``` - + * Query your database! - + When you query your database, you'll query against the actual data. the query will be modified to search using the fields marked with the TsVector annotation automatically - + ```php $query = $this->em->createQuery('SELECT a FROM Article a WHERE tsquery(a.title,:searchQuery) = true'); $query->setParameter('searchQuery','Baboons'); $result = $query->getArrayResult(); - ``` - + ``` + If you'd like to retrieve the ranking of your full text search, simply use the tsrank function: - + ```php $query = $this->em->createQuery('SELECT a, tsrank(a.title,:searchQuery) as rank FROM Article a WHERE tsquery(a.title,:searchQuery) = true'); $query->setParameter('searchQuery','Baboons'); $result = $query->getArrayResult(); - + var_dump($result[0]['rank']); // int 0.67907 - ``` - + ``` + You can even order by rank: - + ```php $query = $this->em->createQuery('SELECT a FROM Article a WHERE tsquery(a.title,:searchQuery) = true ORDER BY tsrank(a.title,:searchQuery) DESC'); - ``` - + ``` + ## TODO * Add language to SQL field definition * Add language and weighting to queries diff --git a/src/Common/TsVectorSubscriber.php b/src/Common/TsVectorSubscriber.php index fb5ae19..f64fe46 100644 --- a/src/Common/TsVectorSubscriber.php +++ b/src/Common/TsVectorSubscriber.php @@ -20,166 +20,184 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\MappingException; +use VertigoLabs\DoctrineFullTextPostgres\DBAL\Types\TsVector as TsVectorType; use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; -use \VertigoLabs\DoctrineFullTextPostgres\DBAL\Types\TsVector as TsVectorType; /** - * Class TsVectorSubscriber - * @package VertigoLabs\DoctrineFullTextPostgres\Common + * Class TsVectorSubscriber. */ class TsVectorSubscriber implements EventSubscriber { - const ANNOTATION_NS = 'VertigoLabs\\DoctrineFullTextPostgres\\ORM\\Mapping\\'; - const ANNOTATION_TSVECTOR = 'TsVector'; - - private static $supportedTypes = [ - 'string', - 'text', - 'array', - 'simple_array', - 'json', - 'json_array', - ]; - - /** - * @var AnnotationReader - */ - private $reader; - - public function __construct() - { - AnnotationRegistry::registerAutoloadNamespace(self::ANNOTATION_NS); - $this->reader = new AnnotationReader(); - - if (!Type::hasType(strtolower(self::ANNOTATION_TSVECTOR))) { - Type::addType(strtolower(self::ANNOTATION_TSVECTOR),TsVectorType::class); - } - } - /** - * Returns an array of events this subscriber wants to listen to. - * - * @return array - */ - public function getSubscribedEvents() - { - return [ - Events::loadClassMetadata, - Events::preFlush, - Events::preUpdate, - ]; - } - - public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) - { - /** @var ClassMetadata $metaData */ - $metaData = $eventArgs->getClassMetadata(); - - $class = $metaData->getReflectionClass(); - foreach($class->getProperties() as $prop) { - /** @var TsVector $annotation */ - $annotation = $this->reader->getPropertyAnnotation($prop, self::ANNOTATION_NS . self::ANNOTATION_TSVECTOR ); - if (is_null( $annotation ) ) { - continue; - } - $this->checkWatchFields($class, $prop, $annotation); - $metaData->mapField([ - 'fieldName' => $prop->getName(), - 'columnName'=>$this->getColumnName($prop,$annotation), - 'type'=>'tsvector', - 'weight'=> strtoupper($annotation->weight), - 'language' => strtolower($annotation->language), - 'nullable' => $this->isWatchFieldNullable($class, $annotation) - ]); - } - } - - public function preFlush(PreFlushEventArgs $eventArgs) - { - $uow = $eventArgs->getEntityManager()->getUnitOfWork(); - $insertions = $uow->getScheduledEntityInsertions(); - $this->setTsVector($insertions); - } - - public function preUpdate(PreUpdateEventArgs $eventArgs) - { - $uow = $eventArgs->getEntityManager()->getUnitOfWork(); - $updates = $uow->getScheduledEntityUpdates(); - $this->setTsVector($updates); - } - - private function setTsVector($entities) { - foreach($entities as $entity) { - $refl = new \ReflectionObject($entity); - foreach ($refl->getProperties() as $prop) { - /** @var TsVector $annot */ - $annot = $this->reader->getPropertyAnnotation($prop, TsVector::class); - if (is_null($annot)) { - continue; - } - - $fields = $annot->fields; - $tsVectorVal = []; - foreach($fields as $field) { - $field = $refl->getProperty($field); - $field->setAccessible(true); - $fieldValue = $field->getValue($entity); - if (is_array($fieldValue)) { - $fieldValue = implode(' ', $fieldValue); - } - $tsVectorVal[] = $fieldValue; - } - $prop->setAccessible(true); - $value = [ - 'data'=>join(' ',$tsVectorVal), - 'language'=>$annot->language, - 'weight'=>$annot->weight - ]; - $prop->setValue($entity,$value); - } - } - } - - private function getColumnName(\ReflectionProperty $property, TsVector $annotation) - { - $name = $annotation->name; - if (is_null($name)) { - $name = $property->getName(); - } - return $name; - } - - private function checkWatchFields(\ReflectionClass $class, \ReflectionProperty $targetProperty, TsVector $annotation) - { - foreach ($annotation->fields as $fieldName) { - if (!$class->hasProperty($fieldName)) { - throw new MappingException(sprintf('Class does not contain %s property',$fieldName)); - } - $property = $class->getProperty($fieldName); - /** @var Column $propAnnot */ - $propAnnot = $this->reader->getPropertyAnnotation($property, Column::class ); - if (!in_array($propAnnot->type, self::$supportedTypes)) { - throw new AnnotationException(sprintf( - '%s::%s TsVector field can only be assigned to ( "%s" ) columns. %1$s::%s has the type %s', - $class->getName(), - $targetProperty->getName(), - implode('" | "', self::$supportedTypes), - $fieldName, - $propAnnot->type - )); - } - } - } - - private function isWatchFieldNullable(\ReflectionClass $class, TsVector $annotation) - { - foreach ($annotation->fields as $fieldName) { - $property = $class->getProperty($fieldName); - /** @var Column $propAnnot */ - $propAnnot = $this->reader->getPropertyAnnotation($property, Column::class ); - if ($propAnnot->nullable === false) { - return false; - } - } - return true; - } + const ANNOTATION_NS = 'VertigoLabs\\DoctrineFullTextPostgres\\ORM\\Mapping\\'; + const ANNOTATION_TSVECTOR = 'TsVector'; + + private static $supportedTypes = [ + 'string', + 'text', + 'array', + 'simple_array', + 'json', + 'json_array', + ]; + + /** + * @var AnnotationReader + */ + private $reader; + + public function __construct() + { + AnnotationRegistry::registerAutoloadNamespace(self::ANNOTATION_NS); + $this->reader = new AnnotationReader(); + + if (!Type::hasType(strtolower(self::ANNOTATION_TSVECTOR))) { + Type::addType(strtolower(self::ANNOTATION_TSVECTOR), TsVectorType::class); + } + } + + /** + * Returns an array of events this subscriber wants to listen to. + * + * @return array + */ + public function getSubscribedEvents() + { + return [ + Events::loadClassMetadata, + Events::preFlush, + Events::preUpdate, + ]; + } + + public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) + { + /** @var ClassMetadata $metaData */ + $metaData = $eventArgs->getClassMetadata(); + + $class = $metaData->getReflectionClass(); + foreach ($class->getProperties() as $prop) { + /** @var TsVector $annotation */ + $annotation = $this->reader->getPropertyAnnotation($prop, self::ANNOTATION_NS.self::ANNOTATION_TSVECTOR); + if (null === $annotation) { + continue; + } + $this->checkWatchFields($class, $prop, $annotation); + $metaData->mapField([ + 'fieldName' => $prop->getName(), + 'columnName' => $this->getColumnName($prop, $annotation), + 'type' => 'tsvector', + 'weight' => strtoupper($annotation->weight), + 'language' => strtolower($annotation->language), + ]); + } + } + + public function preFlush(PreFlushEventArgs $eventArgs) + { + $uow = $eventArgs->getEntityManager()->getUnitOfWork(); + $insertions = $uow->getScheduledEntityInsertions(); + $this->setTsVector($insertions); + } + + public function preUpdate(PreUpdateEventArgs $eventArgs) + { + $uow = $eventArgs->getEntityManager()->getUnitOfWork(); + $updates = $uow->getScheduledEntityUpdates(); + $this->setTsVector($updates); + } + + private function setTsVector($entities) + { + foreach ($entities as $entity) { + $refl = new \ReflectionObject($entity); + foreach ($refl->getProperties() as $prop) { + /** @var TsVector $annot */ + $annot = $this->reader->getPropertyAnnotation($prop, TsVector::class); + if (null === $annot) { + continue; + } + + $fields = $annot->fields; + $tsVectorVal = []; + foreach ($fields as $field) { + if ($refl->hasMethod($field)) { + $method = $refl->getMethod($field); + $method->setAccessible(true); + $methodValue = $method->invoke($entity); + if (is_array($methodValue)) { + $methodValue = implode(' ', $methodValue); + } + $tsVectorVal[] = $methodValue; + } + if ($refl->hasProperty($field)) { + $field = $refl->getProperty($field); + $field->setAccessible(true); + $fieldValue = $field->getValue($entity); + if (is_array($fieldValue)) { + $fieldValue = implode(' ', $fieldValue); + } + $tsVectorVal[] = $fieldValue; + } + } + $prop->setAccessible(true); + $value = [ + 'data' => join(' ', $tsVectorVal), + 'language' => $annot->language, + 'weight' => $annot->weight, + ]; + $prop->setValue($entity, $value); + } + } + } + + private function getColumnName(\ReflectionProperty $property, TsVector $annotation) + { + $name = $annotation->name; + if (null === $name) { + $name = $property->getName(); + } + + return $name; + } + + private function checkWatchFields(\ReflectionClass $class, \ReflectionProperty $targetProperty, TsVector $annotation) + { + foreach ($annotation->fields as $fieldName) { + if ($class->hasMethod($fieldName)) { + continue; + } + + if (!$class->hasProperty($fieldName)) { + throw new MappingException(sprintf('Class does not contain %s property or getter', $fieldName)); + } + + $property = $class->getProperty($fieldName); + /** @var Column $propAnnot */ + $propAnnot = $this->reader->getPropertyAnnotation($property, Column::class); + if (!in_array($propAnnot->type, self::$supportedTypes)) { + throw new AnnotationException(sprintf( + '%s::%s TsVector field can only be assigned to ( "%s" ) columns. %1$s::%s has the type %s', + $class->getName(), + $targetProperty->getName(), + implode('" | "', self::$supportedTypes), + $fieldName, + $propAnnot->type + )); + } + } + } + + private function isWatchFieldNullable(\ReflectionClass $class, TsVector $annotation) + { + foreach ($annotation->fields as $fieldName) { + $property = $class->getProperty($fieldName); + /** @var Column $propAnnot */ + $propAnnot = $this->reader->getPropertyAnnotation($property, Column::class); + if (false === $propAnnot->nullable) { + return false; + } + } + + return true; + } } diff --git a/src/DBAL/Types/TsVector.php b/src/DBAL/Types/TsVector.php index 5879bfc..5212ce7 100644 --- a/src/DBAL/Types/TsVector.php +++ b/src/DBAL/Types/TsVector.php @@ -12,76 +12,75 @@ use Doctrine\DBAL\Types\Type; /** - * Class TsVector - * @package VertigoLabs\DoctrineFullTextPostgres\DBAL\Types + * Class TsVector. + * * @todo figure out how to get the weight into the converted sql code */ class TsVector extends Type { + /** + * Gets the SQL declaration snippet for a field of this type. + * + * @param array $fieldDeclaration the field declaration + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform the currently used database platform + * + * @return string + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return 'tsvector'; + } - /** - * Gets the SQL declaration snippet for a field of this type. - * - * @param array $fieldDeclaration The field declaration. - * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. - * - * @return string - */ - public function getSQLDeclaration( array $fieldDeclaration, AbstractPlatform $platform ) - { - return 'tsvector'; - } + public function canRequireSQLConversion() + { + return true; + } - public function canRequireSQLConversion() - { - return true; - } + /** + * Converts a value from its database representation to its PHP representation + * of this type. + * + * @param mixed $value the value to convert + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform the currently used database platform + * + * @return mixed the PHP representation of the value + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value; + } - /** - * Converts a value from its database representation to its PHP representation - * of this type. - * - * @param mixed $value The value to convert. - * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. - * - * @return mixed The PHP representation of the value. - */ - public function convertToPHPValue( $value, AbstractPlatform $platform ) - { - return $value; - } + /** + * Converts a value from its PHP representation to its database representation + * of this type. + * + * @param mixed $value the value to convert + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform the currently used database platform + * + * @return mixed the database representation of the value + */ + public function convertToDatabaseValueSQL($sqlExp, AbstractPlatform $platform) + { + return sprintf("to_tsvector('english', ?)", $sqlExp); + } - /** - * Converts a value from its PHP representation to its database representation - * of this type. - * - * @param mixed $value The value to convert. - * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. - * - * @return mixed The database representation of the value. - */ - public function convertToDatabaseValueSQL( $sqlExp, AbstractPlatform $platform ) - { - return sprintf("to_tsvector('english', ?)", $sqlExp); - } + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return $value['data']; + } - public function convertToDatabaseValue($value, AbstractPlatform $platform) - { - return $value['data']; - } + /** + * Gets the name of this type. + * + * @return string + */ + public function getName() + { + return 'tsvector'; + } - /** - * Gets the name of this type. - * - * @return string - */ - public function getName() - { - return 'tsvector'; - } - - public function getMappedDatabaseTypes(AbstractPlatform $platform) - { - return ['tsvector']; - } + public function getMappedDatabaseTypes(AbstractPlatform $platform) + { + return ['tsvector']; + } } diff --git a/src/ORM/Mapping/TsVector.php b/src/ORM/Mapping/TsVector.php index d010376..de58d8f 100644 --- a/src/ORM/Mapping/TsVector.php +++ b/src/ORM/Mapping/TsVector.php @@ -12,29 +12,29 @@ use Doctrine\Common\Annotations\Annotation\Target; /** - * Class TsVector - * @package VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping + * Class TsVector. + * * @Annotation * @Target("PROPERTY") */ final class TsVector extends Annotation { - /** - * @var array - * @Annotation\Required() - */ - public $fields = []; - /** - * @var string - */ - public $name; - /** - * @var string - * @Annotation\Enum({'A',"B","C","D"}) - */ - public $weight = 'D'; - /** - * @var string - */ - public $language = 'english'; + /** + * @var array + * @Annotation\Required() + */ + public $fields = []; + /** + * @var string + */ + public $name; + /** + * @var string + * @Annotation\Enum({'A',"B","C","D"}) + */ + public $weight = 'D'; + /** + * @var string + */ + public $language = 'english'; } diff --git a/src/ORM/Query/AST/Functions/TSFunction.php b/src/ORM/Query/AST/Functions/TSFunction.php index a2e2655..dee8e2b 100644 --- a/src/ORM/Query/AST/Functions/TSFunction.php +++ b/src/ORM/Query/AST/Functions/TSFunction.php @@ -18,48 +18,47 @@ use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; /** - * Class TSFunction - * @package VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions + * Class TSFunction. */ abstract class TSFunction extends FunctionNode { - /** - * @var PathExpression - */ - public $ftsField = null; - /** - * @var PathExpression - */ - public $queryString = null; + /** + * @var PathExpression + */ + public $ftsField = null; + /** + * @var PathExpression + */ + public $queryString = null; - public function parse(Parser $parser) - { - $parser->match(Lexer::T_IDENTIFIER); - $parser->match(Lexer::T_OPEN_PARENTHESIS); - $this->ftsField = $parser->StringPrimary(); - $parser->match(Lexer::T_COMMA); - $this->queryString = $parser->StringPrimary(); - $parser->match(Lexer::T_CLOSE_PARENTHESIS); - } + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + $this->ftsField = $parser->StringPrimary(); + $parser->match(Lexer::T_COMMA); + $this->queryString = $parser->StringPrimary(); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } - protected function findFTSField(SqlWalker $sqlWalker) - { - $reader = new AnnotationReader(); - $dqlAlias = $this->ftsField->identificationVariable; - $class = $sqlWalker->getQueryComponent($dqlAlias); - /** @var ClassMetadata $classMetaData */ - $classMetaData = $class['metadata']; - $classRefl = $classMetaData->getReflectionClass(); - foreach($classRefl->getProperties() as $prop) { - /** @var TsVector $annot */ - $annot = $reader->getPropertyAnnotation($prop, TsVector::class); - if (is_null($annot)) { - continue; - } - if (in_array($this->ftsField->field,$annot->fields)) { - $this->ftsField->field = $prop->name; - break; - } - } - } + protected function findFTSField(SqlWalker $sqlWalker) + { + $reader = new AnnotationReader(); + $dqlAlias = $this->ftsField->identificationVariable; + $class = $sqlWalker->getQueryComponent($dqlAlias); + /** @var ClassMetadata $classMetaData */ + $classMetaData = $class['metadata']; + $classRefl = $classMetaData->getReflectionClass(); + foreach ($classRefl->getProperties() as $prop) { + /** @var TsVector $annot */ + $annot = $reader->getPropertyAnnotation($prop, TsVector::class); + if (null === $annot) { + continue; + } + if (in_array($this->ftsField->field, $annot->fields)) { + $this->ftsField->field = $prop->name; + break; + } + } + } } diff --git a/src/ORM/Query/AST/Functions/TsPlainQueryFunction.php b/src/ORM/Query/AST/Functions/TsPlainQueryFunction.php new file mode 100644 index 0000000..10d10a5 --- /dev/null +++ b/src/ORM/Query/AST/Functions/TsPlainQueryFunction.php @@ -0,0 +1,18 @@ +findFTSField($sqlWalker); + + return $this->ftsField->dispatch($sqlWalker).' @@ plainto_tsquery('.$this->queryString->dispatch($sqlWalker).')'; + } +} diff --git a/src/ORM/Query/AST/Functions/TsQueryFunction.php b/src/ORM/Query/AST/Functions/TsQueryFunction.php index dadda10..f5eee8d 100644 --- a/src/ORM/Query/AST/Functions/TsQueryFunction.php +++ b/src/ORM/Query/AST/Functions/TsQueryFunction.php @@ -11,14 +11,14 @@ use Doctrine\ORM\Query\SqlWalker; /** - * Class TsQueryFunction - * @package VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions + * Class TsQueryFunction. */ class TsQueryFunction extends TSFunction { - public function getSql(SqlWalker $sqlWalker) - { - $this->findFTSField($sqlWalker); - return $this->ftsField->dispatch($sqlWalker).' @@ to_tsquery('.$this->queryString->dispatch($sqlWalker).')'; - } + public function getSql(SqlWalker $sqlWalker) + { + $this->findFTSField($sqlWalker); + + return $this->ftsField->dispatch($sqlWalker).' @@ to_tsquery('.$this->queryString->dispatch($sqlWalker).')'; + } } diff --git a/src/ORM/Query/AST/Functions/TsRankCDFunction.php b/src/ORM/Query/AST/Functions/TsRankCDFunction.php index ef46154..61eb8d3 100644 --- a/src/ORM/Query/AST/Functions/TsRankCDFunction.php +++ b/src/ORM/Query/AST/Functions/TsRankCDFunction.php @@ -11,14 +11,14 @@ use Doctrine\ORM\Query\SqlWalker; /** - * Class TsRankCDFunction - * @package VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions + * Class TsRankCDFunction. */ class TsRankCDFunction extends TSFunction { - public function getSql(SqlWalker $sqlWalker) - { - $this->findFTSField($sqlWalker); - return 'ts_rank_cd('.$this->ftsField->dispatch($sqlWalker).', to_tsquery('.$this->queryString->dispatch($sqlWalker).'))'; - } + public function getSql(SqlWalker $sqlWalker) + { + $this->findFTSField($sqlWalker); + + return 'ts_rank_cd('.$this->ftsField->dispatch($sqlWalker).', to_tsquery('.$this->queryString->dispatch($sqlWalker).'))'; + } } diff --git a/src/ORM/Query/AST/Functions/TsRankFunction.php b/src/ORM/Query/AST/Functions/TsRankFunction.php index b3bd271..917ec2e 100644 --- a/src/ORM/Query/AST/Functions/TsRankFunction.php +++ b/src/ORM/Query/AST/Functions/TsRankFunction.php @@ -11,14 +11,14 @@ use Doctrine\ORM\Query\SqlWalker; /** - * Class TsRankFunction - * @package VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions + * Class TsRankFunction. */ class TsRankFunction extends TSFunction { - public function getSql(SqlWalker $sqlWalker) - { - $this->findFTSField($sqlWalker); - return 'ts_rank('.$this->ftsField->dispatch($sqlWalker).', to_tsquery('.$this->queryString->dispatch($sqlWalker).'))'; - } + public function getSql(SqlWalker $sqlWalker) + { + $this->findFTSField($sqlWalker); + + return 'ts_rank('.$this->ftsField->dispatch($sqlWalker).', to_tsquery('.$this->queryString->dispatch($sqlWalker).'))'; + } } diff --git a/tests/VertigoLabs/Base/BaseORMTestCase.php b/tests/VertigoLabs/Base/BaseORMTestCase.php index 534ed5f..43f3ce6 100644 --- a/tests/VertigoLabs/Base/BaseORMTestCase.php +++ b/tests/VertigoLabs/Base/BaseORMTestCase.php @@ -15,6 +15,7 @@ use Doctrine\ORM\Tools\Setup; use VertigoLabs\DoctrineFullTextPostgres\Common\TsVectorSubscriber; use VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsQueryFunction; +use VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsPlainQueryFunction; use VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankCDFunction; use VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankFunction; @@ -45,6 +46,7 @@ public function setUpDatabase() __DIR__.'../TsVector/Fixture' ],$isDevMode); $doctrineConfig->addCustomStringFunction('tsquery', TsQueryFunction::class); + $doctrineConfig->addCustomStringFunction('tsplainquery', TsPlainQueryFunction::class); $doctrineConfig->addCustomStringFunction('tsrank', TsRankFunction::class); $doctrineConfig->addCustomStringFunction('tsrankcd', TsRankCDFunction::class); @@ -87,4 +89,4 @@ private function dropSchemas($classes) { $this->schemaTool->dropSchema( $classes ); } -} \ No newline at end of file +} diff --git a/tests/VertigoLabs/TsQuery/TsPlainQueryTest.php b/tests/VertigoLabs/TsQuery/TsPlainQueryTest.php new file mode 100644 index 0000000..f256989 --- /dev/null +++ b/tests/VertigoLabs/TsQuery/TsPlainQueryTest.php @@ -0,0 +1,59 @@ +setUpSchema([Article::class]); + + foreach ($this->articleProvider() as $articleData) { + $article = new Article(); + $article->setTitle($articleData[0]); + $article->setBody($articleData[1]); + $this->em->persist($article); + } + $this->em->flush(); + } + + /** + * @test + * @dataProvider searchDataProvider + */ + public function shouldReturnArticles($queryStr, $field, $numberFound, $assertRes) + { + $query = $this->em->createQuery('SELECT a FROM TsVector\\Fixture\\Article a WHERE tsplainquery(a.'.$field.',:searchQuery) = true'); + $query->setParameter('searchQuery',$queryStr); + $result = $query->getArrayResult(); + + $this->assertEquals($numberFound, count($result)); + } + + public function articleProvider() + { + return [ + ['Test Article One', 'This is a test article used for running unit tests. It contains a couple keywords such as Elephant, Dolphin, Kitten, and Baboon.'], + ['Baboons Invade Seaworld', 'In a crazy turn of events a pack a rabid red baboons invade Seaworld. Officials say that the Dolphins are being held hostage'], + ['Elephants learn to fly', 'Yesterday several witnesses claim to have seen a seen a number of purple elephants flying through the sky over the downtown area.'], + ['Giraffes Shorter Than Experts Though','A recent study has shown that giraffes are actually much shorter than researchers previously believed. "What we didn\'t realize was that they were actually always surrounded by other really short object" says one official.'], + ['Green Kittens not as cute as believed','A recent uncovering of an underground "cat mafia" has found new evidence that the media is being paid to over-exaggerate the cuteness of kittens who have been painted green.'], + ['Test Article Two', 'This is another test article used for running unit tests. This article has color based keywords such as; Blue, Green, Red, and Yellow.'] + ]; + } + + public function searchDataProvider() + { + return [ + ['dolphins','body', 2, true], + ['Dolphins','body', 2, true], + ['Dolphins','title', 0, false], + ['Dolphins seaworld','title', 1, false], + ['Dolphins seaworld','body', 2, false], + ]; + } +} diff --git a/tests/VertigoLabs/TsVector/Fixture/GetterEntity.php b/tests/VertigoLabs/TsVector/Fixture/GetterEntity.php new file mode 100644 index 0000000..cce3b04 --- /dev/null +++ b/tests/VertigoLabs/TsVector/Fixture/GetterEntity.php @@ -0,0 +1,39 @@ + + * @copyright: + * @date: 9/18/2015 + * @time: 3:12 PM + */ + +namespace TsVector\Fixture; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; +use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; + +/** + * Class MissingColumnEntity + * @package VertigoLabs\TsVector\Fixture + * @Entity() + */ +class GetterEntity +{ + /** + * @var integer + * @Id() + * @Column(name="id", type="integer", nullable=false) + */ + private $id; + + /** + * @TsVector(fields={"calculateColumn"}) + */ + private $missingColumnFTS; + + public function getCalculateColumn() + { + + } +} diff --git a/tests/VertigoLabs/TsVector/TsVectorTest.php b/tests/VertigoLabs/TsVector/TsVectorTest.php index 2b037d7..b350964 100644 --- a/tests/VertigoLabs/TsVector/TsVectorTest.php +++ b/tests/VertigoLabs/TsVector/TsVectorTest.php @@ -95,6 +95,14 @@ public function mustHaveCorrectColumnType() $metaData = $this->em->getClassMetadata(WrongColumnTypeEntity::class); } + /** + * @test + */ + public function mustHaveGetter() + { + $metaData = $this->em->getClassMetadata(GetterEntity::class); + } + /** * @test */ @@ -123,4 +131,4 @@ public function shouldInsertData() $this->em->flush(); } -} \ No newline at end of file +}