Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def index_def_named(name, rollover: nil)
current_sources: [SELF_RELATIONSHIP_NAME],
fields_by_path: {},
has_had_multiple_sources: false,
sourced_from_nested_paths_by_relationship: {}
sourced_from_nested_paths_by_qualified_relationship: {}
)

DatastoreCore::IndexDefinition.with(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ def index_def_named(name, rollover: nil)
current_sources: [SELF_RELATIONSHIP_NAME],
fields_by_path: {},
has_had_multiple_sources: false,
sourced_from_nested_paths_by_relationship: {}
sourced_from_nested_paths_by_qualified_relationship: {}
)

DatastoreCore::IndexDefinition.with(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,24 @@ module RuntimeMetadata
# Runtime metadata related to a datastore index definition.
#
# @private
class IndexDefinition < ::Data.define(:route_with, :rollover, :default_sort_fields, :current_sources, :fields_by_path, :has_had_multiple_sources, :sourced_from_nested_paths_by_relationship)
class IndexDefinition < ::Data.define(:route_with, :rollover, :default_sort_fields, :current_sources, :fields_by_path, :has_had_multiple_sources, :sourced_from_nested_paths_by_qualified_relationship)
ROUTE_WITH = "route_with"
ROLLOVER = "rollover"
DEFAULT_SORT_FIELDS = "default_sort_fields"
CURRENT_SOURCES = "current_sources"
FIELDS_BY_PATH = "fields_by_path"
HAS_HAD_MULTIPLE_SOURCES = "has_had_multiple_sources"
SOURCED_FROM_NESTED_PATHS_BY_RELATIONSHIP = "sourced_from_nested_paths_by_relationship"
SOURCED_FROM_NESTED_PATHS_BY_QUALIFIED_RELATIONSHIP = "sourced_from_nested_paths_by_qualified_relationship"

def initialize(route_with:, rollover:, default_sort_fields:, current_sources:, fields_by_path:, has_had_multiple_sources:, sourced_from_nested_paths_by_relationship:)
def initialize(route_with:, rollover:, default_sort_fields:, current_sources:, fields_by_path:, has_had_multiple_sources:, sourced_from_nested_paths_by_qualified_relationship:)
super(
route_with: route_with,
rollover: rollover,
default_sort_fields: default_sort_fields,
current_sources: current_sources.to_set,
fields_by_path: fields_by_path,
has_had_multiple_sources: has_had_multiple_sources,
sourced_from_nested_paths_by_relationship: sourced_from_nested_paths_by_relationship
sourced_from_nested_paths_by_qualified_relationship: sourced_from_nested_paths_by_qualified_relationship
)
end

Expand All @@ -46,7 +46,7 @@ def self.from_hash(hash)
current_sources: hash[CURRENT_SOURCES] || [],
fields_by_path: (hash[FIELDS_BY_PATH] || {}).transform_values { |h| IndexField.from_hash(h) },
has_had_multiple_sources: hash[HAS_HAD_MULTIPLE_SOURCES] || false,
sourced_from_nested_paths_by_relationship: (hash[SOURCED_FROM_NESTED_PATHS_BY_RELATIONSHIP] || {}).transform_values { |segments| segments.map { |h| SourcedFromNestedPathSegment.from_hash(h) } }
sourced_from_nested_paths_by_qualified_relationship: (hash[SOURCED_FROM_NESTED_PATHS_BY_QUALIFIED_RELATIONSHIP] || {}).transform_values { |segments| segments.map { |h| SourcedFromNestedPathSegment.from_hash(h) } }
)
end

Expand All @@ -59,7 +59,7 @@ def to_dumpable_hash
HAS_HAD_MULTIPLE_SOURCES => (true if has_had_multiple_sources),
ROLLOVER => rollover&.to_dumpable_hash,
ROUTE_WITH => route_with,
SOURCED_FROM_NESTED_PATHS_BY_RELATIONSHIP => sourced_from_nested_paths_by_relationship.transform_values { |segments| segments.map(&:to_dumpable_hash) }
SOURCED_FROM_NESTED_PATHS_BY_QUALIFIED_RELATIONSHIP => sourced_from_nested_paths_by_qualified_relationship.transform_values { |segments| segments.map(&:to_dumpable_hash) }
}
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module ElasticGraph
attr_reader current_sources: ::Set[::String]
attr_reader fields_by_path: ::Hash[::String, IndexField]
attr_reader has_had_multiple_sources: bool
attr_reader sourced_from_nested_paths_by_relationship: ::Hash[::String, ::Array[sourcedFromNestedPathSegment]]
attr_reader sourced_from_nested_paths_by_qualified_relationship: ::Hash[::String, ::Array[sourcedFromNestedPathSegment]]

