elasticgraph-schema_artifacts 0.17.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +3 -0
- data/elasticgraph-schema_artifacts.gemspec +21 -0
- data/lib/elastic_graph/schema_artifacts/artifacts_helper_methods.rb +34 -0
- data/lib/elastic_graph/schema_artifacts/from_disk.rb +112 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/computation_detail.rb +34 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/enum.rb +65 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/extension.rb +51 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/extension_loader.rb +125 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/graphql_field.rb +54 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/hash_dumper.rb +21 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/index_definition.rb +78 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/index_field.rb +33 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/object_type.rb +81 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/params.rb +81 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/relation.rb +39 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/scalar_type.rb +94 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/schema.rb +99 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rb +165 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/sort_field.rb +47 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/update_target.rb +80 -0
- metadata +273 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/graphql_field"
|
10
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/hash_dumper"
|
11
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/update_target"
|
12
|
+
|
13
|
+
module ElasticGraph
|
14
|
+
module SchemaArtifacts
|
15
|
+
module RuntimeMetadata
|
16
|
+
# Provides runtime metadata related to object types.
|
17
|
+
class ObjectType < ::Data.define(
|
18
|
+
:update_targets,
|
19
|
+
:index_definition_names,
|
20
|
+
:graphql_fields_by_name,
|
21
|
+
:elasticgraph_category,
|
22
|
+
# Indicates the name of the GraphQL type from which this type was generated. Note that a `nil` value doesn't
|
23
|
+
# imply that this type was user-defined; we have recently introduced this metadata and are not yet setting
|
24
|
+
# it for all generated GraphQL types. For now, we are only setting it for specific cases where we need it.
|
25
|
+
:source_type,
|
26
|
+
:graphql_only_return_type
|
27
|
+
)
|
28
|
+
UPDATE_TARGETS = "update_targets"
|
29
|
+
INDEX_DEFINITION_NAMES = "index_definition_names"
|
30
|
+
GRAPHQL_FIELDS_BY_NAME = "graphql_fields_by_name"
|
31
|
+
ELASTICGRAPH_CATEGORY = "elasticgraph_category"
|
32
|
+
SOURCE_TYPE = "source_type"
|
33
|
+
GRAPHQL_ONLY_RETURN_TYPE = "graphql_only_return_type"
|
34
|
+
|
35
|
+
def initialize(update_targets:, index_definition_names:, graphql_fields_by_name:, elasticgraph_category:, source_type:, graphql_only_return_type:)
|
36
|
+
graphql_fields_by_name = graphql_fields_by_name.select { |name, field| field.needed?(name) }
|
37
|
+
|
38
|
+
super(
|
39
|
+
update_targets: update_targets,
|
40
|
+
index_definition_names: index_definition_names,
|
41
|
+
graphql_fields_by_name: graphql_fields_by_name,
|
42
|
+
elasticgraph_category: elasticgraph_category,
|
43
|
+
source_type: source_type,
|
44
|
+
graphql_only_return_type: graphql_only_return_type
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.from_hash(hash)
|
49
|
+
update_targets = hash[UPDATE_TARGETS]&.map do |update_target_hash|
|
50
|
+
UpdateTarget.from_hash(update_target_hash)
|
51
|
+
end || []
|
52
|
+
|
53
|
+
graphql_fields_by_name = hash[GRAPHQL_FIELDS_BY_NAME]&.transform_values do |field_hash|
|
54
|
+
GraphQLField.from_hash(field_hash)
|
55
|
+
end || {}
|
56
|
+
|
57
|
+
new(
|
58
|
+
update_targets: update_targets,
|
59
|
+
index_definition_names: hash[INDEX_DEFINITION_NAMES] || [],
|
60
|
+
graphql_fields_by_name: graphql_fields_by_name,
|
61
|
+
elasticgraph_category: hash[ELASTICGRAPH_CATEGORY]&.to_sym || nil,
|
62
|
+
source_type: hash[SOURCE_TYPE] || nil,
|
63
|
+
graphql_only_return_type: !!hash[GRAPHQL_ONLY_RETURN_TYPE]
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_dumpable_hash
|
68
|
+
{
|
69
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
70
|
+
ELASTICGRAPH_CATEGORY => elasticgraph_category&.to_s,
|
71
|
+
GRAPHQL_FIELDS_BY_NAME => HashDumper.dump_hash(graphql_fields_by_name, &:to_dumpable_hash),
|
72
|
+
GRAPHQL_ONLY_RETURN_TYPE => graphql_only_return_type ? true : nil,
|
73
|
+
INDEX_DEFINITION_NAMES => index_definition_names,
|
74
|
+
SOURCE_TYPE => source_type,
|
75
|
+
UPDATE_TARGETS => update_targets.map(&:to_dumpable_hash)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/hash_dumper"
|
10
|
+
require "elastic_graph/support/hash_util"
|
11
|
+
|
12
|
+
module ElasticGraph
|
13
|
+
module SchemaArtifacts
|
14
|
+
module RuntimeMetadata
|
15
|
+
module Param
|
16
|
+
def self.dump_params_hash(hash_of_params)
|
17
|
+
hash_of_params.sort_by(&:first).to_h { |name, param| [name, param.to_dumpable_hash(name)] }
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.load_params_hash(hash_of_hashes)
|
21
|
+
hash_of_hashes.to_h { |name, hash| [name, from_hash(hash, name)] }
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_hash(hash, name)
|
25
|
+
if hash.key?(StaticParam::VALUE)
|
26
|
+
StaticParam.from_hash(hash)
|
27
|
+
else
|
28
|
+
DynamicParam.from_hash(hash, name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Represents metadata about dynamic params we pass to our update scripts.
|
34
|
+
class DynamicParam < ::Data.define(:source_path, :cardinality)
|
35
|
+
SOURCE_PATH = "source_path"
|
36
|
+
CARDINALITY = "cardinality"
|
37
|
+
|
38
|
+
def self.from_hash(hash, name)
|
39
|
+
new(
|
40
|
+
source_path: hash[SOURCE_PATH] || name,
|
41
|
+
cardinality: hash.fetch(CARDINALITY).to_sym
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_dumpable_hash(param_name)
|
46
|
+
{
|
47
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
48
|
+
CARDINALITY => cardinality.to_s,
|
49
|
+
SOURCE_PATH => (source_path if source_path != param_name)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def value_for(event_or_prepared_record)
|
54
|
+
case cardinality
|
55
|
+
when :many then Support::HashUtil.fetch_leaf_values_at_path(event_or_prepared_record, source_path) { [] }
|
56
|
+
when :one then Support::HashUtil.fetch_value_at_path(event_or_prepared_record, source_path) { nil }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class StaticParam < ::Data.define(:value)
|
62
|
+
VALUE = "value"
|
63
|
+
|
64
|
+
def self.from_hash(hash)
|
65
|
+
new(value: hash.fetch(VALUE))
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_dumpable_hash(param_name)
|
69
|
+
{
|
70
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
71
|
+
VALUE => value
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def value_for(event_or_prepared_record)
|
76
|
+
value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
module ElasticGraph
|
10
|
+
module SchemaArtifacts
|
11
|
+
module RuntimeMetadata
|
12
|
+
class Relation < ::Data.define(:foreign_key, :direction, :additional_filter, :foreign_key_nested_paths)
|
13
|
+
FOREIGN_KEY = "foreign_key"
|
14
|
+
DIRECTION = "direction"
|
15
|
+
ADDITIONAL_FILTER = "additional_filter"
|
16
|
+
FOREIGN_KEY_NESTED_PATHS = "foreign_key_nested_paths"
|
17
|
+
|
18
|
+
def self.from_hash(hash)
|
19
|
+
new(
|
20
|
+
foreign_key: hash[FOREIGN_KEY],
|
21
|
+
direction: hash.fetch(DIRECTION).to_sym,
|
22
|
+
additional_filter: hash[ADDITIONAL_FILTER] || {},
|
23
|
+
foreign_key_nested_paths: hash[FOREIGN_KEY_NESTED_PATHS] || []
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_dumpable_hash
|
28
|
+
{
|
29
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
30
|
+
ADDITIONAL_FILTER => additional_filter,
|
31
|
+
DIRECTION => direction.to_s,
|
32
|
+
FOREIGN_KEY => foreign_key,
|
33
|
+
FOREIGN_KEY_NESTED_PATHS => foreign_key_nested_paths
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/extension_loader"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module SchemaArtifacts
|
13
|
+
module RuntimeMetadata
|
14
|
+
# Provides runtime metadata related to scalar types.
|
15
|
+
class ScalarType < ::Data.define(:coercion_adapter_ref, :indexing_preparer_ref)
|
16
|
+
def self.coercion_adapter_extension_loader
|
17
|
+
@coercion_adapter_extension_loader ||= ExtensionLoader.new(ScalarCoercionAdapterInterface)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.indexing_preparer_extension_loader
|
21
|
+
@indexing_preparer_extension_loader ||= ExtensionLoader.new(ScalarIndexingPreparerInterface)
|
22
|
+
end
|
23
|
+
|
24
|
+
DEFAULT_COERCION_ADAPTER_REF = {
|
25
|
+
"extension_name" => "ElasticGraph::GraphQL::ScalarCoercionAdapters::NoOp",
|
26
|
+
"require_path" => "elastic_graph/graphql/scalar_coercion_adapters/no_op"
|
27
|
+
}
|
28
|
+
|
29
|
+
DEFAULT_INDEXING_PREPARER_REF = {
|
30
|
+
"extension_name" => "ElasticGraph::Indexer::IndexingPreparers::NoOp",
|
31
|
+
"require_path" => "elastic_graph/indexer/indexing_preparers/no_op"
|
32
|
+
}
|
33
|
+
|
34
|
+
# Loads multiple `ScalarType`s from a hash mapping a scalar type name to its
|
35
|
+
# serialized hash form (matching what `to_dumpable_hash` returns). We expose a method
|
36
|
+
# this way because we want to use a single loader for all `ScalarType`s that
|
37
|
+
# need to get loaded, as it performs some caching for efficiency.
|
38
|
+
def self.load_many(scalar_type_hashes_by_name)
|
39
|
+
scalar_type_hashes_by_name.transform_values do |hash|
|
40
|
+
new(
|
41
|
+
coercion_adapter_ref: hash.fetch("coercion_adapter"),
|
42
|
+
# `indexing_preparer` is new as of Q4 2022, and as such is not present in schema artifacts
|
43
|
+
# dumped before then. Therefore, we allow for the key to not be present in the runtime
|
44
|
+
# metadata--important so that we don't have a "chicken and egg" problem where the rake tasks
|
45
|
+
# that need to be loaded to dump new schema artifacts fail at load time due to the missing key.
|
46
|
+
indexing_preparer_ref: hash.fetch("indexing_preparer", DEFAULT_INDEXING_PREPARER_REF)
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Loads the coercion adapter. This is done lazily on first access (rather than eagerly in `load_many`)
|
52
|
+
# to allow us to remove a runtime dependency of `elasticgraph-schema_artifacts` on `elasticgraph-graphql`.
|
53
|
+
# The built-in coercion adapters are defined in `elasticgraph-graphql`, and we want to be able to load
|
54
|
+
# runtime metadata without requiring the `elasticgraph-graphql` gem (and its dependencies) to be available.
|
55
|
+
# For example, we use runtime metadata from `elasticgraph-indexer` but do not want `elasticgraph-graphql`
|
56
|
+
# to be loaded as part of that.
|
57
|
+
#
|
58
|
+
# elasticgraph-graphql provides the one caller that calls this method, ensuring that the adapters are
|
59
|
+
# available to be loaded.
|
60
|
+
def load_coercion_adapter
|
61
|
+
Extension.load_from_hash(coercion_adapter_ref, via: self.class.coercion_adapter_extension_loader)
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_indexing_preparer
|
65
|
+
Extension.load_from_hash(indexing_preparer_ref, via: self.class.indexing_preparer_extension_loader)
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_dumpable_hash
|
69
|
+
{
|
70
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
71
|
+
"coercion_adapter" => load_coercion_adapter.to_dumpable_hash,
|
72
|
+
"indexing_preparer" => load_indexing_preparer.to_dumpable_hash
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
# `to_h` is used internally by `Value#with` and we want `#to_dumpable_hash` to be the public API.
|
77
|
+
private :to_h
|
78
|
+
end
|
79
|
+
|
80
|
+
class ScalarCoercionAdapterInterface
|
81
|
+
def self.coerce_input(value, ctx)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.coerce_result(value, ctx)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class ScalarIndexingPreparerInterface
|
89
|
+
def self.prepare_for_indexing(value)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/enum"
|
10
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/extension"
|
11
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/extension_loader"
|
12
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/hash_dumper"
|
13
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/index_definition"
|
14
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/object_type"
|
15
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/scalar_type"
|
16
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/schema_element_names"
|
17
|
+
require "elastic_graph/support/hash_util"
|
18
|
+
|
19
|
+
module ElasticGraph
|
20
|
+
module SchemaArtifacts
|
21
|
+
module RuntimeMetadata
|
22
|
+
# Entry point for runtime metadata for an entire schema.
|
23
|
+
class Schema < ::Data.define(
|
24
|
+
:object_types_by_name,
|
25
|
+
:scalar_types_by_name,
|
26
|
+
:enum_types_by_name,
|
27
|
+
:index_definitions_by_name,
|
28
|
+
:schema_element_names,
|
29
|
+
:graphql_extension_modules,
|
30
|
+
:static_script_ids_by_scoped_name
|
31
|
+
)
|
32
|
+
OBJECT_TYPES_BY_NAME = "object_types_by_name"
|
33
|
+
SCALAR_TYPES_BY_NAME = "scalar_types_by_name"
|
34
|
+
ENUM_TYPES_BY_NAME = "enum_types_by_name"
|
35
|
+
INDEX_DEFINITIONS_BY_NAME = "index_definitions_by_name"
|
36
|
+
SCHEMA_ELEMENT_NAMES = "schema_element_names"
|
37
|
+
GRAPHQL_EXTENSION_MODULES = "graphql_extension_modules"
|
38
|
+
STATIC_SCRIPT_IDS_BY_NAME = "static_script_ids_by_scoped_name"
|
39
|
+
|
40
|
+
def self.from_hash(hash, for_context:)
|
41
|
+
object_types_by_name = hash[OBJECT_TYPES_BY_NAME]&.transform_values do |type_hash|
|
42
|
+
ObjectType.from_hash(type_hash)
|
43
|
+
end || {}
|
44
|
+
|
45
|
+
scalar_types_by_name = hash[SCALAR_TYPES_BY_NAME]&.then do |subhash|
|
46
|
+
ScalarType.load_many(subhash)
|
47
|
+
end || {}
|
48
|
+
|
49
|
+
enum_types_by_name = hash[ENUM_TYPES_BY_NAME]&.transform_values do |type_hash|
|
50
|
+
Enum::Type.from_hash(type_hash)
|
51
|
+
end || {}
|
52
|
+
|
53
|
+
index_definitions_by_name = hash[INDEX_DEFINITIONS_BY_NAME]&.transform_values do |index_hash|
|
54
|
+
IndexDefinition.from_hash(index_hash)
|
55
|
+
end || {}
|
56
|
+
|
57
|
+
schema_element_names = SchemaElementNames.from_hash(hash.fetch(SCHEMA_ELEMENT_NAMES))
|
58
|
+
|
59
|
+
loader = ExtensionLoader.new(Module.new)
|
60
|
+
graphql_extension_modules =
|
61
|
+
if for_context == :graphql
|
62
|
+
hash[GRAPHQL_EXTENSION_MODULES]&.map do |ext_mod_hash|
|
63
|
+
Extension.load_from_hash(ext_mod_hash, via: loader)
|
64
|
+
end || []
|
65
|
+
else
|
66
|
+
# Avoid loading GraphQL extrnsion modules if we're not in a GraphQL context. We can't count
|
67
|
+
# on the extension modules even being available to load in other contexts.
|
68
|
+
[] # : ::Array[Extension]
|
69
|
+
end
|
70
|
+
|
71
|
+
static_script_ids_by_scoped_name = hash[STATIC_SCRIPT_IDS_BY_NAME] || {}
|
72
|
+
|
73
|
+
new(
|
74
|
+
object_types_by_name: object_types_by_name,
|
75
|
+
scalar_types_by_name: scalar_types_by_name,
|
76
|
+
enum_types_by_name: enum_types_by_name,
|
77
|
+
index_definitions_by_name: index_definitions_by_name,
|
78
|
+
schema_element_names: schema_element_names,
|
79
|
+
graphql_extension_modules: graphql_extension_modules,
|
80
|
+
static_script_ids_by_scoped_name: static_script_ids_by_scoped_name
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_dumpable_hash
|
85
|
+
Support::HashUtil.recursively_prune_nils_and_empties_from({
|
86
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
87
|
+
ENUM_TYPES_BY_NAME => HashDumper.dump_hash(enum_types_by_name, &:to_dumpable_hash),
|
88
|
+
GRAPHQL_EXTENSION_MODULES => graphql_extension_modules.map(&:to_dumpable_hash),
|
89
|
+
INDEX_DEFINITIONS_BY_NAME => HashDumper.dump_hash(index_definitions_by_name, &:to_dumpable_hash),
|
90
|
+
OBJECT_TYPES_BY_NAME => HashDumper.dump_hash(object_types_by_name, &:to_dumpable_hash),
|
91
|
+
SCALAR_TYPES_BY_NAME => HashDumper.dump_hash(scalar_types_by_name, &:to_dumpable_hash),
|
92
|
+
SCHEMA_ELEMENT_NAMES => schema_element_names.to_dumpable_hash,
|
93
|
+
STATIC_SCRIPT_IDS_BY_NAME => static_script_ids_by_scoped_name
|
94
|
+
})
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/constants"
|
10
|
+
require "elastic_graph/error"
|
11
|
+
|
12
|
+
module ElasticGraph
|
13
|
+
module SchemaArtifacts
|
14
|
+
module RuntimeMetadata
|
15
|
+
# Defines a generic schema element names API. Defined as a separate class to facilitate easy testing.
|
16
|
+
class SchemaElementNamesDefinition
|
17
|
+
def self.new(*element_names)
|
18
|
+
::Data.define(:form, :overrides, :exposed_name_by_canonical_name, :canonical_name_by_exposed_name) do
|
19
|
+
const_set(:ELEMENT_NAMES, element_names)
|
20
|
+
|
21
|
+
define_method :initialize do |form:, overrides: {}|
|
22
|
+
extend(CONVERTERS.fetch(form.to_s) do
|
23
|
+
raise SchemaError,
|
24
|
+
"Invalid schema element name form: #{form.inspect}. " \
|
25
|
+
"Only valid values are: #{CONVERTERS.keys.inspect}."
|
26
|
+
end)
|
27
|
+
|
28
|
+
unused_keys = overrides.keys.map(&:to_s) - element_names.map(&:to_s)
|
29
|
+
if unused_keys.any?
|
30
|
+
raise SchemaError,
|
31
|
+
"`overrides` contains entries that do not match any schema " \
|
32
|
+
"elements: #{unused_keys.to_a.inspect}. Are any misspelled?"
|
33
|
+
end
|
34
|
+
|
35
|
+
exposed_name_by_canonical_name = element_names.each_with_object({}) do |element, names|
|
36
|
+
names[element] = overrides.fetch(element) do
|
37
|
+
overrides.fetch(element.to_s) do
|
38
|
+
normalize_case(element.to_s)
|
39
|
+
end
|
40
|
+
end.to_s
|
41
|
+
end.freeze
|
42
|
+
|
43
|
+
canonical_name_by_exposed_name = exposed_name_by_canonical_name.invert
|
44
|
+
validate_no_name_collisions(canonical_name_by_exposed_name, exposed_name_by_canonical_name)
|
45
|
+
|
46
|
+
super(
|
47
|
+
form: form,
|
48
|
+
overrides: overrides,
|
49
|
+
exposed_name_by_canonical_name: exposed_name_by_canonical_name,
|
50
|
+
canonical_name_by_exposed_name: canonical_name_by_exposed_name
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
# standard:disable Lint/NestedMethodDefinition
|
55
|
+
element_names.each do |element|
|
56
|
+
method_name = SnakeCaseConverter.normalize_case(element.to_s)
|
57
|
+
define_method(method_name) { exposed_name_by_canonical_name.fetch(element) }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the _canonical_ name for the given _exposed name_. The canonical name
|
61
|
+
# is the name we use within the source code of our framework; the exposed name
|
62
|
+
# is the name exposed in the specific GraphQL schema based on the configuration
|
63
|
+
# of the project.
|
64
|
+
def canonical_name_for(exposed_name)
|
65
|
+
canonical_name_by_exposed_name[exposed_name.to_s]
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.from_hash(hash)
|
69
|
+
new(
|
70
|
+
form: hash.fetch(FORM).to_sym,
|
71
|
+
overrides: hash[OVERRIDES] || {}
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_dumpable_hash
|
76
|
+
{
|
77
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
78
|
+
FORM => form.to_s,
|
79
|
+
OVERRIDES => overrides
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
"#<#{self.class.name} form=#{form}, overrides=#{overrides}>"
|
85
|
+
end
|
86
|
+
alias_method :inspect, :to_s
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def validate_no_name_collisions(canonical_name_by_exposed_name, exposed_name_by_canonical_name)
|
91
|
+
return if canonical_name_by_exposed_name.size == exposed_name_by_canonical_name.size
|
92
|
+
|
93
|
+
collisions = exposed_name_by_canonical_name
|
94
|
+
.group_by { |k, v| v }
|
95
|
+
.reject { |v, kv_pairs| kv_pairs.size == 1 }
|
96
|
+
.transform_values { |kv_pairs| kv_pairs.map(&:first) }
|
97
|
+
.map do |duplicate_exposed_name, canonical_names|
|
98
|
+
"#{canonical_names.inspect} all map to the same exposed name: #{duplicate_exposed_name}"
|
99
|
+
end.join(" and ")
|
100
|
+
|
101
|
+
raise SchemaError, collisions
|
102
|
+
end
|
103
|
+
# standard:enable Lint/NestedMethodDefinition
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
FORM = "form"
|
108
|
+
OVERRIDES = "overrides"
|
109
|
+
|
110
|
+
module SnakeCaseConverter
|
111
|
+
extend self
|
112
|
+
|
113
|
+
def normalize_case(name)
|
114
|
+
name.gsub(/([[:upper:]])/) { "_#{$1.downcase}" }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module CamelCaseConverter
|
119
|
+
extend self
|
120
|
+
|
121
|
+
def normalize_case(name)
|
122
|
+
name.gsub(/_(\w)/) { $1.upcase }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
CONVERTERS = {
|
127
|
+
"snake_case" => SnakeCaseConverter,
|
128
|
+
"camelCase" => CamelCaseConverter
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
SchemaElementNames = SchemaElementNamesDefinition.new(
|
133
|
+
# Filter arg and operation names:
|
134
|
+
:filter,
|
135
|
+
:equal_to_any_of, :gt, :gte, :lt, :lte, :matches, :matches_phrase, :matches_query, :any_of, :all_of, :not,
|
136
|
+
:time_of_day, :any_satisfy,
|
137
|
+
# Directives
|
138
|
+
:eg_latency_slo, :ms,
|
139
|
+
# For sorting.
|
140
|
+
:order_by,
|
141
|
+
# For aggregation
|
142
|
+
:grouped_by, :count, :count_detail, :aggregated_values, :sub_aggregations,
|
143
|
+
# Date/time grouping aggregation fields
|
144
|
+
:as_date_time, :as_date, :as_time_of_day, :as_day_of_week,
|
145
|
+
# Date/time grouping aggregation arguments
|
146
|
+
:offset, :amount, :unit, :time_zone, :truncation_unit,
|
147
|
+
# TODO: Drop support for legacy grouping schema that uses `granularity` and `offset_days`
|
148
|
+
:granularity, :offset_days,
|
149
|
+
# For aggregation counts.
|
150
|
+
:approximate_value, :exact_value, :upper_bound,
|
151
|
+
# For pagination.
|
152
|
+
:first, :after, :last, :before,
|
153
|
+
:edges, :node, :nodes, :cursor,
|
154
|
+
:page_info, :start_cursor, :end_cursor, :total_edge_count, :has_previous_page, :has_next_page,
|
155
|
+
# Subfields of `GeoLocation`/`GeoLocationFilterInput`:
|
156
|
+
:latitude, :longitude, :near, :max_distance,
|
157
|
+
# Subfields of `MatchesQueryFilterInput`/`MatchesPhraseFilterInput`
|
158
|
+
:query, :phrase, :allowed_edits_per_term, :require_all_terms,
|
159
|
+
# Aggregated values field names:
|
160
|
+
:exact_min, :exact_max, :approximate_min, :approximate_max, :approximate_avg, :approximate_sum, :exact_sum,
|
161
|
+
:approximate_distinct_value_count
|
162
|
+
)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/error"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module SchemaArtifacts
|
13
|
+
module RuntimeMetadata
|
14
|
+
class SortField < ::Data.define(:field_path, :direction)
|
15
|
+
def initialize(field_path:, direction:)
|
16
|
+
unless direction == :asc || direction == :desc
|
17
|
+
raise SchemaError, "Sort direction `#{direction.inspect}` is invalid; it must be `:asc` or `:desc`"
|
18
|
+
end
|
19
|
+
|
20
|
+
super(field_path: field_path, direction: direction)
|
21
|
+
end
|
22
|
+
|
23
|
+
FIELD_PATH = "field_path"
|
24
|
+
DIRECTION = "direction"
|
25
|
+
|
26
|
+
def self.from_hash(hash)
|
27
|
+
new(
|
28
|
+
field_path: hash[FIELD_PATH],
|
29
|
+
direction: hash.fetch(DIRECTION).to_sym
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_dumpable_hash
|
34
|
+
{
|
35
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
36
|
+
DIRECTION => direction.to_s,
|
37
|
+
FIELD_PATH => field_path
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_query_clause
|
42
|
+
{field_path => {"order" => direction.to_s}}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/constants"
|
10
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/params"
|
11
|
+
|
12
|
+
module ElasticGraph
|
13
|
+
module SchemaArtifacts
|
14
|
+
module RuntimeMetadata
|
15
|
+
# Provides runtime metadata related to the targets of datastore `update` calls.
|
16
|
+
class UpdateTarget < ::Data.define(
|
17
|
+
:type,
|
18
|
+
:relationship,
|
19
|
+
:script_id,
|
20
|
+
:id_source,
|
21
|
+
:routing_value_source,
|
22
|
+
:rollover_timestamp_value_source,
|
23
|
+
:data_params,
|
24
|
+
:metadata_params
|
25
|
+
)
|
26
|
+
TYPE = "type"
|
27
|
+
RELATIONSHIP = "relationship"
|
28
|
+
SCRIPT_ID = "script_id"
|
29
|
+
ID_SOURCE = "id_source"
|
30
|
+
ROUTING_VALUE_SOURCE = "routing_value_source"
|
31
|
+
ROLLOVER_TIMESTAMP_VALUE_SOURCE = "rollover_timestamp_value_source"
|
32
|
+
DATA_PARAMS = "data_params"
|
33
|
+
METADATA_PARAMS = "metadata_params"
|
34
|
+
|
35
|
+
def self.from_hash(hash)
|
36
|
+
new(
|
37
|
+
type: hash[TYPE],
|
38
|
+
relationship: hash[RELATIONSHIP],
|
39
|
+
script_id: hash[SCRIPT_ID],
|
40
|
+
id_source: hash[ID_SOURCE],
|
41
|
+
routing_value_source: hash[ROUTING_VALUE_SOURCE],
|
42
|
+
rollover_timestamp_value_source: hash[ROLLOVER_TIMESTAMP_VALUE_SOURCE],
|
43
|
+
data_params: Param.load_params_hash(hash[DATA_PARAMS] || {}),
|
44
|
+
metadata_params: Param.load_params_hash(hash[METADATA_PARAMS] || {})
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_dumpable_hash
|
49
|
+
{
|
50
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
51
|
+
DATA_PARAMS => Param.dump_params_hash(data_params),
|
52
|
+
ID_SOURCE => id_source,
|
53
|
+
METADATA_PARAMS => Param.dump_params_hash(metadata_params),
|
54
|
+
RELATIONSHIP => relationship,
|
55
|
+
ROLLOVER_TIMESTAMP_VALUE_SOURCE => rollover_timestamp_value_source,
|
56
|
+
ROUTING_VALUE_SOURCE => routing_value_source,
|
57
|
+
SCRIPT_ID => script_id,
|
58
|
+
TYPE => type
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def for_normal_indexing?
|
63
|
+
script_id == INDEX_DATA_UPDATE_SCRIPT_ID
|
64
|
+
end
|
65
|
+
|
66
|
+
def params_for(doc_id:, event:, prepared_record:)
|
67
|
+
data = data_params.to_h do |name, param|
|
68
|
+
[name, param.value_for(prepared_record)]
|
69
|
+
end
|
70
|
+
|
71
|
+
meta = metadata_params.to_h do |name, param|
|
72
|
+
[name, param.value_for(event)]
|
73
|
+
end
|
74
|
+
|
75
|
+
meta.merge({"id" => doc_id, "data" => data})
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|