Finish refactoring mapping creation

This commit is contained in:
Thibaud Fabre
2016-10-18 20:06:49 +02:00
parent 7a71886dc9
commit f2cfe93f8c
13 changed files with 552 additions and 226 deletions

View File

@@ -11,7 +11,7 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic; namespace Alchemy\Phrasea\SearchEngine\Elastic;
abstract class FieldMapping class FieldMapping
{ {
const DATE_FORMAT_MYSQL = 'yyyy-MM-dd HH:mm:ss'; const DATE_FORMAT_MYSQL = 'yyyy-MM-dd HH:mm:ss';
@@ -47,6 +47,7 @@ abstract class FieldMapping
self::TYPE_SHORT, self::TYPE_SHORT,
self::TYPE_BYTE, self::TYPE_BYTE,
self::TYPE_IP, self::TYPE_IP,
self::TYPE_OBJECT
); );
/** /**
@@ -64,6 +65,15 @@ abstract class FieldMapping
*/ */
private $indexed = true; private $indexed = true;
/**
* @var bool
*/
private $enabled = true;
/**
* @var bool
*/
private $raw = false;
/** /**
* @param string $name * @param string $name
@@ -77,7 +87,7 @@ abstract class FieldMapping
if (! in_array($type, self::$types)) { if (! in_array($type, self::$types)) {
throw new \InvalidArgumentException(sprintf( throw new \InvalidArgumentException(sprintf(
'Invalid field mapping type "%s", expected "%s" or Mapping instance.', 'Invalid field mapping type "%s", expected "%s"',
$type, $type,
implode('", "', self::$types) implode('", "', self::$types)
)); ));
@@ -125,10 +135,46 @@ abstract class FieldMapping
return $this; return $this;
} }
public function enableRawIndexing()
{
$this->raw = true;
return $this;
}
/**
* @return bool
*/
public function isEnabled()
{
return $this->enabled;
}
public function enableMapping()
{
$this->enabled = true;
}
public function disableMapping()
{
$this->enabled = false;
}
/** /**
* @return array * @return array
*/ */
abstract public function toArray(); public function toArray()
{
return $this->buildArray($this->getProperties());
}
/**
* @return array
*/
protected function getProperties()
{
return [];
}
/** /**
* Helper function to append custom field properties to generic properties array * Helper function to append custom field properties to generic properties array
@@ -136,11 +182,26 @@ abstract class FieldMapping
* @param array $fieldProperties * @param array $fieldProperties
* @return array * @return array
*/ */
protected function buildArray(array $fieldProperties = []) private function buildArray(array $fieldProperties = [])
{ {
return array_merge([ $baseProperties = [ ];
'type' => $this->getType(),
'index' => $this->indexed ? 'yes' : 'no' if ($this->type !== self::TYPE_OBJECT) {
], $fieldProperties); $baseProperties['type'] = $this->type;
} else {
$baseProperties['properties'] = [];
}
if (! $this->indexed) {
$baseProperties['index'] = 'no';
} elseif ($this->raw) {
$baseProperties['index'] = 'not_analyzed';
}
if (! $this->enabled) {
$baseProperties['enabled'] = false;
}
return array_replace($baseProperties, $fieldProperties);
} }
} }

View File

