elasticgraph-schema_definition 1.0.2 → 1.0.3.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/lib/elastic_graph/schema_definition/api.rb +34 -2
  4. data/lib/elastic_graph/schema_definition/factory.rb +31 -1
  5. data/lib/elastic_graph/schema_definition/indexing/derived_fields/append_only_set.rb +1 -1
  6. data/lib/elastic_graph/schema_definition/indexing/derived_fields/field_initializer_support.rb +1 -1
  7. data/lib/elastic_graph/schema_definition/indexing/derived_fields/immutable_value.rb +1 -1
  8. data/lib/elastic_graph/schema_definition/indexing/derived_fields/min_or_max_value.rb +1 -1
  9. data/lib/elastic_graph/schema_definition/indexing/derived_indexed_type.rb +1 -1
  10. data/lib/elastic_graph/schema_definition/indexing/event_envelope.rb +1 -1
  11. data/lib/elastic_graph/schema_definition/indexing/field.rb +8 -2
  12. data/lib/elastic_graph/schema_definition/indexing/field_reference.rb +6 -4
  13. data/lib/elastic_graph/schema_definition/indexing/field_type/enum.rb +1 -1
  14. data/lib/elastic_graph/schema_definition/indexing/field_type/object.rb +10 -4
  15. data/lib/elastic_graph/schema_definition/indexing/field_type/scalar.rb +1 -1
  16. data/lib/elastic_graph/schema_definition/indexing/field_type/union.rb +1 -1
  17. data/lib/elastic_graph/schema_definition/indexing/index.rb +39 -4
  18. data/lib/elastic_graph/schema_definition/indexing/json_schema_field_metadata.rb +1 -1
  19. data/lib/elastic_graph/schema_definition/indexing/json_schema_with_metadata.rb +1 -1
  20. data/lib/elastic_graph/schema_definition/indexing/list_counts_mapping.rb +1 -1
  21. data/lib/elastic_graph/schema_definition/indexing/relationship_resolver.rb +1 -1
  22. data/lib/elastic_graph/schema_definition/indexing/rollover_config.rb +1 -1
  23. data/lib/elastic_graph/schema_definition/indexing/update_target_factory.rb +1 -1
  24. data/lib/elastic_graph/schema_definition/indexing/update_target_resolver.rb +1 -1
  25. data/lib/elastic_graph/schema_definition/json_schema_pruner.rb +1 -1
  26. data/lib/elastic_graph/schema_definition/mixins/can_be_graphql_only.rb +1 -1
  27. data/lib/elastic_graph/schema_definition/mixins/has_derived_graphql_type_customizations.rb +1 -1
  28. data/lib/elastic_graph/schema_definition/mixins/has_directives.rb +1 -1
  29. data/lib/elastic_graph/schema_definition/mixins/has_documentation.rb +1 -1
  30. data/lib/elastic_graph/schema_definition/mixins/has_indices.rb +2 -2
  31. data/lib/elastic_graph/schema_definition/mixins/has_readable_to_s_and_inspect.rb +1 -1
  32. data/lib/elastic_graph/schema_definition/mixins/has_subtypes.rb +1 -1
  33. data/lib/elastic_graph/schema_definition/mixins/has_type_info.rb +1 -1
  34. data/lib/elastic_graph/schema_definition/mixins/implements_interfaces.rb +1 -1
  35. data/lib/elastic_graph/schema_definition/mixins/supports_default_value.rb +1 -1
  36. data/lib/elastic_graph/schema_definition/mixins/supports_filtering_and_aggregation.rb +1 -1
  37. data/lib/elastic_graph/schema_definition/mixins/verifies_graphql_name.rb +1 -1
  38. data/lib/elastic_graph/schema_definition/rake_tasks.rb +6 -6
  39. data/lib/elastic_graph/schema_definition/results.rb +12 -1
  40. data/lib/elastic_graph/schema_definition/schema_artifact_manager.rb +39 -29
  41. data/lib/elastic_graph/schema_definition/schema_elements/argument.rb +1 -1
  42. data/lib/elastic_graph/schema_definition/schema_elements/built_in_types.rb +8 -1
  43. data/lib/elastic_graph/schema_definition/schema_elements/deprecated_element.rb +1 -1
  44. data/lib/elastic_graph/schema_definition/schema_elements/directive.rb +1 -1
  45. data/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb +1 -1
  46. data/lib/elastic_graph/schema_definition/schema_elements/enum_value.rb +1 -1
  47. data/lib/elastic_graph/schema_definition/schema_elements/enum_value_namer.rb +1 -1
  48. data/lib/elastic_graph/schema_definition/schema_elements/enums_for_indexed_types.rb +1 -1
  49. data/lib/elastic_graph/schema_definition/schema_elements/field.rb +6 -3
  50. data/lib/elastic_graph/schema_definition/schema_elements/field_path.rb +1 -1
  51. data/lib/elastic_graph/schema_definition/schema_elements/field_source.rb +1 -1
  52. data/lib/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rb +1 -1
  53. data/lib/elastic_graph/schema_definition/schema_elements/input_field.rb +1 -1
  54. data/lib/elastic_graph/schema_definition/schema_elements/input_type.rb +1 -1
  55. data/lib/elastic_graph/schema_definition/schema_elements/interface_type.rb +1 -1
  56. data/lib/elastic_graph/schema_definition/schema_elements/list_counts_state.rb +1 -1
  57. data/lib/elastic_graph/schema_definition/schema_elements/object_type.rb +1 -1
  58. data/lib/elastic_graph/schema_definition/schema_elements/relationship.rb +3 -2
  59. data/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb +82 -4
  60. data/lib/elastic_graph/schema_definition/schema_elements/sort_order_enum_value.rb +1 -1
  61. data/lib/elastic_graph/schema_definition/schema_elements/sub_aggregation_path.rb +1 -1
  62. data/lib/elastic_graph/schema_definition/schema_elements/type_namer.rb +1 -1
  63. data/lib/elastic_graph/schema_definition/schema_elements/type_reference.rb +1 -1
  64. data/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb +5 -3
  65. data/lib/elastic_graph/schema_definition/schema_elements/union_type.rb +1 -1
  66. data/lib/elastic_graph/schema_definition/scripting/file_system_repository.rb +1 -1
  67. data/lib/elastic_graph/schema_definition/scripting/script.rb +1 -1
  68. data/lib/elastic_graph/schema_definition/state.rb +7 -3
  69. data/lib/elastic_graph/schema_definition/test_support.rb +2 -3
  70. metadata +32 -26
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -45,28 +45,6 @@ module ElasticGraph
45
45
  "they can perform code generation and event validation."
