├── .gitignore ├── .php_cs ├── README.md ├── composer.json ├── src ├── Common │ └── TsVectorSubscriber.php ├── DBAL │ └── Types │ │ └── TsVector.php └── ORM │ ├── Mapping │ └── TsVector.php │ └── Query │ └── AST │ └── Functions │ ├── TSFunction.php │ ├── TsPlainQueryFunction.php │ ├── TsQueryFunction.php │ ├── TsRankCDFunction.php │ ├── TsRankFunction.php │ └── TsWebsearchQueryFunction.php └── tests ├── VertigoLabs ├── Base │ └── BaseORMTestCase.php ├── TsQuery │ ├── TsPlainQueryTest.php │ └── TsQueryTest.php ├── TsRank │ └── TsRankTest.php └── TsVector │ ├── Fixture │ ├── Article.php │ ├── DefaultAnnotationsEntity.php │ ├── FullAnnotationsEntity.php │ ├── GetterEntity.php │ ├── MissingColumnEntity.php │ └── WrongColumnTypeEntity.php │ └── TsVectorTest.php ├── bootstrap.php └── phpunit.xml /.gitignore: -------------------------------------------------------------------------------- 1 | ### JetBrains template 2 | *.iml 3 | .idea/ 4 | ### Composer template 5 | composer.phar 6 | vendor/ 7 | composer.lock 8 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in(__DIR__.'/src') 5 | ; 6 | 7 | $rules = [ 8 | '@Symfony' => true, 9 | 'list_syntax' => ['syntax' => 'short'], 10 | 'array_syntax' => ['syntax' => 'short'], 11 | 'linebreak_after_opening_tag' => true, 12 | 'no_useless_else' => true, 13 | 'no_useless_return' => true, 14 | 'ordered_imports' => true, 15 | // Risky checks 16 | 'psr4' => true, 17 | 'ereg_to_preg' => true, 18 | 'is_null' => true, 19 | 'non_printable_character' => true, 20 | 'random_api_migration' => true, 21 | 'ternary_to_null_coalescing' => true, 22 | ]; 23 | 24 | return PhpCsFixer\Config::create() 25 | ->setRules($rules) 26 | ->setUsingCache(false) 27 | ->setFinder($finder) 28 | ->setRiskyAllowed(true) 29 | ; 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DoctrineFullTextPostrgres 2 | 3 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/4754c670-381a-46fe-a0d6-42b189f83ebd/big.png)](https://insight.sensiolabs.com/projects/4754c670-381a-46fe-a0d6-42b189f83ebd) 4 | 5 | A simple to use set of database types, and annotations to use postgresql's full text search engine with doctrine 6 | 7 | ## Installation 8 | * Register Doctrine Annotation: 9 | 10 | ```php 11 | \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace("VertigoLabs\\DoctrineFullTextPostgres\\ORM\\Mapping\\"); 12 | ``` 13 | * Register Doctrine Type: 14 | 15 | ```php 16 | Type::addType('tsvector',\VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVectorType::class); 17 | ``` 18 | * Register Doctrine Event Subscriber 19 | 20 | ```php 21 | $this->em->getEventManager()->addEventSubscriber(new \VertigoLabs\DoctrineFullTextPostgres\Common\TsVectorSubscriber()); 22 | ``` 23 | 24 | * Register Doctrine Functions 25 | ```php 26 | $doctrineConfig->addCustomStringFunction('tsquery', \VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsQueryFunction::class); 27 | $doctrineConfig->addCustomStringFunction('tsplainquery', \VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsPlainQueryFunction::class); 28 | $doctrineConfig->addCustomStringFunction('tsrank', \VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankFunction::class); 29 | $doctrineConfig->addCustomStringFunction('tsrankcd', \VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankCDFunction::class); 30 | ``` 31 | 32 | ## Symfony installation 33 | 34 | * Add to config 35 | 36 | ```yaml 37 | doctrine: 38 | dbal: 39 | types: 40 | tsvector: VertigoLabs\DoctrineFullTextPostgres\DBAL\Types\TsVector 41 | mapping_types: 42 | tsvector: tsvector 43 | orm: 44 | entity_managers: 45 | default: 46 | dql: 47 | string_functions: 48 | tsquery: VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsQueryFunction 49 | tsplainquery: VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsPlainQueryFunction 50 | tsrank: VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankFunction 51 | tsrankcd: VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankCDFunction 52 | 53 | services: 54 | vertigolabs.doctrinefulltextpostgres.listener: 55 | class: VertigoLabs\DoctrineFullTextPostgres\Common\TsVectorSubscriber 56 | tags: 57 | - { name: doctrine.event_subscriber, connection: default } 58 | ``` 59 | 60 | ## Usage 61 | * Create your entity 62 | 63 | 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. 64 | A TsVector annotation only requires the ```fields``` parameter. There are optional ```weight``` and ```language``` parameters as well, however they are not used yet. 65 | 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 66 | 67 | ```php 68 | 69 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 70 | 71 | class Article 72 | { 73 | /** 74 | * @var string 75 | * @Column(name="title", type="string", nullable=false) 76 | */ 77 | private $title; 78 | 79 | /** 80 | * @var TsVector 81 | * @TsVector(name="title_fts", fields={"title"}) 82 | */ 83 | private $titleFTS; 84 | 85 | /** 86 | * @var string 87 | * @Column(name="body", type="text", nullable=true) 88 | */ 89 | private $body; 90 | 91 | /** 92 | * @var TsVector 93 | * @TsVector(name="body_fts", fields={"body"}) 94 | */ 95 | private $bodyFTS; 96 | } 97 | ``` 98 | 99 | * Insert some data 100 | 101 | 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. 102 | 103 | ```php 104 | $article = new Article(); 105 | $article->setTitle('Baboons Invade Seaworld'); 106 | $article->setBody('In a crazy turn of events a pack a rabid red baboons invade Seaworld. Officials say that the Dolphins are being held hostage'); 107 | $this->em->persist($article); 108 | $this->em->flush(); 109 | ``` 110 | 111 | * Query your database! 112 | 113 | 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 114 | 115 | ```php 116 | $query = $this->em->createQuery('SELECT a FROM Article a WHERE tsquery(a.title,:searchQuery) = true'); 117 | $query->setParameter('searchQuery','Baboons'); 118 | $result = $query->getArrayResult(); 119 | ``` 120 | 121 | If you'd like to retrieve the ranking of your full text search, simply use the tsrank function: 122 | 123 | ```php 124 | $query = $this->em->createQuery('SELECT a, tsrank(a.title,:searchQuery) as rank FROM Article a WHERE tsquery(a.title,:searchQuery) = true'); 125 | $query->setParameter('searchQuery','Baboons'); 126 | $result = $query->getArrayResult(); 127 | 128 | var_dump($result[0]['rank']); // int 0.67907 129 | ``` 130 | 131 | You can even order by rank: 132 | 133 | ```php 134 | $query = $this->em->createQuery('SELECT a FROM Article a WHERE tsquery(a.title,:searchQuery) = true ORDER BY tsrank(a.title,:searchQuery) DESC'); 135 | ``` 136 | 137 | ## TODO 138 | * Add language to SQL field definition 139 | * Add language and weighting to queries 140 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vertigolabs/doctrine-full-text-postgres", 3 | "description": "Tools to use full-text searching in Postgresql with Doctrine", 4 | "authors": [ 5 | { 6 | "name": "James Murray", 7 | "email": "jaimz@vertigolabs.org" 8 | } 9 | ], 10 | "license": "MIT", 11 | "type": "library", 12 | "require": { 13 | "doctrine/orm": "~2.4", 14 | "php": ">=5.4" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": "~4.4", 18 | "phpunit/phpunit-mock-objects": "~2.3" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "VertigoLabs\\DoctrineFullTextPostgres\\": "src/" 23 | } 24 | }, 25 | "minimum-stability": "dev" 26 | } 27 | -------------------------------------------------------------------------------- /src/Common/TsVectorSubscriber.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/15/2015 6 | * @time: 5:18 PM 7 | */ 8 | 9 | namespace VertigoLabs\DoctrineFullTextPostgres\Common; 10 | 11 | use Doctrine\Common\Annotations\AnnotationException; 12 | use Doctrine\Common\Annotations\AnnotationReader; 13 | use Doctrine\Common\Annotations\AnnotationRegistry; 14 | use Doctrine\Common\EventSubscriber; 15 | use Doctrine\DBAL\Types\Type; 16 | use Doctrine\ORM\Event\LoadClassMetadataEventArgs; 17 | use Doctrine\ORM\Event\PreFlushEventArgs; 18 | use Doctrine\ORM\Event\PreUpdateEventArgs; 19 | use Doctrine\ORM\Events; 20 | use Doctrine\ORM\Mapping\ClassMetadata; 21 | use Doctrine\ORM\Mapping\Column; 22 | use Doctrine\ORM\Mapping\MappingException; 23 | use VertigoLabs\DoctrineFullTextPostgres\DBAL\Types\TsVector as TsVectorType; 24 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 25 | 26 | /** 27 | * Class TsVectorSubscriber. 28 | */ 29 | class TsVectorSubscriber implements EventSubscriber 30 | { 31 | const ANNOTATION_NS = 'VertigoLabs\\DoctrineFullTextPostgres\\ORM\\Mapping\\'; 32 | const ANNOTATION_TSVECTOR = 'TsVector'; 33 | 34 | private static $supportedTypes = [ 35 | 'string', 36 | 'text', 37 | 'array', 38 | 'simple_array', 39 | 'json', 40 | 'json_array', 41 | ]; 42 | 43 | /** 44 | * @var AnnotationReader 45 | */ 46 | private $reader; 47 | 48 | public function __construct() 49 | { 50 | AnnotationRegistry::registerAutoloadNamespace(self::ANNOTATION_NS); 51 | $this->reader = new AnnotationReader(); 52 | 53 | if (!Type::hasType(strtolower(self::ANNOTATION_TSVECTOR))) { 54 | Type::addType(strtolower(self::ANNOTATION_TSVECTOR), TsVectorType::class); 55 | } 56 | } 57 | 58 | /** 59 | * Returns an array of events this subscriber wants to listen to. 60 | * 61 | * @return array 62 | */ 63 | public function getSubscribedEvents() 64 | { 65 | return [ 66 | Events::loadClassMetadata, 67 | Events::preFlush, 68 | Events::preUpdate, 69 | ]; 70 | } 71 | 72 | public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) 73 | { 74 | /** @var ClassMetadata $metaData */ 75 | $metaData = $eventArgs->getClassMetadata(); 76 | 77 | $class = $metaData->getReflectionClass(); 78 | foreach ($class->getProperties() as $prop) { 79 | /** @var TsVector $annotation */ 80 | $annotation = $this->reader->getPropertyAnnotation($prop, self::ANNOTATION_NS.self::ANNOTATION_TSVECTOR); 81 | if (null === $annotation) { 82 | continue; 83 | } 84 | $this->checkWatchFields($class, $prop, $annotation); 85 | $metaData->mapField([ 86 | 'fieldName' => $prop->getName(), 87 | 'columnName' => $this->getColumnName($prop, $annotation), 88 | 'type' => 'tsvector', 89 | 'weight' => strtoupper($annotation->weight), 90 | 'language' => strtolower($annotation->language), 91 | 'nullable' => $this->isWatchFieldNullable($class, $annotation) 92 | ]); 93 | } 94 | } 95 | 96 | public function preFlush(PreFlushEventArgs $eventArgs) 97 | { 98 | $uow = $eventArgs->getEntityManager()->getUnitOfWork(); 99 | $insertions = $uow->getScheduledEntityInsertions(); 100 | $this->setTsVector($insertions); 101 | } 102 | 103 | public function preUpdate(PreUpdateEventArgs $eventArgs) 104 | { 105 | $uow = $eventArgs->getEntityManager()->getUnitOfWork(); 106 | $updates = $uow->getScheduledEntityUpdates(); 107 | $this->setTsVector($updates); 108 | } 109 | 110 | private function setTsVector($entities) 111 | { 112 | foreach ($entities as $entity) { 113 | $refl = new \ReflectionObject($entity); 114 | foreach ($refl->getProperties() as $prop) { 115 | /** @var TsVector $annot */ 116 | $annot = $this->reader->getPropertyAnnotation($prop, TsVector::class); 117 | if (null === $annot) { 118 | continue; 119 | } 120 | 121 | $fields = $annot->fields; 122 | $tsVectorVal = []; 123 | foreach ($fields as $field) { 124 | if ($refl->hasMethod($field)) { 125 | $method = $refl->getMethod($field); 126 | $method->setAccessible(true); 127 | $methodValue = $method->invoke($entity); 128 | if (is_array($methodValue)) { 129 | $methodValue = implode(' ', $methodValue); 130 | } 131 | $tsVectorVal[] = $methodValue; 132 | } 133 | if ($refl->hasProperty($field)) { 134 | $field = $refl->getProperty($field); 135 | $field->setAccessible(true); 136 | $fieldValue = $field->getValue($entity); 137 | if (is_array($fieldValue)) { 138 | $fieldValue = implode(' ', $fieldValue); 139 | } 140 | $tsVectorVal[] = $fieldValue; 141 | } 142 | } 143 | $prop->setAccessible(true); 144 | $value = [ 145 | 'data' => join(' ', $tsVectorVal), 146 | 'language' => $annot->language, 147 | 'weight' => $annot->weight, 148 | ]; 149 | $prop->setValue($entity, $value); 150 | } 151 | } 152 | } 153 | 154 | private function getColumnName(\ReflectionProperty $property, TsVector $annotation) 155 | { 156 | $name = $annotation->name; 157 | if (null === $name) { 158 | $name = $property->getName(); 159 | } 160 | 161 | return $name; 162 | } 163 | 164 | private function checkWatchFields(\ReflectionClass $class, \ReflectionProperty $targetProperty, TsVector $annotation) 165 | { 166 | foreach ($annotation->fields as $fieldName) { 167 | if ($class->hasMethod($fieldName)) { 168 | continue; 169 | } 170 | 171 | if (!$class->hasProperty($fieldName)) { 172 | throw new MappingException(sprintf('Class does not contain %s property or getter', $fieldName)); 173 | } 174 | 175 | $property = $class->getProperty($fieldName); 176 | /** @var Column $propAnnot */ 177 | $propAnnot = $this->reader->getPropertyAnnotation($property, Column::class); 178 | if (!in_array($propAnnot->type, self::$supportedTypes)) { 179 | throw new AnnotationException(sprintf( 180 | '%s::%s TsVector field can only be assigned to ( "%s" ) columns. %1$s::%s has the type %s', 181 | $class->getName(), 182 | $targetProperty->getName(), 183 | implode('" | "', self::$supportedTypes), 184 | $fieldName, 185 | $propAnnot->type 186 | )); 187 | } 188 | } 189 | } 190 | 191 | private function isWatchFieldNullable(\ReflectionClass $class, TsVector $annotation) 192 | { 193 | foreach ($annotation->fields as $fieldName) { 194 | if ($class->hasMethod($fieldName)) { 195 | continue; 196 | } 197 | 198 | $property = $class->getProperty($fieldName); 199 | /** @var Column $propAnnot */ 200 | $propAnnot = $this->reader->getPropertyAnnotation($property, Column::class); 201 | if (false === $propAnnot->nullable) { 202 | return false; 203 | } 204 | } 205 | 206 | return true; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/DBAL/Types/TsVector.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/15/2015 6 | * @time: 3:12 PM 7 | */ 8 | 9 | namespace VertigoLabs\DoctrineFullTextPostgres\DBAL\Types; 10 | 11 | use Doctrine\DBAL\Platforms\AbstractPlatform; 12 | use Doctrine\DBAL\Types\Type; 13 | 14 | /** 15 | * Class TsVector. 16 | * 17 | * @todo figure out how to get the weight into the converted sql code 18 | */ 19 | class TsVector extends Type 20 | { 21 | /** 22 | * Gets the SQL declaration snippet for a field of this type. 23 | * 24 | * @param array $fieldDeclaration the field declaration 25 | * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform the currently used database platform 26 | * 27 | * @return string 28 | */ 29 | public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) 30 | { 31 | return 'tsvector'; 32 | } 33 | 34 | public function canRequireSQLConversion() 35 | { 36 | return true; 37 | } 38 | 39 | /** 40 | * Converts a value from its database representation to its PHP representation 41 | * of this type. 42 | * 43 | * @param mixed $value the value to convert 44 | * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform the currently used database platform 45 | * 46 | * @return mixed the PHP representation of the value 47 | */ 48 | public function convertToPHPValue($value, AbstractPlatform $platform) 49 | { 50 | return $value; 51 | } 52 | 53 | /** 54 | * Converts a value from its PHP representation to its database representation 55 | * of this type. 56 | * 57 | * @param mixed $value the value to convert 58 | * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform the currently used database platform 59 | * 60 | * @return mixed the database representation of the value 61 | */ 62 | public function convertToDatabaseValueSQL($sqlExp, AbstractPlatform $platform) 63 | { 64 | return sprintf("to_tsvector('english', ?)", $sqlExp); 65 | } 66 | 67 | public function convertToDatabaseValue($value, AbstractPlatform $platform) 68 | { 69 | return $value['data']; 70 | } 71 | 72 | /** 73 | * Gets the name of this type. 74 | * 75 | * @return string 76 | */ 77 | public function getName() 78 | { 79 | return 'tsvector'; 80 | } 81 | 82 | public function getMappedDatabaseTypes(AbstractPlatform $platform) 83 | { 84 | return ['tsvector']; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/ORM/Mapping/TsVector.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/15/2015 6 | * @time: 3:20 PM 7 | */ 8 | 9 | namespace VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping; 10 | 11 | use Doctrine\Common\Annotations\Annotation; 12 | use Doctrine\Common\Annotations\Annotation\Target; 13 | 14 | /** 15 | * Class TsVector. 16 | * 17 | * @Annotation 18 | * @Target("PROPERTY") 19 | */ 20 | final class TsVector extends Annotation 21 | { 22 | /** 23 | * @var array 24 | * @Annotation\Required() 25 | */ 26 | public $fields = []; 27 | /** 28 | * @var string 29 | */ 30 | public $name; 31 | /** 32 | * @var string 33 | * @Annotation\Enum({"A","B","C","D"}) 34 | */ 35 | public $weight = 'D'; 36 | /** 37 | * @var string 38 | */ 39 | public $language = 'english'; 40 | } 41 | -------------------------------------------------------------------------------- /src/ORM/Query/AST/Functions/TSFunction.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/19/2015 6 | * @time: 7:35 PM 7 | */ 8 | 9 | namespace VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions; 10 | 11 | use Doctrine\Common\Annotations\AnnotationReader; 12 | use Doctrine\Common\Persistence\Mapping\ClassMetadata; 13 | use Doctrine\ORM\Query\AST\Functions\FunctionNode; 14 | use Doctrine\ORM\Query\AST\PathExpression; 15 | use Doctrine\ORM\Query\Lexer; 16 | use Doctrine\ORM\Query\Parser; 17 | use Doctrine\ORM\Query\SqlWalker; 18 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 19 | 20 | /** 21 | * Class TSFunction. 22 | */ 23 | abstract class TSFunction extends FunctionNode 24 | { 25 | /** 26 | * @var PathExpression 27 | */ 28 | public $ftsField = null; 29 | /** 30 | * @var PathExpression 31 | */ 32 | public $queryString = null; 33 | 34 | public function parse(Parser $parser) 35 | { 36 | $parser->match(Lexer::T_IDENTIFIER); 37 | $parser->match(Lexer::T_OPEN_PARENTHESIS); 38 | $this->ftsField = $parser->StringPrimary(); 39 | $parser->match(Lexer::T_COMMA); 40 | $this->queryString = $parser->StringPrimary(); 41 | $parser->match(Lexer::T_CLOSE_PARENTHESIS); 42 | } 43 | 44 | protected function findFTSField(SqlWalker $sqlWalker) 45 | { 46 | $reader = new AnnotationReader(); 47 | $dqlAlias = $this->ftsField->identificationVariable; 48 | $class = $sqlWalker->getQueryComponent($dqlAlias); 49 | /** @var ClassMetadata $classMetaData */ 50 | $classMetaData = $class['metadata']; 51 | $classRefl = $classMetaData->getReflectionClass(); 52 | foreach ($classRefl->getProperties() as $prop) { 53 | /** @var TsVector $annot */ 54 | $annot = $reader->getPropertyAnnotation($prop, TsVector::class); 55 | if (null === $annot) { 56 | continue; 57 | } 58 | if (in_array($this->ftsField->field, $annot->fields)) { 59 | $this->ftsField->field = $prop->name; 60 | break; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ORM/Query/AST/Functions/TsPlainQueryFunction.php: -------------------------------------------------------------------------------- 1 | findFTSField($sqlWalker); 15 | 16 | return $this->ftsField->dispatch($sqlWalker).' @@ plainto_tsquery('.$this->queryString->dispatch($sqlWalker).')'; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ORM/Query/AST/Functions/TsQueryFunction.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/19/2015 6 | * @time: 10:18 AM 7 | */ 8 | 9 | namespace VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions; 10 | 11 | use Doctrine\ORM\Query\SqlWalker; 12 | 13 | /** 14 | * Class TsQueryFunction. 15 | */ 16 | class TsQueryFunction extends TSFunction 17 | { 18 | public function getSql(SqlWalker $sqlWalker) 19 | { 20 | $this->findFTSField($sqlWalker); 21 | 22 | return $this->ftsField->dispatch($sqlWalker).' @@ to_tsquery('.$this->queryString->dispatch($sqlWalker).')'; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ORM/Query/AST/Functions/TsRankCDFunction.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/19/2015 6 | * @time: 7:25 PM 7 | */ 8 | 9 | namespace VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions; 10 | 11 | use Doctrine\ORM\Query\SqlWalker; 12 | 13 | /** 14 | * Class TsRankCDFunction. 15 | */ 16 | class TsRankCDFunction extends TSFunction 17 | { 18 | public function getSql(SqlWalker $sqlWalker) 19 | { 20 | $this->findFTSField($sqlWalker); 21 | 22 | return 'ts_rank_cd('.$this->ftsField->dispatch($sqlWalker).', to_tsquery('.$this->queryString->dispatch($sqlWalker).'))'; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ORM/Query/AST/Functions/TsRankFunction.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/19/2015 6 | * @time: 7:25 PM 7 | */ 8 | 9 | namespace VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions; 10 | 11 | use Doctrine\ORM\Query\SqlWalker; 12 | 13 | /** 14 | * Class TsRankFunction. 15 | */ 16 | class TsRankFunction extends TSFunction 17 | { 18 | public function getSql(SqlWalker $sqlWalker) 19 | { 20 | $this->findFTSField($sqlWalker); 21 | 22 | return 'ts_rank('.$this->ftsField->dispatch($sqlWalker).', to_tsquery('.$this->queryString->dispatch($sqlWalker).'))'; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ORM/Query/AST/Functions/TsWebsearchQueryFunction.php: -------------------------------------------------------------------------------- 1 | findFTSField($sqlWalker); 15 | 16 | return $this->ftsField->dispatch($sqlWalker).' @@ websearch_to_tsquery('.$this->queryString->dispatch($sqlWalker).')'; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/VertigoLabs/Base/BaseORMTestCase.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/15/2015 6 | * @time: 5:12 PM 7 | */ 8 | 9 | namespace Base; 10 | 11 | use Doctrine\Common\Persistence\PersistentObject; 12 | use Doctrine\DBAL\Types\Type; 13 | use Doctrine\ORM\EntityManager; 14 | use Doctrine\ORM\Tools\SchemaTool; 15 | use Doctrine\ORM\Tools\Setup; 16 | use VertigoLabs\DoctrineFullTextPostgres\Common\TsVectorSubscriber; 17 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsQueryFunction; 18 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsPlainQueryFunction; 19 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankCDFunction; 20 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Query\AST\Functions\TsRankFunction; 21 | 22 | class BaseORMTestCase extends \PHPUnit_Framework_TestCase 23 | { 24 | /** 25 | * @var EntityManager 26 | */ 27 | protected $em; 28 | /** 29 | * @var SchemaTool 30 | */ 31 | protected $schemaTool; 32 | 33 | protected $createdSchemas = []; 34 | 35 | public function setUp() 36 | { 37 | $this->setUpDatabase(); 38 | $this->setUpListeners(); 39 | // $this->setUpSchema(); 40 | } 41 | 42 | public function setUpDatabase() 43 | { 44 | $isDevMode = true; 45 | $doctrineConfig = Setup::createAnnotationMetadataConfiguration([ 46 | __DIR__.'../TsVector/Fixture' 47 | ],$isDevMode); 48 | $doctrineConfig->addCustomStringFunction('tsquery', TsQueryFunction::class); 49 | $doctrineConfig->addCustomStringFunction('tsplainquery', TsPlainQueryFunction::class); 50 | $doctrineConfig->addCustomStringFunction('tsrank', TsRankFunction::class); 51 | $doctrineConfig->addCustomStringFunction('tsrankcd', TsRankCDFunction::class); 52 | 53 | $dbConfig = [ 54 | 'host'=>'localhost', 55 | 'user'=>'postgres', 56 | 'dbname' => 'ts_vector_test', 57 | 'driver' => 'pdo_pgsql' 58 | ]; 59 | 60 | $this->em = EntityManager::create($dbConfig,$doctrineConfig); 61 | $this->em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('tsvector','tsvector'); 62 | $this->schemaTool = new SchemaTool($this->em); 63 | PersistentObject::setObjectManager($this->em); 64 | } 65 | 66 | public function setUpSchema(array $classes) 67 | { 68 | foreach ($classes as $k=>$class) { 69 | $classes[$k] = $this->em->getClassMetadata( $class ); 70 | } 71 | $this->dropSchemas($classes); 72 | $this->createdSchemas = array_merge($this->createdSchemas, $classes); 73 | $this->schemaTool->createSchema($classes); 74 | } 75 | 76 | public function setUpListeners() 77 | { 78 | $this->em->getEventManager()->addEventSubscriber(new TsVectorSubscriber()); 79 | } 80 | 81 | public function tearDown() 82 | { 83 | if (count($this->createdSchemas) > 0) { 84 | $this->dropSchemas($this->createdSchemas); 85 | } 86 | } 87 | 88 | private function dropSchemas($classes) 89 | { 90 | $this->schemaTool->dropSchema( $classes ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/VertigoLabs/TsQuery/TsPlainQueryTest.php: -------------------------------------------------------------------------------- 1 | setUpSchema([Article::class]); 14 | 15 | foreach ($this->articleProvider() as $articleData) { 16 | $article = new Article(); 17 | $article->setTitle($articleData[0]); 18 | $article->setBody($articleData[1]); 19 | $this->em->persist($article); 20 | } 21 | $this->em->flush(); 22 | } 23 | 24 | /** 25 | * @test 26 | * @dataProvider searchDataProvider 27 | */ 28 | public function shouldReturnArticles($queryStr, $field, $numberFound, $assertRes) 29 | { 30 | $query = $this->em->createQuery('SELECT a FROM TsVector\\Fixture\\Article a WHERE tsplainquery(a.'.$field.',:searchQuery) = true'); 31 | $query->setParameter('searchQuery',$queryStr); 32 | $result = $query->getArrayResult(); 33 | 34 | $this->assertEquals($numberFound, count($result)); 35 | } 36 | 37 | public function articleProvider() 38 | { 39 | return [ 40 | ['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.'], 41 | ['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'], 42 | ['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.'], 43 | ['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.'], 44 | ['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.'], 45 | ['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.'] 46 | ]; 47 | } 48 | 49 | public function searchDataProvider() 50 | { 51 | return [ 52 | ['dolphins','body', 2, true], 53 | ['Dolphins','body', 2, true], 54 | ['Dolphins','title', 0, false], 55 | ['Dolphins seaworld','title', 1, false], 56 | ['Dolphins seaworld','body', 2, false], 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/VertigoLabs/TsQuery/TsQueryTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/19/2015 6 | * @time: 10:11 AM 7 | */ 8 | 9 | namespace VertigoLabs\TsQuery; 10 | 11 | use Base\BaseORMTestCase; 12 | use TsVector\Fixture\Article; 13 | 14 | class TsQueryTest extends BaseORMTestCase 15 | { 16 | public function setUp() 17 | { 18 | parent::setUp(); 19 | $this->setUpSchema([Article::class]); 20 | 21 | foreach ($this->articleProvider() as $articleData) { 22 | $article = new Article(); 23 | $article->setTitle($articleData[0]); 24 | $article->setBody($articleData[1]); 25 | $this->em->persist($article); 26 | } 27 | $this->em->flush(); 28 | } 29 | 30 | /** 31 | * @test 32 | * @dataProvider searchDataProvider 33 | */ 34 | public function shouldReturnArticles($queryStr, $field, $numberFound, $assertRes) 35 | { 36 | $query = $this->em->createQuery('SELECT a FROM TsVector\\Fixture\\Article a WHERE tsquery(a.'.$field.',:searchQuery) = true'); 37 | $query->setParameter('searchQuery',$queryStr); 38 | $result = $query->getArrayResult(); 39 | 40 | $this->assertEquals($numberFound, count($result)); 41 | } 42 | 43 | public function articleProvider() 44 | { 45 | return [ 46 | ['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.'], 47 | ['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'], 48 | ['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.'], 49 | ['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.'], 50 | ['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.'], 51 | ['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.'] 52 | ]; 53 | } 54 | 55 | public function searchDataProvider() 56 | { 57 | return [ 58 | ['dolphins','body', 2, true], 59 | ['Dolphins','body', 2, true], 60 | ['Dolphins','title', 0, false], 61 | ['Dolphins | seaworld','title', 1, false], 62 | ['Dolphins | seaworld','body', 2, false], 63 | ]; 64 | } 65 | } -------------------------------------------------------------------------------- /tests/VertigoLabs/TsRank/TsRankTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/19/2015 6 | * @time: 10:11 AM 7 | */ 8 | 9 | namespace VertigoLabs\TsQuery; 10 | 11 | use Base\BaseORMTestCase; 12 | use TsVector\Fixture\Article; 13 | 14 | class TsRankTest extends BaseORMTestCase 15 | { 16 | public function setUp() 17 | { 18 | parent::setUp(); 19 | $this->setUpSchema([Article::class]); 20 | 21 | foreach ($this->articleProvider() as $articleData) { 22 | $article = new Article(); 23 | $article->setTitle($articleData[0]); 24 | $article->setBody($articleData[1]); 25 | $this->em->persist($article); 26 | } 27 | $this->em->flush(); 28 | } 29 | 30 | /** 31 | * @test 32 | * @dataProvider searchDataProvider 33 | */ 34 | public function shouldReturnRank($queryStr, $field, $numberFound) 35 | { 36 | $query = $this->em->createQuery('SELECT a, tsrank(a.'.$field.',:searchQuery) as rank FROM TsVector\\Fixture\\Article a WHERE tsquery(a.'.$field.',:searchQuery) = true'); 37 | $query->setParameter('searchQuery',$queryStr); 38 | $results = $query->getArrayResult(); 39 | 40 | $this->assertEquals($numberFound, count($results)); 41 | foreach($results as $result) { 42 | $this->assertArrayHasKey( 'rank', $result ); 43 | } 44 | } 45 | 46 | /** 47 | * @test 48 | * @dataProvider phraseSearchDataProvider 49 | */ 50 | public function shouldReturnCoverDensityRank($queryStr, $field, $numberFound) 51 | { 52 | $query = $this->em->createQuery('SELECT a, tsrankcd(a.'.$field.',:searchQuery) as rank FROM TsVector\\Fixture\\Article a WHERE tsquery(a.'.$field.',:searchQuery) = true'); 53 | $query->setParameter('searchQuery',$queryStr); 54 | $results = $query->getArrayResult(); 55 | 56 | $this->assertEquals($numberFound, count($results)); 57 | foreach($results as $result) { 58 | $this->assertArrayHasKey( 'rank', $result ); 59 | } 60 | } 61 | 62 | public function articleProvider() 63 | { 64 | return [ 65 | ['Test Article One', 'This is a test article used for running unit tests. It contains a couple keywords such as Elephant, Dolphin, Kitten, Giraffe, and Baboon.'], 66 | ['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'], 67 | ['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.'], 68 | ['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 the giraffes were actually always surrounded by other really short object" says one official.'], 69 | ['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.'], 70 | ['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.'] 71 | ]; 72 | } 73 | 74 | public function searchDataProvider() 75 | { 76 | return [ 77 | ['dolphins | elephant','body', 3], 78 | ['Dolphins','body', 2], 79 | ['Dolphins','title', 0], 80 | ['Dolphins | seaworld','title', 1], 81 | ['Dolphins | seaworld','body', 2], 82 | ['Giraffe','body', 2], 83 | ]; 84 | } 85 | 86 | public function phraseSearchDataProvider() 87 | { 88 | return [ 89 | ['giraffes','body', 2], 90 | ['kittens','body', 2], 91 | ['cats','body', 1], 92 | ]; 93 | } 94 | } -------------------------------------------------------------------------------- /tests/VertigoLabs/TsVector/Fixture/Article.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/15/2015 6 | * @time: 5:34 PM 7 | */ 8 | 9 | namespace TsVector\Fixture; 10 | 11 | use Doctrine\ORM\Mapping\Column; 12 | use Doctrine\ORM\Mapping\Entity; 13 | use Doctrine\ORM\Mapping\GeneratedValue; 14 | use Doctrine\ORM\Mapping\Id; 15 | use Doctrine\ORM\Mapping\Table; 16 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 17 | 18 | /** 19 | * Class Article 20 | * @package TsVector\Fixture 21 | * @Table(name="articles") 22 | * @Entity() 23 | */ 24 | class Article 25 | { 26 | /** 27 | * @var integer 28 | * @Id() 29 | * @GeneratedValue(strategy="IDENTITY") 30 | * @Column(name="id", type="integer", nullable=false) 31 | */ 32 | private $id; 33 | 34 | /** 35 | * @var string 36 | * @Column(name="title", type="string", nullable=false) 37 | */ 38 | private $title; 39 | 40 | /** 41 | * @var \VertigoLabs\DoctrineFullTextPostgres\DBAL\Types\TsVector 42 | * @TsVector(name="title_fts", fields={"title"}, weight="A") 43 | */ 44 | private $titleFTS; 45 | /** 46 | * @var string 47 | * @Column(name="body", type="text", nullable=true) 48 | */ 49 | private $body; 50 | 51 | /** 52 | * @var \VertigoLabs\DoctrineFullTextPostgres\DBAL\Types\TsVector 53 | * @TsVector(name="body_fts", fields={"body"}) 54 | */ 55 | private $bodyFTS; 56 | 57 | /** 58 | * @return string 59 | */ 60 | public function getTitle() 61 | { 62 | return $this->title; 63 | } 64 | 65 | /** 66 | * @param string $title 67 | * 68 | * @return Article 69 | */ 70 | public function setTitle( $title ) 71 | { 72 | $this->title = $title; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * @return string 79 | */ 80 | public function getBody() 81 | { 82 | return $this->body; 83 | } 84 | 85 | /** 86 | * @param string $body 87 | * 88 | * @return Article 89 | */ 90 | public function setBody( $body ) 91 | { 92 | $this->body = $body; 93 | 94 | return $this; 95 | } 96 | } -------------------------------------------------------------------------------- /tests/VertigoLabs/TsVector/Fixture/DefaultAnnotationsEntity.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/16/2015 6 | * @time: 11:26 AM 7 | */ 8 | 9 | namespace TsVector\Fixture; 10 | 11 | use Doctrine\ORM\Mapping\Column; 12 | use Doctrine\ORM\Mapping\Entity; 13 | use Doctrine\ORM\Mapping\Id; 14 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 15 | 16 | /** 17 | * Class AllDefaults 18 | * @package VertigoLabs\TsVector\Fixture 19 | * @Entity() 20 | */ 21 | class DefaultAnnotationsEntity 22 | { 23 | /** 24 | * @var integer 25 | * @Id() 26 | * @Column(name="id", type="integer", nullable=false) 27 | */ 28 | private $id; 29 | /** 30 | * @var string 31 | * @Column(type="string", nullable=true) 32 | */ 33 | private $allDefaults; 34 | /** 35 | * @TsVector(fields={"allDefaults"}) 36 | */ 37 | private $allDefaultsFTS; 38 | } -------------------------------------------------------------------------------- /tests/VertigoLabs/TsVector/Fixture/FullAnnotationsEntity.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/16/2015 6 | * @time: 11:26 AM 7 | */ 8 | 9 | namespace TsVector\Fixture; 10 | 11 | use Doctrine\ORM\Mapping\Column; 12 | use Doctrine\ORM\Mapping\Entity; 13 | use Doctrine\ORM\Mapping\Id; 14 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 15 | 16 | /** 17 | * @package VertigoLabs\TsVector\Fixture 18 | * @Entity() 19 | */ 20 | class FullAnnotationsEntity 21 | { 22 | /** 23 | * @var integer 24 | * @Id() 25 | * @Column(name="id", type="integer", nullable=false) 26 | */ 27 | private $id; 28 | /** 29 | * @var string 30 | * @Column(type="string", nullable=true) 31 | */ 32 | private $allCustom; 33 | /** 34 | * @TsVector(fields={"allCustom"}, name="fts_custom", weight="A", language="french") 35 | */ 36 | private $allCustomFTS; 37 | } -------------------------------------------------------------------------------- /tests/VertigoLabs/TsVector/Fixture/GetterEntity.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/18/2015 6 | * @time: 3:12 PM 7 | */ 8 | 9 | namespace TsVector\Fixture; 10 | 11 | use Doctrine\ORM\Mapping\Column; 12 | use Doctrine\ORM\Mapping\Entity; 13 | use Doctrine\ORM\Mapping\Id; 14 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 15 | 16 | /** 17 | * Class MissingColumnEntity 18 | * @package VertigoLabs\TsVector\Fixture 19 | * @Entity() 20 | */ 21 | class GetterEntity 22 | { 23 | /** 24 | * @var integer 25 | * @Id() 26 | * @Column(name="id", type="integer", nullable=false) 27 | */ 28 | private $id; 29 | 30 | /** 31 | * @TsVector(fields={"calculateColumn"}) 32 | */ 33 | private $missingColumnFTS; 34 | 35 | public function getCalculateColumn() 36 | { 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/VertigoLabs/TsVector/Fixture/MissingColumnEntity.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/18/2015 6 | * @time: 3:12 PM 7 | */ 8 | 9 | namespace TsVector\Fixture; 10 | 11 | use Doctrine\ORM\Mapping\Column; 12 | use Doctrine\ORM\Mapping\Entity; 13 | use Doctrine\ORM\Mapping\Id; 14 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 15 | 16 | /** 17 | * Class MissingColumnEntity 18 | * @package VertigoLabs\TsVector\Fixture 19 | * @Entity() 20 | */ 21 | class MissingColumnEntity 22 | { 23 | /** 24 | * @var integer 25 | * @Id() 26 | * @Column(name="id", type="integer", nullable=false) 27 | */ 28 | private $id; 29 | /** 30 | * @TsVector(fields={"missingColumn"}) 31 | */ 32 | private $missingColumnFTS; 33 | } -------------------------------------------------------------------------------- /tests/VertigoLabs/TsVector/Fixture/WrongColumnTypeEntity.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/18/2015 6 | * @time: 3:12 PM 7 | */ 8 | 9 | namespace TsVector\Fixture; 10 | 11 | use Doctrine\ORM\Mapping\Column; 12 | use Doctrine\ORM\Mapping\Entity; 13 | use Doctrine\ORM\Mapping\Id; 14 | use VertigoLabs\DoctrineFullTextPostgres\ORM\Mapping\TsVector; 15 | 16 | /** 17 | * @package VertigoLabs\TsVector\Fixture 18 | * @Entity() 19 | */ 20 | class WrongColumnTypeEntity 21 | { 22 | /** 23 | * @var integer 24 | * @Id() 25 | * @Column(name="id", type="integer", nullable=false) 26 | */ 27 | private $id; 28 | /** 29 | * @Column(type="integer", nullable=true) 30 | */ 31 | private $wrongColumnType; 32 | /** 33 | * @TsVector(fields={"wrongColumnType"}) 34 | */ 35 | private $wrongColumnTypeFTS; 36 | } -------------------------------------------------------------------------------- /tests/VertigoLabs/TsVector/TsVectorTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/15/2015 6 | * @time: 5:15 PM 7 | */ 8 | 9 | namespace VertigoLabs\TsVector; 10 | 11 | use Doctrine\Common\Annotations\AnnotationReader; 12 | use Doctrine\Common\EventManager; 13 | use Base\BaseORMTestCase; 14 | use TsVector\Fixture\FullAnnotationsEntity; 15 | use VertigoLabs\DoctrineFullTextPostgres\Common\TsVectorSubscriber; 16 | use TsVector\Fixture\Article; 17 | use TsVector\Fixture\DefaultAnnotationsEntity; 18 | use TsVector\Fixture\MissingColumnEntity; 19 | use TsVector\Fixture\WrongColumnTypeEntity; 20 | 21 | class TsVectorTest extends BaseORMTestCase 22 | { 23 | public function setUp() 24 | { 25 | parent::setUp(); 26 | 27 | $evm = new EventManager(); 28 | $evm->addEventSubscriber(new TsVectorSubscriber()); 29 | // $this->getMock 30 | } 31 | 32 | /** 33 | */ 34 | public function shouldReceiveAnnotation() 35 | { 36 | $reader = new AnnotationReader(); 37 | $refObj = new \ReflectionClass(Article::class); 38 | 39 | $titleProp = $refObj->getProperty('title'); 40 | $bodyProp = $refObj->getProperty('body'); 41 | $titleAnnotation = $reader->getPropertyAnnotation($titleProp, 'Vertigolabs\\DoctrineFullTextPostgres\\ORM\\Mapping\\TsVector'); 42 | $bodyAnnotation = $reader->getPropertyAnnotation($bodyProp, 'Vertigolabs\\DoctrineFullTextPostgres\\ORM\\Mapping\\TsVector'); 43 | 44 | $this->assertNotNull($titleAnnotation,'TsVector annotation not found for title'); 45 | $this->assertNotNull($bodyAnnotation,'TsVector annotation not found for body'); 46 | } 47 | 48 | /** 49 | * @test 50 | */ 51 | public function shouldReceiveDefaults() 52 | { 53 | $metaData = $this->em->getClassMetadata(DefaultAnnotationsEntity::class); 54 | 55 | $allDefaultsMetadata = $metaData->getFieldMapping('allDefaultsFTS'); 56 | 57 | $this->assertEquals('allDefaultsFTS', $allDefaultsMetadata['fieldName']); 58 | $this->assertEquals('allDefaultsFTS', $allDefaultsMetadata['columnName']); 59 | $this->assertEquals('D', $allDefaultsMetadata['weight']); 60 | $this->assertEquals('english', $allDefaultsMetadata['language']); 61 | } 62 | 63 | /** 64 | * @test 65 | */ 66 | public function shouldReceiveCustom() 67 | { 68 | $metaData = $this->em->getClassMetadata(FullAnnotationsEntity::class); 69 | 70 | $allDefaultsMetadata = $metaData->getFieldMapping('allCustomFTS'); 71 | 72 | $this->assertEquals('allCustomFTS', $allDefaultsMetadata['fieldName']); 73 | $this->assertEquals('fts_custom', $allDefaultsMetadata['columnName']); 74 | $this->assertEquals('A', $allDefaultsMetadata['weight']); 75 | $this->assertEquals('french', $allDefaultsMetadata['language']); 76 | } 77 | 78 | /** 79 | * @test 80 | * @expectedException \Doctrine\ORM\Mapping\MappingException 81 | * @expectedExceptionMessage Class does not contain missingColumn property 82 | */ 83 | public function mustHaveColumn() 84 | { 85 | $metaData = $this->em->getClassMetadata(MissingColumnEntity::class); 86 | } 87 | 88 | /** 89 | * @test 90 | * @expectedException \Doctrine\Common\Annotations\AnnotationException 91 | * @expectedExceptionMessage TsVector\Fixture\WrongColumnTypeEntity::wrongColumnTypeFTS TsVector field can only be assigned to ( "string" | "text" | "array" | "simple_array" | "json" | "json_array" ) columns. TsVector\Fixture\WrongColumnTypeEntity::wrongColumnType has the type integer 92 | */ 93 | public function mustHaveCorrectColumnType() 94 | { 95 | $metaData = $this->em->getClassMetadata(WrongColumnTypeEntity::class); 96 | } 97 | 98 | /** 99 | * @test 100 | */ 101 | public function mustHaveGetter() 102 | { 103 | $metaData = $this->em->getClassMetadata(GetterEntity::class); 104 | } 105 | 106 | /** 107 | * @test 108 | */ 109 | public function shouldCreateSchema() 110 | { 111 | $classes = [ 112 | $this->em->getClassMetadata(Article::class) 113 | ]; 114 | $sql = $this->schemaTool->getCreateSchemaSql($classes); 115 | 116 | $this->assertRegExp('/title_fts tsvector|body_fts tsvector/',$sql[0]); 117 | } 118 | 119 | /** 120 | * @test 121 | */ 122 | public function shouldInsertData() 123 | { 124 | $this->setUpSchema([Article::class]); 125 | 126 | $article = new Article(); 127 | $article->setTitle('test one'); 128 | $article->setBody('This is test one'); 129 | 130 | $this->em->persist($article); 131 | $this->em->flush(); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright: 5 | * @date: 9/15/2015 6 | * @time: 4:32 PM 7 | */ 8 | define('TESTS_PATH',__DIR__); 9 | define('TESTS_TEMP_PATH',__DIR__.'/temp'); 10 | define('VENDOR_PATH',__DIR__.'../vendor'); 11 | 12 | if (!class_exists('PHPUnit_Framework_TestCase') || version_compare(PHPUnit_Runner_Version::id(), '3.5') < 0) { 13 | die('PHPUnit framework 3.5 or newer is required'); 14 | } 15 | 16 | if (!class_exists('PHPUnit_Framework_MockObject_MockBuilder')) { 17 | die('PHPUnit MockObject Plugin 1.0.8 or newer is required'); 18 | } 19 | 20 | /** @var \Composer\Autoload\ClassLoader $loader */ 21 | $loader = require __DIR__.'/../vendor/autoload.php'; 22 | 23 | $loader->add('VertigoLabs\\ORM\\Mapping\\Mock',__DIR__); 24 | $loader->add('Base',__DIR__.'/VertigoLabs'); 25 | 26 | #fixtures 27 | $loader->add('TsVector\\Fixture',__DIR__.'/VertigoLabs'); 28 | 29 | \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader([$loader,'loadClass']); 30 | \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace("VertigoLabs\\DoctrineFullTextPostgres\\ORM\\Mapping\\"); 31 | \Doctrine\DBAL\Types\Type::addType('tsvector',\VertigoLabs\DoctrineFullTextPostgres\DBAL\Types\TsVector::class); 32 | 33 | $reader = new \Doctrine\Common\Annotations\AnnotationReader(); 34 | $reader = new \Doctrine\Common\Annotations\CachedReader($reader,new \Doctrine\Common\Cache\ArrayCache()); 35 | $_ENV['annotation_reader'] = $reader; -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./VertigoLabs/TsVector 15 | 16 | 17 | ./VertigoLabs/TsQuery 18 | 19 | 20 | ./VertigoLabs/TsRank 21 | 22 | 23 | --------------------------------------------------------------------------------