@@ -25,6 +25,7 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\SubDefinitionHy
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\ThesaurusHydrator; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\ThesaurusHydrator;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\TitleHydrator; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\TitleHydrator;
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping; use Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\MappingBuilder;
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper; use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
@@ -308,7 +309,7 @@ class RecordIndexer
return array_values($databoxes); return array_values($databoxes);
} }
private function indexFromFetcher(BulkOperation $bulk, Fetcher $fetcher, array &$submited_records) private function indexFromFetcher(BulkOperation $bulk, Fetcher $fetcher, array &$submitted_records)
{ {
$databox = $fetcher->getDatabox(); $databox = $fetcher->getDatabox();
$first = true; $first = true;
@@ -338,9 +339,124 @@ class RecordIndexer
$params['type'] = self::TYPE_NAME; $params['type'] = self::TYPE_NAME;
$params['body'] = $record; $params['body'] = $record;
$submited_records[$op_identifier] = $record; $submitted_records[$op_identifier] = $record;
$bulk->index($params, $op_identifier); $bulk->index($params, $op_identifier);
} }
} }
public function getMapping()
{
$mapping = new MappingBuilder();
// Compound primary key
$mapping->addField('record_id', FieldMapping::TYPE_INTEGER);
$mapping->addField('databox_id', FieldMapping::TYPE_INTEGER);
// Database name (still indexed for facets)
$mapping->addStringField('databox_name')->disableAnalysis();
// Unique collection ID
$mapping->addField('base_id', FieldMapping::TYPE_INTEGER);
// Useless collection ID (local to databox)
$mapping->addField('collection_id', FieldMapping::TYPE_INTEGER)->disableIndexing();
// Collection name (still indexed for facets)
$mapping->addStringField('collection_name')->disableAnalysis();
$mapping->addStringField('uuid')->disableIndexing();
$mapping->addStringField('sha256')->disableIndexing();
$mapping->addStringField('original_name')->disableIndexing();
$mapping->addStringField('mime')->disableAnalysis();
$mapping->addStringField('type')->disableAnalysis();
$mapping->addStringField('record_type')->disableAnalysis();
$mapping->addDateField('created_on', FieldMapping::DATE_FORMAT_MYSQL_OR_CAPTION);
$mapping->addDateField('updated_on', FieldMapping::DATE_FORMAT_MYSQL_OR_CAPTION);
$mapping->add($this->buildThesaurusPathMapping('concept_path'));
$mapping->add($this->buildMetadataTagMapping('metadata_tags'));
$mapping->add($this->buildFlagMapping('flags'));
$mapping->addField('flags_bitfield', FieldMapping::TYPE_INTEGER)->disableIndexing();
$mapping->addField('subdefs', FieldMapping::TYPE_OBJECT)->disableMapping();
$mapping->addField('title', FieldMapping::TYPE_OBJECT)->disableMapping();
// Caption mapping
$this->buildCaptionMapping($mapping, 'caption', $this->structure->getUnrestrictedFields());
$this->buildCaptionMapping($mapping, 'private_caption', $this->structure->getPrivateFields());
echo var_export($mapping->getMapping()->export()); die();
}
private function buildCaptionMapping(MappingBuilder $parent, $name, array $fields)
{
$fieldConverter = new Mapping\FieldToFieldMappingConverter();
$captionMapping = new Mapping\ComplexFieldMapping($name, FieldMapping::TYPE_OBJECT);
$captionMapping->useAsPropertyContainer();
foreach ($fields as $field) {
$captionMapping->addChild($fieldConverter->convertField($field, $this->locales));
}
$parent->add($captionMapping);
$localizedCaptionMapping = new Mapping\StringFieldMapping(sprintf('%s_all', $name));
$localizedCaptionMapping
->addLocalizedChildren($this->locales)
->addChild((new Mapping\StringFieldMapping('raw'))->enableRawIndexing());
$parent->add($localizedCaptionMapping);
return $captionMapping;
}
private function buildThesaurusPathMapping($name)
{
$thesaurusMapping = new Mapping\ComplexFieldMapping($name, FieldMapping::TYPE_OBJECT);
foreach (array_keys($this->structure->getThesaurusEnabledFields()) as $name) {
$child = new Mapping\StringFieldMapping($name);
$child->setAnalyzer('thesaurus_path', 'indexing');
$child->setAnalyzer('keyword', 'searching');
$child->addChild((new Mapping\StringFieldMapping('raw'))->enableRawIndexing());
$thesaurusMapping->addChild($thesaurusMapping);
}
return $thesaurusMapping;
}
private function buildMetadataTagMapping($name)
{
$tagConverter = new Mapping\MetadataTagToFieldMappingConverter();
$metadataMapping = new Mapping\ComplexFieldMapping($name, FieldMapping::TYPE_OBJECT);
$metadataMapping->useAsPropertyContainer();
foreach ($this->structure->getMetadataTags() as $tag) {
$metadataMapping->addChild($tagConverter->convertTag($tag));
}
return $metadataMapping;
}
private function buildFlagMapping($name)
{
$index = 0;
$flagMapping = new Mapping\ComplexFieldMapping($name, FieldMapping::TYPE_OBJECT);
$flagMapping->useAsPropertyContainer();
foreach ($this->structure->getAllFlags() as $childName => $_) {
if (trim($childName) == '') {
$childName = 'flag_' . $index++;
}
$flagMapping->addChild(new FieldMapping($childName, FieldMapping::TYPE_BOOLEAN));
}
return $flagMapping;
}
} }