46
46
  ]
47
47
  )
48
-
49
- # Here we round-trip the SDL string through the GraphQL gem's formatting logic. This provides
50
- # nice, consistent formatting (alphabetical order, consistent spacing, etc) and also prunes out
51
- # any "orphaned" schema types (that is, types that are defined but never referenced).
52
- # We also prepend a line break so there's a blank line between the comment block and the
53
- # schema elements.
54
- graphql_schema = ::GraphQL::Schema.from_definition(schema_definition_results.graphql_schema_string).to_definition.chomp
55
-
56
- unversioned_artifacts = [
57
- new_yaml_artifact(DATASTORE_CONFIG_FILE, schema_definition_results.datastore_config),
58
- new_yaml_artifact(RUNTIME_METADATA_FILE, pruned_runtime_metadata(graphql_schema).to_dumpable_hash),
59
- @json_schemas_artifact,
60
- new_raw_artifact(GRAPHQL_SCHEMA_FILE, "\n" + graphql_schema)
61
- ]
62
-
63
- versioned_artifacts = build_desired_versioned_json_schemas(@json_schemas_artifact.desired_contents).values.map do |versioned_schema|
64
- new_versioned_json_schema_artifact(versioned_schema)
65
- end
66
-
67
- @artifacts = (unversioned_artifacts + versioned_artifacts).sort_by(&:file_name)
68
- notify_about_unused_type_name_overrides
69
- notify_about_unused_enum_value_overrides
70
48
  end
