From fc0e71e28e49d9cdc84fadfda40692bdfc415a6c Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Fri, 22 May 2026 11:40:40 +0300 Subject: Engine: Add buildIndexMappings() helper (single typeless mapping) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single 'properties' block, documentType as a keyword field inside it, no include_in_all anywhere (the field was removed in ES 6). Relationships emit as keyword fields with doc_values: false, matching the bundled engine's ES-5-mode behavior. Field-data multi-analyzer shape (raw, keywords, stems) preserved. 🤖 Generated by [robots](https://vyos.io) --- ...SElasticModernFulltextStorageEngineTestCase.php | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php') diff --git a/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php b/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php index ff4ade8..ab6f5c6 100644 --- a/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php +++ b/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php @@ -112,4 +112,58 @@ final class VyOSElasticModernFulltextStorageEngineTestCase $filter); } + public function testBuildIndexMappingsShape() { + $engine = id(new VyOSElasticModernFulltextStorageEngine()) + ->setVersion(7); + + $doc_types = array('TASK', 'DREV'); + $fields = array('title', 'body', 'comment'); + $relationships = array('authorPHID', 'projectPHID'); + $mappings = $engine->buildIndexMappings( + $doc_types, $fields, $relationships, 'text'); + + // Single typeless mapping with one 'properties' block. + $this->assertTrue(isset($mappings['properties'])); + $this->assertFalse(isset($mappings['TASK'])); + $this->assertFalse(isset($mappings['DREV'])); + + // Field properties exist with the multi-analyzer shape. + $this->assertTrue(isset($mappings['properties']['title'])); + $this->assertEqual('text', $mappings['properties']['title']['type']); + $this->assertTrue( + isset($mappings['properties']['title']['fields']['raw'])); + $this->assertTrue( + isset($mappings['properties']['title']['fields']['keywords'])); + $this->assertTrue( + isset($mappings['properties']['title']['fields']['stems'])); + + // Relationships emit as keyword fields with doc_values:false. + $this->assertEqual( + 'keyword', + $mappings['properties']['authorPHID']['type']); + $this->assertEqual( + false, + $mappings['properties']['authorPHID']['doc_values']); + $this->assertEqual( + 'date', + $mappings['properties']['authorPHID_ts']['type']); + + // No include_in_all anywhere. + $this->assertFalse( + isset($mappings['properties']['authorPHID']['include_in_all'])); + + // documentType is a keyword field inside properties. + $this->assertEqual( + 'keyword', + $mappings['properties']['documentType']['type']); + + // Standard date fields present. + $this->assertEqual( + 'date', + $mappings['properties']['dateCreated']['type']); + $this->assertEqual( + 'date', + $mappings['properties']['lastModified']['type']); + } + } -- cgit v1.2.3 From 9259fbf741f4a080285fbdd8d8586681558c0ab6 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Sat, 23 May 2026 13:25:30 +0300 Subject: E5 fixup: expand testBuildIndexMappingsShape coverage to body/comment/projectPHID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by [robots](https://vyos.io) --- ...SElasticModernFulltextStorageEngineTestCase.php | 64 +++++++++++++--------- 1 file changed, 39 insertions(+), 25 deletions(-) (limited to 'src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php') diff --git a/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php b/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php index ab6f5c6..979c419 100644 --- a/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php +++ b/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php @@ -113,8 +113,7 @@ final class VyOSElasticModernFulltextStorageEngineTestCase } public function testBuildIndexMappingsShape() { - $engine = id(new VyOSElasticModernFulltextStorageEngine()) - ->setVersion(7); + $engine = $this->newEngine()->setVersion(7); $doc_types = array('TASK', 'DREV'); $fields = array('title', 'body', 'comment'); @@ -127,30 +126,45 @@ final class VyOSElasticModernFulltextStorageEngineTestCase $this->assertFalse(isset($mappings['TASK'])); $this->assertFalse(isset($mappings['DREV'])); - // Field properties exist with the multi-analyzer shape. - $this->assertTrue(isset($mappings['properties']['title'])); - $this->assertEqual('text', $mappings['properties']['title']['type']); - $this->assertTrue( - isset($mappings['properties']['title']['fields']['raw'])); - $this->assertTrue( - isset($mappings['properties']['title']['fields']['keywords'])); - $this->assertTrue( - isset($mappings['properties']['title']['fields']['stems'])); - - // Relationships emit as keyword fields with doc_values:false. - $this->assertEqual( - 'keyword', - $mappings['properties']['authorPHID']['type']); - $this->assertEqual( - false, - $mappings['properties']['authorPHID']['doc_values']); - $this->assertEqual( - 'date', - $mappings['properties']['authorPHID_ts']['type']); + // All three text fields have the multi-analyzer shape. + foreach (array('title', 'body', 'comment') as $field) { + $this->assertTrue( + isset($mappings['properties'][$field]), + pht('Field "%s" missing from mappings.', $field)); + $this->assertEqual( + 'text', + $mappings['properties'][$field]['type'], + pht('Field "%s" should be type text.', $field)); + $this->assertTrue( + isset($mappings['properties'][$field]['fields']['raw']), + pht('Field "%s" missing raw sub-field.', $field)); + $this->assertTrue( + isset($mappings['properties'][$field]['fields']['keywords']), + pht('Field "%s" missing keywords sub-field.', $field)); + $this->assertTrue( + isset($mappings['properties'][$field]['fields']['stems']), + pht('Field "%s" missing stems sub-field.', $field)); + } - // No include_in_all anywhere. - $this->assertFalse( - isset($mappings['properties']['authorPHID']['include_in_all'])); + // Both relationships emit as keyword fields with doc_values:false. + foreach (array('authorPHID', 'projectPHID') as $rel) { + $this->assertEqual( + 'keyword', + $mappings['properties'][$rel]['type'], + pht('Relationship "%s" should be keyword type.', $rel)); + $this->assertEqual( + false, + $mappings['properties'][$rel]['doc_values'], + pht('Relationship "%s" should have doc_values:false.', $rel)); + $this->assertEqual( + 'date', + $mappings['properties'][$rel.'_ts']['type'], + pht('Relationship "%s" missing timestamp field.', $rel)); + // No include_in_all anywhere. + $this->assertFalse( + isset($mappings['properties'][$rel]['include_in_all']), + pht('Relationship "%s" should not have include_in_all.', $rel)); + } // documentType is a keyword field inside properties. $this->assertEqual( -- cgit v1.2.3 From de83584f8b782bde516d6b39f9f1bc4214585058 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Sat, 23 May 2026 13:31:46 +0300 Subject: E5 fixup: detect intra-key duplicates in buildIndexMappings(); add collision tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by [robots](https://vyos.io) --- ...SElasticModernFulltextStorageEngineTestCase.php | 56 ++++++++++++++++++++++ .../VyOSElasticModernFulltextStorageEngine.php | 21 ++++++-- 2 files changed, 73 insertions(+), 4 deletions(-) (limited to 'src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php') diff --git a/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php b/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php index 979c419..e3daab4 100644 --- a/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php +++ b/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php @@ -180,4 +180,60 @@ final class VyOSElasticModernFulltextStorageEngineTestCase $mappings['properties']['lastModified']['type']); } + public function testBuildIndexMappingsRejectsReservedFieldName() { + $engine = $this->newEngine()->setVersion(7); + $caught = null; + try { + $engine->buildIndexMappings( + array(), array('documentType'), array(), 'text'); + } catch (Exception $e) { + $caught = $e; + } + $this->assertTrue( + $caught !== null, + pht('Expected exception when field name collides with reserved key.')); + } + + public function testBuildIndexMappingsRejectsReservedRelationshipName() { + $engine = $this->newEngine()->setVersion(7); + $caught = null; + try { + $engine->buildIndexMappings( + array(), array(), array('lastModified'), 'text'); + } catch (Exception $e) { + $caught = $e; + } + $this->assertTrue( + $caught !== null, + pht('Expected exception when relationship name collides with reserved key.')); + } + + public function testBuildIndexMappingsRejectsFooTsClash() { + // A field named "foo_ts" would clash with the timestamp slot auto-generated + // for a relationship named "foo". + $engine = $this->newEngine()->setVersion(7); + $caught = null; + try { + $engine->buildIndexMappings( + array(), array('foo_ts'), array('foo'), 'text'); + } catch (Exception $e) { + $caught = $e; + } + $this->assertTrue( + $caught !== null, + pht('Expected exception for field/relationship timestamp-slot collision.')); + } + + public function testBuildIndexMappingsEmptyInputsYieldStandardFields() { + $engine = $this->newEngine()->setVersion(7); + $mappings = $engine->buildIndexMappings(array(), array(), array(), 'text'); + $this->assertTrue(isset($mappings['properties']['documentType'])); + $this->assertTrue(isset($mappings['properties']['dateCreated'])); + $this->assertTrue(isset($mappings['properties']['lastModified'])); + $this->assertEqual( + 'keyword', $mappings['properties']['documentType']['type']); + $this->assertEqual('date', $mappings['properties']['dateCreated']['type']); + $this->assertEqual('date', $mappings['properties']['lastModified']['type']); + } + } diff --git a/src/engine/VyOSElasticModernFulltextStorageEngine.php b/src/engine/VyOSElasticModernFulltextStorageEngine.php index f798e4b..fe88adc 100644 --- a/src/engine/VyOSElasticModernFulltextStorageEngine.php +++ b/src/engine/VyOSElasticModernFulltextStorageEngine.php @@ -55,10 +55,10 @@ abstract class VyOSElasticModernFulltextStorageEngine // Caller-supplied $fields or $relationships must not shadow them. static $reserved = array('documentType', 'dateCreated', 'lastModified'); - $all_caller_keys = array_merge( - $fields, - $relationships, - array_map(function($r) { return $r.'_ts'; }, $relationships)); + $rel_ts_keys = array_map(function($r) { return $r.'_ts'; }, $relationships); + $all_caller_keys = array_merge($fields, $relationships, $rel_ts_keys); + + // Check for caller-supplied names that shadow reserved fields. $collisions = array_intersect($all_caller_keys, $reserved); if ($collisions) { throw new Exception( @@ -68,6 +68,19 @@ abstract class VyOSElasticModernFulltextStorageEngine implode('", "', array_values($collisions)))); } + // Check for duplicates within caller-supplied keys themselves + // (e.g. a field named "foo_ts" that would clash with relationship "foo"'s + // implicit timestamp slot). + $counts = array_count_values($all_caller_keys); + $duplicates = array_keys(array_filter($counts, function($c) { return $c > 1; })); + if ($duplicates) { + throw new Exception( + pht( + 'buildIndexMappings(): caller-supplied key(s) "%s" appear more '. + 'than once (check for field/relationship/timestamp-slot collisions).', + implode('", "', $duplicates))); + } + $properties = array(); foreach ($fields as $field) { -- cgit v1.2.3