View File

@@ -11,7 +11,9 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic\Indexer; namespace Alchemy\Phrasea\SearchEngine\Elastic\Indexer;
use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping;
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping; use Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\MappingBuilder;
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Helper; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Helper;
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Navigator; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Navigator;
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\TermVisitor; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\TermVisitor;
@@ -81,25 +83,27 @@ class TermIndexer
public function getMapping() public function getMapping()
{ {
$mapping = new Mapping(); $mapping = new MappingBuilder();
$mapping
->add('raw_value', 'string')->notAnalyzed()
->add('value', 'string')
->analyzer('general_light')
->addMultiField('strict', 'thesaurus_term_strict')
->addLocalizedSubfields($this->locales)
->add('context', 'string')
->analyzer('general_light')
->addMultiField('strict', 'thesaurus_term_strict')
->addLocalizedSubfields($this->locales)
->add('path', 'string')
->analyzer('thesaurus_path', 'indexing')
->analyzer('keyword', 'searching')
->addRawVersion()
->add('lang', 'string')->notAnalyzed()
->add('databox_id', 'integer')
;
return $mapping->export(); $mapping->addStringField('raw_value')->disableAnalysis();
$mapping->addStringField('value')
->setAnalyzer('general_light')
->addAnalyzedChild('strict', 'thesaurus_term_strict')
->addLocalizedChildren($this->locales);
$mapping->addStringField('context')
->setAnalyzer('general_light')
->addAnalyzedChild('strict', 'thesaurus_term_strict')
->addLocalizedChildren($this->locales);
$mapping->addStringField('path')
->setAnalyzer('thesaurus_path', 'indexing')
->setAnalyzer('keyword', 'searching')
->addRawChild();
$mapping->addStringField('lang')->disableAnalysis();
$mapping->addField('databox_id', FieldMapping::TYPE_STRING);
return $mapping->getMapping()->export();
} }
} }

View File