71
49
 
72
50
  # Dumps all the schema artifacts to disk.
@@ -80,7 +58,7 @@ module ElasticGraph
80
58
  setter_location_path = ::Pathname.new(setter_location.absolute_path.to_s).relative_path_from(::Dir.pwd)
81
59
 
82
60
  abort "A change has been attempted to `json_schemas.yaml`, but the `json_schema_version` has not been correspondingly incremented. Please " \
83
- "increase the schema's version, and then run the `schema_artifacts:dump` command again.\n\n" \
61
+ "increase the schema's version, and then run the `bundle exec rake schema_artifacts:dump` command again.\n\n" \
84
62
  "To update the schema version to the expected version, change line #{setter_location.lineno} at `#{setter_location_path}` to:\n" \
85
63
  " `schema.json_schema_version #{recommended_json_schema_version}`\n\n" \
86
64
  "Alternately, pass `enforce_json_schema_version: false` to `ElasticGraph::SchemaDefinition::RakeTasks.new` to allow the JSON schemas " \
@@ -94,15 +72,15 @@ module ElasticGraph
94
72
  end
95
73
 
96
74
  ::FileUtils.mkdir_p(@schema_artifacts_directory)
97
- @artifacts.each { |artifact| artifact.dump(@output) }
75
+ artifacts.each { |artifact| artifact.dump(@output) }
98
76
  end
99
77
 
100
78
  # Checks that all schema artifacts are up-to-date, raising an exception if not.
101
79
  def check_artifacts
102
- out_of_date_artifacts = @artifacts.select(&:out_of_date?)
80
+ out_of_date_artifacts = artifacts.select(&:out_of_date?)
103
81
 
104
82
  if out_of_date_artifacts.empty?
105
- descriptions = @artifacts.map.with_index(1) { |art, i| "#{i}. #{art.file_name}" }
83
+ descriptions = artifacts.map.with_index(1) { |art, i| "#{i}. #{art.file_name}" }
106
84
  @output.puts <<~EOS
107
85
  Your schema artifacts are all up to date:
108
86
  #{descriptions.join("\n")}
@@ -115,6 +93,38 @@ module ElasticGraph
115
93
 
116
94
  private
117
95
 
96
+ def artifacts
97
+ @artifacts ||= artifacts_from_schema_def.sort_by(&:file_name).tap do
98
+ # This must be deferred until artifacts are generated, as we can't fully detect
99
+ # unused things until after we've used things to generate artifacts.
100
+ notify_about_unused_type_name_overrides
101
+ notify_about_unused_enum_value_overrides
102
+ end
103
+ end
104
+
105
+ # Defined to offer a convenient method to override in an extension in order to add a new schema artifact.
106
+ def artifacts_from_schema_def
107
+ # Here we round-trip the SDL string through the GraphQL gem's formatting logic. This provides
108
+ # nice, consistent formatting (alphabetical order, consistent spacing, etc) and also prunes out
109
+ # any "orphaned" schema types (that is, types that are defined but never referenced).
110
+ # We also prepend a line break so there's a blank line between the comment block and the
111
+ # schema elements.
112
+ graphql_schema = ::GraphQL::Schema.from_definition(schema_definition_results.graphql_schema_string).to_definition.chomp
113
+
114
+ unversioned_artifacts = [
115
+ new_yaml_artifact(DATASTORE_CONFIG_FILE, schema_definition_results.datastore_config),
116
+ new_yaml_artifact(RUNTIME_METADATA_FILE, pruned_runtime_metadata(graphql_schema).to_dumpable_hash),
117
+ @json_schemas_artifact,
118
+ new_raw_artifact(GRAPHQL_SCHEMA_FILE, "\n" + graphql_schema)
119
+ ]
120
+
121
+ versioned_artifacts = build_desired_versioned_json_schemas(@json_schemas_artifact.desired_contents).values.map do |versioned_schema|
122
+ new_versioned_json_schema_artifact(versioned_schema)
123
+ end
124
+
125
+ unversioned_artifacts + versioned_artifacts
126
+ end
127
+
118
128
  def notify_about_unused_type_name_overrides