def initialize: (
route_with: ::String,
Expand All @@ -17,7 +17,7 @@ module ElasticGraph
current_sources: ::Set[::String],
fields_by_path: ::Hash[::String, IndexField],
has_had_multiple_sources: bool,
sourced_from_nested_paths_by_relationship: ::Hash[::String, ::Array[sourcedFromNestedPathSegment]]
sourced_from_nested_paths_by_qualified_relationship: ::Hash[::String, ::Array[sourcedFromNestedPathSegment]]
) -> void

def with: (
Expand All @@ -27,7 +27,7 @@ module ElasticGraph
?current_sources: ::Enumerable[::String],
?fields_by_path: ::Hash[::String, IndexField],
?has_had_multiple_sources: bool,
?sourced_from_nested_paths_by_relationship: ::Hash[::String, ::Array[sourcedFromNestedPathSegment]]
?sourced_from_nested_paths_by_qualified_relationship: ::Hash[::String, ::Array[sourcedFromNestedPathSegment]]
) -> IndexDefinition
end

Expand All @@ -38,7 +38,7 @@ module ElasticGraph
CURRENT_SOURCES: "current_sources"
FIELDS_BY_PATH: "fields_by_path"
HAS_HAD_MULTIPLE_SOURCES: "has_had_multiple_sources"
SOURCED_FROM_NESTED_PATHS_BY_RELATIONSHIP: "sourced_from_nested_paths_by_relationship"
SOURCED_FROM_NESTED_PATHS_BY_QUALIFIED_RELATIONSHIP: "sourced_from_nested_paths_by_qualified_relationship"

def initialize: (
route_with: ::String,
Expand All @@ -47,7 +47,7 @@ module ElasticGraph
current_sources: ::Enumerable[::String],
fields_by_path: ::Hash[::String, IndexField],
has_had_multiple_sources: bool,
sourced_from_nested_paths_by_relationship: ::Hash[::String, ::Array[sourcedFromNestedPathSegment]]
sourced_from_nested_paths_by_qualified_relationship: ::Hash[::String, ::Array[sourcedFromNestedPathSegment]]
) -> void