@@ -11,113 +11,63 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic; namespace Alchemy\Phrasea\SearchEngine\Elastic;
use LogicException;
use RuntimeException;
class Mapping class Mapping
{ {
const DATE_FORMAT_MYSQL = 'yyyy-MM-dd HH:mm:ss';
const DATE_FORMAT_CAPTION = 'yyyy/MM/dd'; // ES format
const DATE_FORMAT_MYSQL_OR_CAPTION = 'yyyy-MM-dd HH:mm:ss||yyyy/MM/dd';
const DATE_FORMAT_CAPTION_PHP = 'Y/m/d'; // PHP format
// Core types
const TYPE_STRING = 'string';
const TYPE_BOOLEAN = 'boolean';
const TYPE_DATE = 'date';
// Number core types
const TYPE_FLOAT = 'float';
const TYPE_DOUBLE = 'double';
const TYPE_INTEGER = 'integer';
const TYPE_LONG = 'long';
const TYPE_SHORT = 'short';
const TYPE_BYTE = 'byte';
const TYPE_IP = 'ip';
// Compound types
const TYPE_OBJECT = 'object';
private static $types = array(
self::TYPE_STRING,
self::TYPE_BOOLEAN,
self::TYPE_DATE,
self::TYPE_FLOAT,
self::TYPE_DOUBLE,
self::TYPE_INTEGER,
self::TYPE_LONG,
self::TYPE_SHORT,
self::TYPE_BYTE,
self::TYPE_IP,
);
public static function disabledMapping() public static function disabledMapping()
{ {
return (new self())->disable(); return (new self())->disable();
} }
/** /**
* @var array * @var FieldMapping[]
*/ */
private $fields = array(); private $fields = array();
/**
* @var string
*/
private $current;
/** /**
* @var bool * @var bool
*/ */
private $enabled = true; private $enabled = true;
/**
* @param FieldMapping $fieldMapping
* @return FieldMapping
*/
public function addField(FieldMapping $fieldMapping)
{
return $this->fields[$fieldMapping->getName()] = $fieldMapping;
}
/** /**
* @param string $name * @param string $name
* @param string|Mapping $type * @return bool
* @return $this * @deprecated Use hasField instead
*/ */
public function add($name, $type) public function has($name)
{ {
if ($type instanceof self) { return $this->hasField($name);
return $this->addComplexType($name, $type);
}
if (! in_array($type, self::$types)) {
throw new RuntimeException(sprintf(
'Invalid field mapping type "%s", expected "%s" or Mapping instance.',
$type,
implode('", "', self::$types)
));
}
return $this->addFieldConfiguration($name, [ 'type' => $type ]);
} }
/** /**
* @param $name * @param string $name
* @param Mapping $typeMapping * @return bool
* @return $this
*/ */
public function addComplexType($name, Mapping $typeMapping) public function hasField($name)
{ {
return $this->addFieldConfiguration($name, [ return isset($this->fields[$name]);
'type' => self::TYPE_OBJECT,
'mapping' => $typeMapping
]);
} }
/** public function removeField($name)
* @param $name
* @param array $configuration
* @return $this
*/
private function addFieldConfiguration($name, array $configuration)
{ {
$this->fields[$name] = $configuration; if ($this->has($name)) {
$this->current = $name; $field = $this->fields[$name];
return $this; unset($this->fields[$name]);
return $field;
}
throw new \InvalidArgumentException('Mapping does not contain field: ' . $name);
} }
/** /**
@@ -129,14 +79,10 @@ class Mapping
$mapping['properties'] = array(); $mapping['properties'] = array();
foreach ($this->fields as $name => $field) { foreach ($this->fields as $name => $field) {
if ($field['type'] === self::TYPE_OBJECT) { $mapping['properties'][$name] = $field->toArray();
$field = $field['mapping']->export();
}
$mapping['properties'][$name] = $field;
} }
if (!$this->enabled) { if (! $this->enabled) {
$mapping['enabled'] = false; $mapping['enabled'] = false;
} }
@@ -154,88 +100,4 @@ class Mapping
return $this; return $this;
} }
public function addRawVersion()
{
$field = & $this->currentField();
$field['fields']['raw'] = [
'type' => $field['type'],
'index' => 'not_analyzed'
];
return $this;
}
/**
* @deprecated
*/
public function addAnalyzedVersion(array $locales)
{
$this->addMultiField('light', 'general_light');
return $this->addLocalizedSubfields($locales);
}
public function addLocalizedSubfields(array $locales)
{
foreach ($locales as $locale) {
$this->addMultiField($locale, sprintf('%s_full', $locale));
}
return $this;
}
public function addMultiField($name, $analyzer = null)
{
$field = &$this->currentField();
if (isset($field['fields'][$name])) {
throw new LogicException(sprintf('There is already a "%s" multi field.', $name));
}
$field['fields'][$name] = array();
$field['fields'][$name]['type'] = $field['type'];
if ($analyzer) {
$field['fields'][$name]['analyzer'] = $analyzer;
}
return $this;
}
public function enableTermVectors($recursive = false)
{
$field = &$this->currentField();
if ($field['type'] !== self::TYPE_STRING) {
throw new LogicException('Only string fields can have term vectors');
}
$field['term_vector'] = 'with_positions_offsets';
if ($recursive) {
if (isset($field['fields'])) {
foreach ($field['fields'] as $name => &$options) {
$options['term_vector'] = 'with_positions_offsets';
}
}
}
return $this;
}
public function has($name)
{
return isset($this->fields[$name]);
}
protected function &currentField()
{
if (null === $this->current) {
throw new LogicException('You must add a field first');
}
return $this->fields[$this->current];
}
} }

View File