119
129
  type_namer = @schema_definition_results.state.type_namer
120
130
  return if (unused_overrides = type_namer.unused_name_overrides).empty?
@@ -338,7 +348,7 @@ module ElasticGraph
338
348
  end
339
349
 
340
350
  <<~EOS.strip
341
- #{out_of_date_artifacts.size} schema artifact(s) are out of date. Run `rake schema_artifacts:dump` to update the following artifact(s):
351
+ #{out_of_date_artifacts.size} schema artifact(s) are out of date. Run `bundle exec rake schema_artifacts:dump` to update the following artifact(s):
342
352
 
343
353
  #{descriptions.join("\n")}
344
354
 
@@ -471,7 +481,7 @@ module ElasticGraph
471
481
 
472
482
  def comment_preamble
473
483
  lines = [
474
- "Generated by `rake schema_artifacts:dump`.",
484
+ "Generated by `bundle exec rake schema_artifacts:dump`.",
475
485
  "DO NOT EDIT BY HAND. Any edits will be lost the next time the rake task is run."
476
486
  ]
477
487
 
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -719,6 +719,11 @@ module ElasticGraph
719
719
  t.prepare_for_indexing_with "ElasticGraph::Indexer::IndexingPreparers::Integer",
720
720
  defined_at: "elastic_graph/indexer/indexing_preparers/integer"
721
721
 
722
+ # The GraphQL gem automatically coerces Int values, so we can safely use MISSING_NUMERIC_PLACEHOLDER
723
+ # as the grouping missing value placeholder even though we don't override the default (no-op) ElasticGraph
724
+ # scalar coercion adapter.
725
+ t.grouping_missing_value_placeholder MISSING_NUMERIC_PLACEHOLDER
726
+
722
727
  define_integral_aggregated_values_for(t)
723
728
  end
724
729
 
@@ -789,6 +794,8 @@ module ElasticGraph
789
794
  t.json_schema type: "string", format: "date-time"
790
795
  t.coerce_with "ElasticGraph::GraphQL::ScalarCoercionAdapters::DateTime",
791
796
  defined_at: "elastic_graph/graphql/scalar_coercion_adapters/date_time"
797
+ t.prepare_for_indexing_with "ElasticGraph::Indexer::IndexingPreparers::DateTime",
798
+ defined_at: "elastic_graph/indexer/indexing_preparers/date_time"
792
799
 
793
800
  t.documentation <<~EOS
794
801
  A timestamp, represented as an [ISO 8601 time string](https://en.wikipedia.org/wiki/ISO_8601).
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -515,7 +515,9 @@ module ElasticGraph
515
515
  # f.sourced_from "capitalOf", "currency"
516
516
  # end
517
517
  #
518
- # t.index "cities"
518
+ # t.index "cities" do |i|
519
+ # i.has_had_multiple_sources!
520
+ # end
519
521
  # end
520
522
  # end
521
523
  def sourced_from(relationship, field_path)
@@ -968,7 +970,8 @@ module ElasticGraph
968
970
  json_schema_options: json_schema_options,
969
971
  accuracy_confidence: accuracy_confidence,
970
972
  source: source,
971
- runtime_field_script: runtime_field_script
973
+ runtime_field_script: runtime_field_script,
974
+ doc_comment: doc_comment
972
975
  )
973
976
  end