def self.from_hash: (::Hash[::String, untyped]) -> IndexDefinition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module RuntimeMetadata
current_sources: Set.new,
fields_by_path: {},
has_had_multiple_sources: false,
sourced_from_nested_paths_by_relationship: {}
sourced_from_nested_paths_by_qualified_relationship: {}
)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ module RuntimeMetadata
"foo.bar" => IndexField.new(source: "other")
},
has_had_multiple_sources: false,
sourced_from_nested_paths_by_relationship: {
sourced_from_nested_paths_by_qualified_relationship: {
"currency" => [
ListPathSegment.new(field: "costs", source_field: "cost_id"),
ObjectPathSegment.new(field: "details")
Expand All @@ -142,7 +142,7 @@ module RuntimeMetadata
current_sources: [SELF_RELATIONSHIP_NAME],
fields_by_path: {},
has_had_multiple_sources: false,
sourced_from_nested_paths_by_relationship: {}
sourced_from_nested_paths_by_qualified_relationship: {}
),
"components" => IndexDefinition.new(
route_with: "group_id",
Expand All @@ -151,7 +151,7 @@ module RuntimeMetadata
current_sources: [SELF_RELATIONSHIP_NAME],
fields_by_path: {},
has_had_multiple_sources: true,
sourced_from_nested_paths_by_relationship: {}
sourced_from_nested_paths_by_qualified_relationship: {}
)
},
schema_element_names: SchemaElementNames.new(
Expand Down Expand Up @@ -279,7 +279,7 @@ module RuntimeMetadata
"source" => "other"
}
},
"sourced_from_nested_paths_by_relationship" => {
"sourced_from_nested_paths_by_qualified_relationship" => {
"currency" => [
{"field" => "costs", "source_field" => "cost_id"},
{"field" => "details"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,21 @@ module Indexing
# @return [RolloverConfig, nil] rollover configuration for the index
# @!attribute [r] has_had_multiple_sources_flag
# @return [Boolean] whether this index has ever had multiple sources
class Index < Struct.new(:name, :default_sort_pairs, :settings, :schema_def_state, :indexed_type, :routing_field_path, :rollover_config, :has_had_multiple_sources_flag)
# @!attribute [r] sourced_from_nested_paths_by_qualified_relationship
# @return [Hash<String, Array<SchemaArtifacts::RuntimeMetadata::ListPathSegment, SchemaArtifacts::RuntimeMetadata::ObjectPathSegment>>]
# map from a qualified (leaf) relationship to the path segments the painless script uses to navigate from this
# root index's documents down to the nested elements that receive `sourced_from` data
class Index < Struct.new(
:name,
:default_sort_pairs,
:settings,
:schema_def_state,
:indexed_type,
:routing_field_path,
:rollover_config,
:has_had_multiple_sources_flag,
:sourced_from_nested_paths_by_qualified_relationship
)
include Mixins::HasReadableToSAndInspect.new { |i| i.name }

# @param name [String] name of the index
Expand All @@ -55,7 +69,7 @@ def initialize(name, settings, schema_def_state, indexed_type)

settings = DEFAULT_SETTINGS.merge(Support::HashUtil.flatten_and_stringify_keys(settings, prefix: "index"))

super(name, [], settings, schema_def_state, indexed_type, nil, nil, false)
super(name, [], settings, schema_def_state, indexed_type, nil, nil, false, {})

schema_def_state.after_user_definition_complete do
# `id` is the field Elasticsearch/OpenSearch use for routing by default:
Expand Down Expand Up @@ -265,10 +279,16 @@ def runtime_metadata
)
end,
has_had_multiple_sources: has_had_multiple_sources_flag,
sourced_from_nested_paths_by_relationship: {} # TODO: Populate with real data once paths are registered on the index
sourced_from_nested_paths_by_qualified_relationship: sourced_from_nested_paths_by_qualified_relationship
)
end

# Registers a resolved `parent_relationship` chain on this index.
# @api private
def register_resolved_relationship_chain(resolved_chain)
sourced_from_nested_paths_by_qualified_relationship[resolved_chain.qualified_relationship] = resolved_chain.sourced_from_nested_paths
end

private

# A regex that requires at least one non-whitespace character.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,42 @@
# frozen_string_literal: true

require "elastic_graph/errors"
require "elastic_graph/schema_artifacts/runtime_metadata/sourced_from_nested_path_segment"

module ElasticGraph
module SchemaDefinition
module Indexing
# The result of resolving a relationship chain.
#
# @private
ResolvedRelationshipChain = ::Data.define(
:root_relationship, # Relationship - the root relationship (no parent_relationship)
:path_segments # Array<PathSegment> - ordered root-to-leaf
class ResolvedRelationshipChain < ::Data.define(
:root_relationship, # Relationship the chain terminated at on the root indexed type
:leaf_relationship, # Relationship the chain was resolved from — backs `sourced_from` field(s)
:path_segments # Array<PathSegment> - the embedding fields to descend, ordered root-to-leaf
)
# The leaf relationship name qualified by its embedding-field path (hence unique per resolved chain)
def qualified_relationship
(path_segments.map { |segment| segment.field.name_in_index } + [leaf_relationship.name_in_index]).join(".")
end

# The runtime-metadata segments the painless script uses to navigate this chain: a `ListPathSegment` for
# each list embedding field (carrying the source field that matches the element) and an `ObjectPathSegment`
# for each object embedding field.
def sourced_from_nested_paths
path_segments.map do |segment|
if (source_field = segment.source_field_name)
SchemaArtifacts::RuntimeMetadata::ListPathSegment.new(
field: segment.field.name_in_index,
source_field: source_field
)
else
SchemaArtifacts::RuntimeMetadata::ObjectPathSegment.new(
field: segment.field.name_in_index
)
end
end
end
end

# Describes how to navigate from a parent type into a nested child element.
# For list fields, `source_field_name` identifies which element to update: the element
Expand Down Expand Up @@ -75,6 +100,7 @@ def resolve(starting_relationship)

resolved_chain = ResolvedRelationshipChain.new(
root_relationship: root_relationship,
leaf_relationship: starting_relationship,
path_segments: path_segments.reverse # reverse so root-to-leaf order
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def resolve_for_type(object_type, &error_reporter)
end
end

# Resolve any `parent_relationship` chains on this type. For now this only surfaces
# configuration errors; later PRs will use the resolved chains to build nested update targets.
# Resolve any `parent_relationship` chains on this type, surfacing configuration errors and
# registering the resolved path segments on the root index.
resolve_relationship_chains(object_type, &error_reporter)

results
Expand Down Expand Up @@ -102,12 +102,28 @@ def resolve_relationship_chains(object_type)
relationships_with_parent_ref = object_type.relationships_by_name.each_value.select(&:parent_ref)
return if relationships_with_parent_ref.empty?

# The set of relationship names this type's `sourced_from` fields draw their data through. We use
# `fields_with_sources` (rather than `sourced_fields_by_relationship_name`) because it works for
# non-indexed (embedded) types — which is exactly where nested `sourced_from` fields live.
relationship_names_with_sourced_fields = object_type.fields_with_sources.map { |f| (_ = f.source).relationship_name }.to_set

chain_resolver = RelationshipChainResolver.new(schema_def_state: @schema_def_state)

relationships_with_parent_ref.each do |relationship|
# TODO: use resolved_chain to build nested update targets once that logic is implemented.
_resolved_chain, chain_errors = chain_resolver.resolve(relationship)
# Every `parent_relationship` chain is resolved so its configuration errors are surfaced, even for
# relationships that don't themselves back any `sourced_from` field.
resolved_chain, chain_errors = chain_resolver.resolve(relationship)
chain_errors.each { |error| yield :sourced_field, error }
next unless resolved_chain

# Only register paths for relationships that back `sourced_from` fields. A pure link in a longer
# chain has no nested data of its own; its navigation lives in the leaf relationship's chain.
next unless relationship_names_with_sourced_fields.include?(relationship.name)

# Register the resolved chain on its root index, which records the path segments keyed by the
# chain's qualified relationship.
root_index = resolved_chain.root_relationship.parent_type.index_def # : Index
root_index.register_resolved_relationship_chain(resolved_chain)
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ module ElasticGraph
attr_reader rollover_config: RolloverConfig?
attr_reader has_had_multiple_sources_flag: bool
attr_reader indexed_type: indexableType
attr_reader sourced_from_nested_paths_by_qualified_relationship: ::Hash[::String, ::Array[SchemaArtifacts::RuntimeMetadata::sourcedFromNestedPathSegment]]

def uses_custom_routing?: () -> bool
def to_index_config: () -> ::Hash[::String, untyped]
def to_index_template_config: () -> ::Hash[::String, untyped]
def runtime_metadata: () -> SchemaArtifacts::RuntimeMetadata::IndexDefinition
def register_resolved_relationship_chain: (ResolvedRelationshipChain) -> void

private

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
module ElasticGraph
module SchemaDefinition
module Indexing
class ResolvedRelationshipChain
class ResolvedRelationshipChainSuperType
attr_reader root_relationship: SchemaElements::Relationship
attr_reader leaf_relationship: SchemaElements::Relationship
attr_reader path_segments: ::Array[PathSegment]

def initialize: (
root_relationship: SchemaElements::Relationship,
leaf_relationship: SchemaElements::Relationship,
path_segments: ::Array[PathSegment]
) -> void
end

class ResolvedRelationshipChain < ResolvedRelationshipChainSuperType
def qualified_relationship: () -> ::String
def sourced_from_nested_paths: () -> ::Array[SchemaArtifacts::RuntimeMetadata::sourcedFromNestedPathSegment]
end

class PathSegment
attr_reader field: SchemaElements::Field
attr_reader source_field_name: ::String?
Expand Down
Loading