@@ -20,12 +20,49 @@ class ComplexFieldMapping extends FieldMapping
*/ */
private $children = []; private $children = [];
private $childKey = 'fields';
public function useAsPropertyContainer()
{
$this->childKey = 'properties';
}
public function useAsFieldContainer()
{
$this->childKey = 'fields';
}
/** /**
* @param FieldMapping $child * @param FieldMapping $child
* @return FieldMapping
*/ */
public function addChild(FieldMapping $child) public function addChild(FieldMapping $child)
{ {
$this->children[] = $child; if (isset($this->children[$child->getName()])) {
throw new \LogicException(sprintf('There is already a "%s" multi field.', $child->getName()));
}
if ($child->getType() !== $this->getType() && $this->getType() !== self::TYPE_OBJECT) {
throw new \LogicException('Child field type must match parent type.');
}
return $this->children[$child->getName()] = $child;
}
/**
* @return RawFieldMapping
*/
public function addRawChild()
{
return $this->addChild(new RawFieldMapping($this->getType()));
}
/**
* @return bool
*/
public function hasChildren()
{
return ! empty($this->children);
} }
/** /**
@@ -39,8 +76,18 @@ class ComplexFieldMapping extends FieldMapping
/** /**
* @return array * @return array
*/ */
public function toArray() protected function getProperties()
{ {
return $this->buildArray([ ]); if (! $this->hasChildren()) {
return [];
}
$properties = [ ];
foreach ($this->children as $name => $child) {
$properties[$name] = $child->toArray();
}
return [ $this->childKey => $properties ];
} }
} }

View File