974
977
 
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -112,8 +112,9 @@ module ElasticGraph
112
112
  # f.sourced_from "launchPlan", "launchDate"
113
113
  # end
114
114
  #
115
- # t.index "campaigns"do |i|
115
+ # t.index "campaigns" do |i|
116
116
  # i.rollover :yearly, "createdAt"
117
+ # i.has_had_multiple_sources!
117
118
  # end
118
119
  # end
119
120
  #
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -44,6 +44,7 @@ module ElasticGraph
44
44
  class ScalarType < Struct.new(
45
45
  :schema_def_state,
46
46
  :type_ref,
47
+ :grouping_missing_value_placeholder_overridden,
47
48
  :mapping_type,
48
49
  :runtime_metadata,
49
50
  :aggregated_values_customizations,
@@ -66,12 +67,13 @@ module ElasticGraph
66
67
 
67
68
  # @private
68
69
  def initialize(schema_def_state, name)
69
- super(schema_def_state, schema_def_state.type_ref(name).to_final_form)
70
+ super(schema_def_state, schema_def_state.type_ref(name).to_final_form, false)
70
71
 
71
72
  # Default the runtime metadata before yielding, so it can be overridden as needed.
72
73
  self.runtime_metadata = SchemaArtifacts::RuntimeMetadata::ScalarType.new(
73
74
  coercion_adapter_ref: SchemaArtifacts::RuntimeMetadata::ScalarType::DEFAULT_COERCION_ADAPTER_REF,
74
- indexing_preparer_ref: SchemaArtifacts::RuntimeMetadata::ScalarType::DEFAULT_INDEXING_PREPARER_REF
75
+ indexing_preparer_ref: SchemaArtifacts::RuntimeMetadata::ScalarType::DEFAULT_INDEXING_PREPARER_REF,
76
+ grouping_missing_value_placeholder: nil
75
77
  )
76
78
 
77
79
  yield self
@@ -84,6 +86,10 @@ module ElasticGraph
84
86
  if missing.any?
85
87
  raise Errors::SchemaError, "Scalar types require `mapping` and `json_schema` to be configured, but `#{name}` lacks #{missing.join(" and ")}."
86
88
  end
89
+
90
+ if (placeholder = inferred_grouping_missing_value_placeholder)
91
+ self.runtime_metadata = runtime_metadata.with(grouping_missing_value_placeholder: placeholder)
92
+ end
87
93
  end
88
94
 
89
95
  # @return [String] name of the scalar type
@@ -154,6 +160,31 @@ module ElasticGraph
154
160
  }).tap(&:load_indexing_preparer) # verify the preparer is valid.
155
161
  end
156
162
 
163
+ # Specifies a placeholder value to use for missing values when grouping by this scalar type.
164
+ # This optimization allows ElasticGraph to use a single terms aggregation instead of separate
165
+ # terms and missing aggregations, reducing the exponential explosion of subaggregations when
166
+ # grouping by multiple fields.
167
+ #
168
+ # @param placeholder [String, Numeric] the placeholder value to use for missing/null values
169
+ # @return [void]
170
+ #
171
+ # @example Define a grouping missing value placeholder
172
+ # ElasticGraph.define_schema do |schema|
173
+ # schema.scalar_type "BigInt" do |t|
174
+ # t.mapping type: "long"
175
+ # t.json_schema type: "integer", minimum: -(2**53) + 1, maximum: (2**53) - 1
176
+ # t.grouping_missing_value_placeholder "NaN"
177
+ # end
178
+ # end
179
+ def grouping_missing_value_placeholder(placeholder)
180
+ unless placeholder.nil? || placeholder.is_a?(String) || placeholder.is_a?(Numeric)
181
+ raise Errors::SchemaError, "grouping_missing_value_placeholder must be a String or Numeric value, but got #{placeholder.class}: #{placeholder.inspect}"
182
+ end
183
+
184
+ self.grouping_missing_value_placeholder_overridden = true
185
+ self.runtime_metadata = runtime_metadata.with(grouping_missing_value_placeholder: placeholder)
186
+ end
187
+
157
188
  # @return [String] the GraphQL SDL form of this scalar
158
189
  def to_sdl
159
190
  "#{formatted_documentation}scalar #{name} #{directives_sdl}"
@@ -310,9 +341,56 @@ module ElasticGraph
310
341
  schema_def_state.factory.new_aggregated_values_type_for_index_leaf_type(name, &customization_block)
311
342
  end
312
343
 
344
+ def inferred_grouping_missing_value_placeholder
345
+ return nil if grouping_missing_value_placeholder_overridden || mapping_type.nil?
346
+
347
+ if STRING_TYPES.include?(mapping_type)
348
+ MISSING_STRING_PLACEHOLDER
349
+ elsif FLOAT_TYPES.include?(mapping_type)
350
+ MISSING_NUMERIC_PLACEHOLDER
351
+ elsif mapping_type == "long"
352
+ # It is only safe to use NaN for a long when the long's range is safe to coerce to a float
353
+ # without loss of precision. This is because using NaN as the missing value will cause
354
+ # the datastore to coerce the other bucket keys to float.
355
+ # JSON schema min/max only constrains newly indexed values, not existing data that may fall outside the range before the constraints were added.
356
+ # This is an edge case where the long range may exceed safe float precision.
357
+ # In this case, users can set grouping_missing_value_placeholder to nil.
358
+ if (json_schema_options[:minimum] || LONG_STRING_MIN) >= JSON_SAFE_LONG_MIN &&
359
+ (json_schema_options[:maximum] || LONG_STRING_MAX) <= JSON_SAFE_LONG_MAX
360
+ inferred_numeric_placeholder_for_integer_type
361
+ end
362
+ elsif mapping_type == "unsigned_long"
363
+ # Similar to the checks above for long except we only need to check the max
364
+ # (since the min is zero even if not specified)
365
+ if (json_schema_options[:maximum] || LONG_STRING_MAX) <= JSON_SAFE_LONG_MAX
366
+ inferred_numeric_placeholder_for_integer_type
367
+ end
368
+ elsif INTEGER_TYPES.include?(mapping_type)
369
+ # All other integer types can safely be coerced to float without loss of precision
370
+ inferred_numeric_placeholder_for_integer_type
371
+ end
372
+ end
373
+
374
+ def inferred_numeric_placeholder_for_integer_type
375
+ # Using NaN as the missing value placeholder causes the datastore to coerce all bucket keys to float.
376
+ # If using the default coercion adapter (which is a no-op), the values won't be coerced back to integers,
377
+ # causing a type change in the returned values. Only use NaN if a custom coercion adapter is configured.
378
+ if runtime_metadata.coercion_adapter_ref == SchemaArtifacts::RuntimeMetadata::ScalarType::DEFAULT_COERCION_ADAPTER_REF
379
+ nil
380
+ else
381
+ MISSING_NUMERIC_PLACEHOLDER
382
+ end
383
+ end
384
+
313
385
  # https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
314
386
  # https://www.elastic.co/guide/en/elasticsearch/reference/7.13/number.html#number
315
- NUMERIC_TYPES = %w[long integer short byte double float half_float scaled_float unsigned_long].to_set
387
+ FLOAT_TYPES = %w[double float half_float scaled_float].to_set
388
+ INTEGER_TYPES = %w[long integer short byte unsigned_long].to_set
389
+ NUMERIC_TYPES = FLOAT_TYPES | INTEGER_TYPES
390
+ # https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/keyword
391
+ # https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/text-type-family
392
+ # https://docs.opensearch.org/latest/mappings/supported-field-types/index/#string-based-field-types
393
+ STRING_TYPES = %w[keyword constant_keyword wildcard text match_only_text pattern_text semantic_text].to_set
316
394
  DATE_TYPES = %w[date date_nanos].to_set