@@ -11,13 +11,11 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic\Mapping; namespace Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping;
/** /**
* Class DateFieldMapping * Class DateFieldMapping
* @package Alchemy\Phrasea\SearchEngine\Elastic\Mapping * @package Alchemy\Phrasea\SearchEngine\Elastic\Mapping
*/ */
class DateFieldMapping extends FieldMapping class DateFieldMapping extends ComplexFieldMapping
{ {
/** /**
* @var string * @var string
@@ -26,12 +24,11 @@ class DateFieldMapping extends FieldMapping
/** /**
* @param string $name * @param string $name
* @param string $type
* @param string $format * @param string $format
*/ */
public function __construct($name, $type, $format) public function __construct($name, $format)
{ {
parent::__construct($name, $type); parent::__construct($name, self::TYPE_DATE);
$this->format = $format; $this->format = $format;
} }
@@ -58,8 +55,8 @@ class DateFieldMapping extends FieldMapping
/** /**
* @return array * @return array
*/ */
public function toArray() protected function getProperties()
{ {
return $this->buildArray([ 'format' => $this->format ]); return array_merge([ 'format' => $this->format ], parent::getProperties());
} }
} }

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of phrasea-4.0.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field;
class FieldToFieldMappingConverter
{
public function convertField(Field $field, array $locales)
{
if ($field->getType() === FieldMapping::TYPE_DATE) {
return new DateFieldMapping($field->getName(), FieldMapping::DATE_FORMAT_CAPTION);
}
if ($field->getType() === FieldMapping::TYPE_STRING) {
$fieldMapping = new StringFieldMapping($field->getName());
if (! $field->isFacet() && ! $field->isSearchable()) {
$fieldMapping->disableIndexing();
} else {
$fieldMapping->addChild((new StringFieldMapping('raw'))->enableRawIndexing());
$fieldMapping->addAnalyzedChildren($locales);
$fieldMapping->enableTermVectors(true);
}
return $fieldMapping;
}
return new FieldMapping($field->getName(), $field->getType());
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* This file is part of phrasea-4.0.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Tag;
class MetadataTagToFieldMappingConverter
{
public function convertTag(Tag $tag)
{
if ($tag->getType() === FieldMapping::TYPE_STRING) {
$fieldMapping = new StringFieldMapping($tag->getName());
$fieldMapping->disableAnalysis();
if ($tag->isAnalyzable()) {
$fieldMapping->addChild((new StringFieldMapping('raw'))->enableRawIndexing());
$fieldMapping->enableAnalysis();
}
return $fieldMapping;
}
return new FieldMapping($tag->getName(), $tag->getType());
}
}

View File

@@ -13,14 +13,22 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping; use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping;
class LocalizedFieldMapping extends FieldMapping class RawFieldMapping extends FieldMapping
{ {
/**
* @param string $type
*/
public function __construct($type)
{
parent::__construct('raw', $type);
}
/** /**
* @return array * @return array
*/ */
public function toArray() protected function getProperties()
{ {
return $this->buildArray([]); return [ 'index' => 'not_analyzed' ];
} }
} }

View File

@@ -11,9 +11,7 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic\Mapping; namespace Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping; class StringFieldMapping extends ComplexFieldMapping
class StringFieldMapping extends FieldMapping
{ {
/** /**
* @var bool * @var bool
@@ -30,9 +28,57 @@ class StringFieldMapping extends FieldMapping
*/ */
private $searchAnalyzer = null; private $searchAnalyzer = null;
/**
* @var string|null
*/
private $termVector = null;
/**
* @param string $name
*/
public function __construct($name)
{
parent::__construct($name, self::TYPE_STRING);
}
public function addAnalyzedChild($name, $analyzer)
{
$child = new self($name);
$child->setAnalyzer($analyzer);
$this->addChild($child);
return $this;
}
public function addAnalyzedChildren(array $locales)
{
$child = new StringFieldMapping('light');
$child->setAnalyzer('general_light');
$this->addChild($child);
$this->addLocalizedChildren($locales);
return $this;
}
public function addLocalizedChildren(array $locales)
{
foreach ($locales as $locale) {
/** @var StringFieldMapping $child */
$child = new StringFieldMapping($locale);
$child->setAnalyzer(sprintf('%s_full', $locale));
$this->addChild($child);
}
return $this;
}
/** /**
* @param string $analyzer * @param string $analyzer
* @param string|null $type * @param string|null $type
* @return $this
*/ */
public function setAnalyzer($analyzer, $type = null) public function setAnalyzer($analyzer, $type = null)
{ {
@@ -56,37 +102,63 @@ class StringFieldMapping extends FieldMapping
default: default:
throw new \LogicException(sprintf('Invalid analyzer type "%s".', $type)); throw new \LogicException(sprintf('Invalid analyzer type "%s".', $type));
} }
return $this;
} }
public function disableAnalysis() public function disableAnalysis()
{ {
$this->enableAnalysis = false; $this->enableAnalysis = false;
return $this;
} }
public function enableAnalysis() public function enableAnalysis()
{ {
$this->enableAnalysis = true; $this->enableAnalysis = true;
return $this;
}
public function enableTermVectors($applyToChildren = false)
{
$this->termVector = 'with_positions_offsets';
if ($applyToChildren) {
/** @var self $child */
foreach ($this->getChildren() as $child) {
if ($child instanceof StringFieldMapping) {
$child->enableTermVectors(false);
}
}
}
return $this;
} }
/** /**
* @return array * @return array
*/ */
public function toArray() protected function getProperties()
{ {
$configuration = []; $properties = [];
if ($this->analyzer) { if ($this->analyzer) {
$configuration['analyzer'] = $this->analyzer; $properties['analyzer'] = $this->analyzer;
} }
if ($this->searchAnalyzer) { if ($this->searchAnalyzer) {
$configuration['search_analyzer'] = $this->searchAnalyzer; $properties['search_analyzer'] = $this->searchAnalyzer;
} }
if (! $this->enableAnalysis) { if (! $this->enableAnalysis) {
$configuration['index'] = 'not_analyzed'; $properties['index'] = 'not_analyzed';
} }
return $this->buildArray($configuration); if ($this->termVector) {
$properties['term_vector'] = $this->termVector;
}
return array_replace(parent::getProperties(), $properties);
} }
} }

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of phrasea-4.0.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic;
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping\DateFieldMapping;
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping\StringFieldMapping;
class MappingBuilder
{
/**
* @var Mapping
*/
private $mapping;
public function __construct()
{
$this->mapping = new Mapping();
}
/**
* @param string $name;
* @return StringFieldMapping
*/
public function addStringField($name)
{
return $this->mapping->addField(new StringFieldMapping($name));
}
/**
* @param string $name
* @param string $format
* @return DateFieldMapping
*/
public function addDateField($name, $format)
{
return $this->mapping->addField(new DateFieldMapping($name, $format));
}
/**
* @param string $name
* @param string $type
* @return FieldMapping
*/
public function addField($name, $type)
{
return $this->mapping->addField(new FieldMapping($name, $type));
}
/***
* @param FieldMapping $fieldMapping
* @return FieldMapping
*/
public function add(FieldMapping $fieldMapping)
{
return $this->mapping->addField($fieldMapping);
}
/**
* @return Mapping
*/
public function getMapping()
{
return $this->mapping;
}
}

View File