317
395
  # The Elasticsearch/OpenSearch docs do not exhaustively give a list of types on which range queries are efficient,
318
396
  # but the docs are clear that it is efficient on numeric and date types, and is inefficient on string
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -146,7 +146,7 @@ module ElasticGraph
146
146
  #
147
147
  # @note Be careful about defining non-nullable fields. Changing a field’s type from non-nullable (e.g. `Int!`) to nullable (e.g.
148
148
  # `Int`) is a breaking change for clients. Making a field non-nullable may also prevent you from applying permissioning to a field
149
- # via an AuthZ layer (as such a layer would have no way to force a field value to `null` when for a client denied field access).
149
+ # via an AuthZ layer (as such a layer would have no way to force a field value to `null` for a client denied field access).
150
150
  # Therefore, we recommend limiting your use of `!` to only a few situations such as defining a type’s primary key (e.g.
151
151
  # `t.field "id", "ID!"`) or defining a list field (e.g. `t.field "authors", "[String!]!"`) since empty lists already provide a
152
152
  # "no data" representation. You can still configure the ElasticGraph indexer to require a non-null value for a field using
@@ -478,10 +478,12 @@ module ElasticGraph
478
478
  # @private
479
479
  def to_indexing_field_type
480
480
  Indexing::FieldType::Object.new(
481
+ schema_def_state: schema_def_state,
481
482
  type_name: name,
482
483
  subfields: indexing_fields_by_name_in_index.values.map(&:to_indexing_field).compact,
483
484
  mapping_options: mapping_options,
484
- json_schema_options: json_schema_options
485
+ json_schema_options: json_schema_options,
486
+ doc_comment: doc_comment
485
487
  )
486
488
  end
487
489
 
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -52,7 +52,9 @@ module ElasticGraph
52
52
  :type_refs_by_name,
53
53
  :output,
54
54
  :type_namer,
55
- :enum_value_namer
55
+ :enum_value_namer,
56
+ :allow_omitted_json_schema_fields,
57
+ :allow_extra_json_schema_fields
56
58
  )
57
59
  include Mixins::HasReadableToSAndInspect.new
58
60
 
@@ -99,7 +101,9 @@ module ElasticGraph
99
101
  name_overrides: type_name_overrides
100
102
  ),
101
103
  enum_value_namer: SchemaElements::EnumValueNamer.new(enum_value_overrides_by_type),
102
- output: output
104
+ output: output,
105
+ allow_omitted_json_schema_fields: false,
106
+ allow_extra_json_schema_fields: true
103
107
  )
104
108
  end
105
109
 
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -85,7 +85,7 @@ module ElasticGraph
85
85
  # Reloading the schema artifacts takes extra time that we don't usually want to spend (so it's opt-in)
86
86
  # but it can be useful in some cases because there is a bit of extra pruning/validation that it applies.
87
87
  tmp_dir = ::Dir.mktmpdir
88
- artifacts_manager = SchemaDefinition::SchemaArtifactManager.new(
88
+ artifacts_manager = api.factory.new_schema_artifact_manager(
89
89
  schema_definition_results: api.results,
90
90
  schema_artifacts_directory: tmp_dir,
91
91
  enforce_json_schema_version: false,
@@ -108,7 +108,6 @@ module ElasticGraph
108
108
  type_def_keyword = '^(type|input|enum|union|interface|scalar|directive)\b'
109
109
  # capture from the start of the type definition for `type` until the next type definition (or `\z` for end of string)
110
110
  type_extraction_regex = /(#{DOC_COMMENTS}?#{type_def_keyword} #{type}\b.*?)(?:(?:#{DOC_COMMENTS}?#{type_def_keyword})|\z)/m
111
- # type_defs = sdl.scan(type_extraction_regex).map(&:first)
112
111
  type_defs = sdl.scan(type_extraction_regex).map { |match| [match].flatten.first }
113
112
 
114
113
  if type_defs.size >= 2