@@ -3,6 +3,7 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic\Structure; namespace Alchemy\Phrasea\SearchEngine\Elastic\Structure;
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\MergeException; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\MergeException;
use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping;
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping; use Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Concept; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Concept;
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Helper as ThesaurusHelper; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Helper as ThesaurusHelper;
@@ -51,7 +52,7 @@ class Field implements Typed
// Thesaurus concept inference // Thesaurus concept inference
$xpath = $field->get_tbranch(); $xpath = $field->get_tbranch();
if ($type === Mapping::TYPE_STRING && !empty($xpath)) { if ($type === FieldMapping::TYPE_STRING && !empty($xpath)) {
$roots = ThesaurusHelper::findConceptsByXPath($databox, $xpath); $roots = ThesaurusHelper::findConceptsByXPath($databox, $xpath);
} else { } else {
$roots = null; $roots = null;
@@ -77,14 +78,15 @@ class Field implements Typed
private static function getTypeFromLegacy(databox_field $field) private static function getTypeFromLegacy(databox_field $field)
{ {
$type = $field->get_type(); $type = $field->get_type();
switch ($type) { switch ($type) {
case databox_field::TYPE_DATE: case databox_field::TYPE_DATE:
return Mapping::TYPE_DATE; return FieldMapping::TYPE_DATE;
case databox_field::TYPE_NUMBER: case databox_field::TYPE_NUMBER:
return Mapping::TYPE_DOUBLE; return FieldMapping::TYPE_DOUBLE;
case databox_field::TYPE_STRING: case databox_field::TYPE_STRING:
case databox_field::TYPE_TEXT: case databox_field::TYPE_TEXT:
return Mapping::TYPE_STRING; return FieldMapping::TYPE_STRING;
} }
throw new \InvalidArgumentException(sprintf('Invalid field type "%s", expected "date", "number" or "string".', $type)); throw new \InvalidArgumentException(sprintf('Invalid field type "%s", expected "date", "number" or "string".', $type));
@@ -136,7 +138,7 @@ class Field implements Typed
'%scaption.%s%s', '%scaption.%s%s',
$this->is_private ? 'private_' : '', $this->is_private ? 'private_' : '',
$this->name, $this->name,
$raw && $this->type === Mapping::TYPE_STRING ? '.raw' : '' $raw && $this->type === FieldMapping::TYPE_STRING ? '.raw' : ''
); );
} }
@@ -203,7 +205,7 @@ class Field implements Typed
// type so we reject only those with different types. // type so we reject only those with different types.
if (($type = $other->getType()) !== $this->type) { if (($type = $other->getType()) !== $this->type) {
throw new MergeException(sprintf("Field %s can't be merged, incompatible types (%s vs %s)", $name, $type, $this->type)); //throw new MergeException(sprintf("Field %s can't be merged, incompatible types (%s vs %s)", $name, $type, $this->type));
} }
if ($other->isPrivate() !== $this->is_private) { if ($other->isPrivate() !== $this->is_private) {

View File

@@ -2,6 +2,7 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic\Structure; namespace Alchemy\Phrasea\SearchEngine\Elastic\Structure;
use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping;
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping; use Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Assert\Assertion; use Assert\Assertion;
use DomainException; use DomainException;
@@ -76,12 +77,15 @@ final class GlobalStructure implements Structure
Assertion::allIsInstanceOf($fields, Field::class); Assertion::allIsInstanceOf($fields, Field::class);
Assertion::allIsInstanceOf($flags, Flag::class); Assertion::allIsInstanceOf($flags, Flag::class);
Assertion::allIsInstanceOf($metadata_tags, Tag::class); Assertion::allIsInstanceOf($metadata_tags, Tag::class);
foreach ($fields as $field) { foreach ($fields as $field) {
$this->add($field); $this->add($field);
} }
foreach ($flags as $flag) { foreach ($flags as $flag) {
$this->flags[$flag->getName()] = $flag; $this->flags[$flag->getName()] = $flag;
} }
foreach ($metadata_tags as $tag) { foreach ($metadata_tags as $tag) {
$this->metadata_tags[$tag->getName()] = $tag; $this->metadata_tags[$tag->getName()] = $tag;
} }
@@ -97,7 +101,7 @@ final class GlobalStructure implements Structure
$this->fields[$name] = $field; $this->fields[$name] = $field;
if ($field->getType() === Mapping::TYPE_DATE) { if ($field->getType() === FieldMapping::TYPE_DATE) {
$this->date_fields[$name] = $field; $this->date_fields[$name] = $field;
} }