deimos-ruby 2.6.0.pre.beta1 → 2.6.0

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -2
  3. data/README.md +45 -20
  4. data/deimos-ruby.gemspec +0 -1
  5. data/docs/CONFIGURATION.md +5 -11
  6. data/lib/deimos/active_record_consume/batch_consumption.rb +128 -31
  7. data/lib/deimos/active_record_consume/batch_record.rb +3 -0
  8. data/lib/deimos/active_record_consume/message_consumption.rb +2 -2
  9. data/lib/deimos/active_record_consumer.rb +6 -1
  10. data/lib/deimos/active_record_producer.rb +1 -1
  11. data/lib/deimos/config/configuration.rb +11 -34
  12. data/lib/deimos/exceptions.rb +17 -0
  13. data/lib/deimos/ext/consumer_route.rb +3 -1
  14. data/lib/deimos/ext/producer_middleware.rb +2 -2
  15. data/lib/deimos/logging.rb +0 -9
  16. data/lib/deimos/producer.rb +5 -5
  17. data/lib/deimos/schema_backends/avro_base.rb +36 -7
  18. data/lib/deimos/schema_class/base.rb +62 -0
  19. data/lib/deimos/schema_class/enum.rb +49 -0
  20. data/lib/deimos/schema_class/record.rb +100 -0
  21. data/lib/deimos/transcoder.rb +6 -6
  22. data/lib/deimos/utils/deadlock_retry.rb +16 -2
  23. data/lib/deimos/utils/schema_class.rb +47 -14
  24. data/lib/deimos/version.rb +1 -1
  25. data/lib/deimos.rb +4 -3
  26. data/lib/generators/deimos/schema_class/templates/schema_class.rb.tt +8 -0
  27. data/lib/generators/deimos/schema_class/templates/schema_enum.rb.tt +13 -0
  28. data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +102 -0
  29. data/lib/generators/deimos/schema_class_generator.rb +369 -0
  30. data/lib/tasks/deimos.rake +4 -16
  31. data/regenerate_test_schema_classes.rb +3 -12
  32. data/spec/active_record_batch_consumer_association_spec.rb +1 -1
  33. data/spec/active_record_batch_consumer_spec.rb +286 -1
  34. data/spec/active_record_consume/batch_consumption_spec.rb +1 -1
  35. data/spec/active_record_consumer_spec.rb +3 -3
  36. data/spec/active_record_producer_spec.rb +1 -1
  37. data/spec/batch_consumer_spec.rb +2 -2
  38. data/spec/consumer_spec.rb +5 -5
  39. data/spec/generators/schema_class/my_schema_spec.rb +16 -0
  40. data/spec/generators/schema_class/my_schema_with_circular_reference_spec.rb +98 -0
  41. data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +237 -0
  42. data/spec/generators/schema_class_generator_spec.rb +283 -0
  43. data/spec/schemas/my_namespace/generated.rb +4 -4
  44. data/spec/schemas/my_namespace/my_long_namespace_schema.rb +2 -2
  45. data/spec/schemas/my_namespace/my_nested_schema.rb +3 -3
  46. data/spec/schemas/my_namespace/my_schema.rb +2 -2
  47. data/spec/schemas/my_namespace/my_schema_compound_key.rb +2 -2
  48. data/spec/schemas/my_namespace/my_schema_id_key.rb +2 -2
  49. data/spec/schemas/my_namespace/my_schema_key.rb +2 -2
  50. data/spec/schemas/my_namespace/my_schema_with_boolean.rb +2 -2
  51. data/spec/schemas/my_namespace/my_schema_with_circular_reference.rb +3 -3
  52. data/spec/schemas/my_namespace/my_schema_with_complex_type.rb +6 -6
  53. data/spec/schemas/my_namespace/my_schema_with_date_time.rb +2 -2
  54. data/spec/schemas/my_namespace/my_schema_with_id.rb +2 -2
  55. data/spec/schemas/my_namespace/my_schema_with_title.rb +2 -2
  56. data/spec/schemas/my_namespace/my_schema_with_union_type.rb +6 -6
  57. data/spec/schemas/my_namespace/my_schema_with_unique_id.rb +2 -2
  58. data/spec/schemas/my_namespace/my_updated_schema.rb +1 -1
  59. data/spec/schemas/my_namespace/request/create_topic.rb +2 -2
  60. data/spec/schemas/my_namespace/request/index.rb +2 -2
  61. data/spec/schemas/my_namespace/request/update_request.rb +2 -2
  62. data/spec/schemas/my_namespace/response/create_topic.rb +2 -2
  63. data/spec/schemas/my_namespace/response/index.rb +2 -2
  64. data/spec/schemas/my_namespace/response/update_response.rb +2 -2
  65. data/spec/schemas/my_namespace/wibble.rb +2 -2
  66. data/spec/schemas/my_namespace/widget.rb +2 -2
  67. data/spec/schemas/my_namespace/widget_the_second.rb +2 -2
  68. data/spec/schemas/my_namespace/widget_the_third.rb +2 -2
  69. data/spec/snapshots/consumers-no-nest.snap +1740 -0
  70. data/spec/snapshots/consumers.snap +1720 -0
  71. data/spec/snapshots/consumers_and_producers-no-nest.snap +1740 -0
  72. data/spec/snapshots/consumers_and_producers.snap +1720 -0
  73. data/spec/snapshots/consumers_circular-no-nest.snap +1740 -0
  74. data/spec/snapshots/consumers_circular.snap +1720 -0
  75. data/spec/snapshots/consumers_complex_types-no-nest.snap +1740 -0
  76. data/spec/snapshots/consumers_complex_types.snap +1720 -0
  77. data/spec/snapshots/consumers_nested-no-nest.snap +1740 -0
  78. data/spec/snapshots/consumers_nested.snap +1720 -0
  79. data/spec/snapshots/namespace_folders.snap +1800 -0
  80. data/spec/snapshots/namespace_map.snap +1800 -0
  81. data/spec/snapshots/producers_with_key-no-nest.snap +1740 -0
  82. data/spec/snapshots/producers_with_key.snap +1720 -0
  83. data/spec/spec_helper.rb +2 -2
  84. data/spec/utils/db_poller_spec.rb +1 -1
  85. data/spec/utils/deadlock_retry_spec.rb +57 -0
  86. metadata +27 -18
  87. data/lib/deimos/schema_class.rb +0 -72
  88. data/spec/schema_class_spec.rb +0 -65
data/spec/spec_helper.rb CHANGED
@@ -223,13 +223,13 @@ RSpec.configure do |config|
223
223
  set_karafka_config(:reraise_errors, true)
224
224
  Deimos.configure do |deimos_config|
225
225
  deimos_config.producers.backend = :kafka
226
- deimos_config.avrogen.nest_child_schemas = true
226
+ deimos_config.schema.nest_child_schemas = true
227
227
  deimos_config.schema.path = File.join(File.expand_path(__dir__), 'schemas')
228
228
  deimos_config.schema.registry_url = ENV['SCHEMA_REGISTRY'] || 'http://localhost:8081'
229
229
  deimos_config.logger = Logger.new(File::NULL)
230
230
  deimos_config.logger.level = Logger::INFO
231
231
  deimos_config.schema.backend = :avro_validation
232
- deimos_config.avrogen.generated_class_path = 'spec/schemas'
232
+ deimos_config.schema.generated_class_path = 'spec/schemas'
233
233
  end
234
234
  end
235
235
 
@@ -316,7 +316,7 @@ each_db_config(Deimos::Utils::DbPoller::Base) do
316
316
  m.call(*args)
317
317
  expect(info.reload.last_sent.in_time_zone).to eq(time_value(mins: -61, secs: 32))
318
318
  expect(info.last_sent_id).to eq(widgets[1].id)
319
- end
319
+ end
320
320
  expect(poller).to receive(:process_and_touch_info).ordered.
321
321
  with([widgets[2], widgets[3], widgets[4]], anything).and_call_original
322
322
  expect(poller).to receive(:process_and_touch_info).ordered.
@@ -7,6 +7,43 @@ RSpec.describe Deimos::Utils::DeadlockRetry do
7
7
  allow(described_class).to receive(:sleep)
8
8
  end
9
9
 
10
+ # `deadlock?` matches against the exception message, so the examples below are literal
11
+ # fragments as each engine emits them, padded with surrounding text to pin substring rather
12
+ # than equality matching. No database is exercised here - these are hand-built exceptions, so
13
+ # they verify the predicate agrees with DEADLOCK_MESSAGES, not that any engine really produces
14
+ # those strings. See the `each_db_config` specs for tests that run against real engines.
15
+ describe '.deadlock?' do
16
+ it 'should match MySQL deadlocks and lock wait timeouts' do
17
+ expect(described_class).to be_deadlock(
18
+ ActiveRecord::Deadlocked.new('Mysql2::Error: Deadlock found when trying to get lock')
19
+ )
20
+ expect(described_class).to be_deadlock(
21
+ ActiveRecord::StatementInvalid.new('Lock wait timeout exceeded; try restarting')
22
+ )
23
+ end
24
+
25
+ it 'should match Postgres deadlock detection' do
26
+ expect(described_class).to be_deadlock(
27
+ ActiveRecord::Deadlocked.new('PG::TRDeadlockDetected: ERROR: deadlock detected')
28
+ )
29
+ end
30
+
31
+ it 'should not match a database error whose message is not a known deadlock' do
32
+ expect(described_class).not_to be_deadlock(ActiveRecord::StatementInvalid.new('Oops!!'))
33
+ end
34
+
35
+ it 'should not match a non-database error that happens to mention a deadlock' do
36
+ expect(described_class).not_to be_deadlock(StandardError.new('deadlock detected'))
37
+ end
38
+
39
+ it 'should not match validation failures' do
40
+ # Batch consumption depends on this: if a RecordInvalid looked like a deadlock, the guard
41
+ # in `save_record_list` would reraise it and the individual fallback would never fire for
42
+ # the poison-record case it exists to handle.
43
+ expect(described_class).not_to be_deadlock(ActiveRecord::RecordInvalid.new)
44
+ end
45
+ end
46
+
10
47
  describe 'deadlock handling' do
11
48
  let(:batch) { [{ key: 1, payload: { test_id: 'abc', some_int: 3 } }] }
12
49
 
@@ -55,6 +92,26 @@ RSpec.describe Deimos::Utils::DeadlockRetry do
55
92
  expect(Widget.all).to contain_exactly(have_attributes(test_id: 'first'), have_attributes(test_id: 'second'))
56
93
  end
57
94
 
95
+ it 'should coerce a scalar tag to an array when reporting metrics' do
96
+ # Regression: mass_updater passes get_tag('topic') (a String) as tags.
97
+ # dogstatsd calls tags.to_a, which raises NoMethodError on a String and
98
+ # would defeat the retry mechanism on the first deadlock.
99
+ metrics = instance_double(Deimos::Metrics::Provider)
100
+ allow(Deimos.config).to receive(:metrics).and_return(metrics)
101
+ expect(metrics).to receive(:increment).with('deadlock', tags: ['Flyers.FlyerItem']).twice
102
+
103
+ expect(Widget).
104
+ to receive(:create).
105
+ and_raise(ActiveRecord::Deadlocked.new('Lock wait timeout exceeded')).
106
+ exactly(3).times
107
+
108
+ expect {
109
+ described_class.wrap('Flyers.FlyerItem') do
110
+ Widget.create(test_id: 'abc')
111
+ end
112
+ }.to raise_error(ActiveRecord::Deadlocked)
113
+ end
114
+
58
115
  it 'should not retry non-deadlock exceptions' do
59
116
  expect(Widget).
60
117
  to receive(:create).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deimos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.0.pre.beta1
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
@@ -9,20 +9,6 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: avro-gen-ruby
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: '0'
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: benchmark
28
14
  requirement: !ruby/object:Gem::Requirement
@@ -550,7 +536,9 @@ files:
550
536
  - lib/deimos/schema_backends/proto_base.rb
551
537
  - lib/deimos/schema_backends/proto_local.rb
552
538
  - lib/deimos/schema_backends/proto_schema_registry.rb
553
- - lib/deimos/schema_class.rb
539
+ - lib/deimos/schema_class/base.rb
540
+ - lib/deimos/schema_class/enum.rb
541
+ - lib/deimos/schema_class/record.rb
554
542
  - lib/deimos/shared_config.rb
555
543
  - lib/deimos/test_helpers.rb
556
544
  - lib/deimos/tracing/datadog.rb
@@ -576,6 +564,10 @@ files:
576
564
  - lib/generators/deimos/outbox_backend/templates/migration
577
565
  - lib/generators/deimos/outbox_backend/templates/rails3_migration
578
566
  - lib/generators/deimos/outbox_backend_generator.rb
567
+ - lib/generators/deimos/schema_class/templates/schema_class.rb.tt
568
+ - lib/generators/deimos/schema_class/templates/schema_enum.rb.tt
569
+ - lib/generators/deimos/schema_class/templates/schema_record.rb.tt
570
+ - lib/generators/deimos/schema_class_generator.rb
579
571
  - lib/generators/deimos/v2/templates/karafka.rb.tt
580
572
  - lib/generators/deimos/v2_generator.rb
581
573
  - lib/tasks/deimos.rake
@@ -603,6 +595,10 @@ files:
603
595
  - spec/gen/sample/v1/sample_key_pb.rb
604
596
  - spec/gen/sample/v1/sample_pb.rb
605
597
  - spec/generators/active_record_generator_spec.rb
598
+ - spec/generators/schema_class/my_schema_spec.rb
599
+ - spec/generators/schema_class/my_schema_with_circular_reference_spec.rb
600
+ - spec/generators/schema_class/my_schema_with_complex_types_spec.rb
601
+ - spec/generators/schema_class_generator_spec.rb
606
602
  - spec/handlers/my_batch_consumer.rb
607
603
  - spec/handlers/my_consumer.rb
608
604
  - spec/kafka_source_spec.rb
@@ -622,7 +618,6 @@ files:
622
618
  - spec/schema_backends/avro_validation_spec.rb
623
619
  - spec/schema_backends/base_spec.rb
624
620
  - spec/schema_backends/proto_schema_registry_spec.rb
625
- - spec/schema_class_spec.rb
626
621
  - spec/schemas/com/my-namespace/Generated.avsc
627
622
  - spec/schemas/com/my-namespace/MyNestedSchema.avsc
628
623
  - spec/schemas/com/my-namespace/MySchema.avsc
@@ -674,6 +669,20 @@ files:
674
669
  - spec/schemas/my_namespace/widget.rb
675
670
  - spec/schemas/my_namespace/widget_the_second.rb
676
671
  - spec/schemas/my_namespace/widget_the_third.rb
672
+ - spec/snapshots/consumers-no-nest.snap
673
+ - spec/snapshots/consumers.snap
674
+ - spec/snapshots/consumers_and_producers-no-nest.snap
675
+ - spec/snapshots/consumers_and_producers.snap
676
+ - spec/snapshots/consumers_circular-no-nest.snap
677
+ - spec/snapshots/consumers_circular.snap
678
+ - spec/snapshots/consumers_complex_types-no-nest.snap
679
+ - spec/snapshots/consumers_complex_types.snap
680
+ - spec/snapshots/consumers_nested-no-nest.snap
681
+ - spec/snapshots/consumers_nested.snap
682
+ - spec/snapshots/namespace_folders.snap
683
+ - spec/snapshots/namespace_map.snap
684
+ - spec/snapshots/producers_with_key-no-nest.snap
685
+ - spec/snapshots/producers_with_key.snap
677
686
  - spec/spec_helper.rb
678
687
  - spec/test_helpers_spec.rb
679
688
  - spec/utils/db_poller_spec.rb
@@ -702,7 +711,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
702
711
  - !ruby/object:Gem::Version
703
712
  version: '0'
704
713
  requirements: []
705
- rubygems_version: 4.0.10
714
+ rubygems_version: 4.0.16
706
715
  specification_version: 4
707
716
  summary: Kafka libraries for Ruby.
708
717
  test_files: []
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'avro_gen'
4
-
5
- module Deimos
6
- # Backwards-compatible shim. Schema class generation moved to the avro-gen-ruby
7
- # gem (namespace AvroGen). Previously-generated classes that reference
8
- # `Deimos::SchemaClass::Record` / `Enum` / `Base` still resolve here, with a
9
- # one-time deprecation warning, via const_missing.
10
- module SchemaClass
11
- DEPRECATED_CONSTANTS = {
12
- Base: AvroGen::SchemaClass::Base,
13
- Record: AvroGen::SchemaClass::Record,
14
- Enum: AvroGen::SchemaClass::Enum
15
- }.freeze
16
-
17
- # The generation settings that live under Deimos.config.avrogen and are
18
- # forwarded to AvroGen.config (same names on both sides).
19
- GENERATION_SETTINGS = %i(
20
- generated_class_path
21
- nest_child_schemas
22
- use_full_namespace
23
- schema_namespace_map
24
- ).freeze
25
-
26
- # @param name [Symbol]
27
- def self.const_missing(name)
28
- klass = DEPRECATED_CONSTANTS[name]
29
- return super unless klass
30
-
31
- Deimos::Logging.deprecate(
32
- "Deimos::SchemaClass::#{name} is deprecated; use AvroGen::SchemaClass::#{name} instead. " \
33
- 'Run `rake avro:upgrade` to update generated classes.'
34
- )
35
- # Define the constant so the warning is only emitted once.
36
- const_set(name, klass)
37
- klass
38
- end
39
-
40
- # Mirror the Deimos generation settings onto AvroGen.config. Deimos.config is
41
- # the source of truth within Deimos, so this runs on every configure (keeping
42
- # a reset in sync). The settings live under Deimos.config.avrogen; the legacy
43
- # Deimos.config.schema.* equivalents still work but emit a deprecation warning.
44
- # Standalone AvroGen users never trigger this and set AvroGen.config directly.
45
- # @!visibility private
46
- def self.sync_config!
47
- # schema.path is shared with the Avro backends, so it stays under `schema`.
48
- AvroGen.config.schema_path = Deimos.config.schema.path
49
- # Refresh AvroGen's cached schema stores on (re)configuration so they don't
50
- # serve stale schemas after the schema path or files change.
51
- AvroGen::SchemaValidator.clear_store_cache!
52
-
53
- GENERATION_SETTINGS.each do |key|
54
- AvroGen.config.send("#{key}=", generation_setting(key))
55
- end
56
- end
57
-
58
- # Resolve a generation setting, preferring the legacy (deprecated)
59
- # Deimos.config.schema.* location when it was explicitly set.
60
- # @!visibility private
61
- def self.generation_setting(key)
62
- if Deimos.config.schema.default_value?(key)
63
- Deimos.config.avrogen.send(key)
64
- else
65
- Deimos::Logging.deprecate(
66
- "Deimos.config.schema.#{key} is deprecated; use Deimos.config.avrogen.#{key} instead."
67
- )
68
- Deimos.config.schema.send(key)
69
- end
70
- end
71
- end
72
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Schema class generation moved to the avro-gen-ruby gem. These specs cover the
4
- # backwards-compatible shims that keep the old Deimos constants working for
5
- # users who haven't yet run `rake avro:upgrade`.
6
- RSpec.describe Deimos::SchemaClass do
7
- describe 'deprecated constants' do
8
- it 'resolves to the AvroGen equivalents' do
9
- expect(Deimos::SchemaClass::Base).to be(AvroGen::SchemaClass::Base)
10
- expect(Deimos::SchemaClass::Record).to be(AvroGen::SchemaClass::Record)
11
- expect(Deimos::SchemaClass::Enum).to be(AvroGen::SchemaClass::Enum)
12
- end
13
-
14
- it 'emits a deprecation warning when an old constant is referenced' do
15
- allow(Deimos::Logging).to receive(:deprecate)
16
- # Stub const_set so re-resolving doesn't emit an "already initialized" warning.
17
- allow(described_class).to receive(:const_set)
18
-
19
- expect(described_class.const_missing(:Record)).to be(AvroGen::SchemaClass::Record)
20
- expect(Deimos::Logging).to have_received(:deprecate).
21
- with(a_string_matching(/Deimos::SchemaClass::Record is deprecated/))
22
- end
23
-
24
- it 'raises NameError for genuinely unknown constants' do
25
- expect { Deimos::SchemaClass::NotARealClass }.to raise_error(NameError)
26
- end
27
- end
28
-
29
- describe Deimos::Utils::SchemaClass do
30
- it 'delegates to AvroGen::SchemaClass with a deprecation warning' do
31
- allow(Deimos::Logging).to receive(:deprecate)
32
-
33
- expect(described_class.modules_for('com.my-namespace')).
34
- to eq(AvroGen::SchemaClass.modules_for('com.my-namespace'))
35
- expect(Deimos::Logging).to have_received(:deprecate).
36
- with(a_string_matching(/Deimos::Utils::SchemaClass\.modules_for is deprecated/))
37
- end
38
-
39
- it 'still raises NoMethodError for genuinely unknown methods' do
40
- expect { described_class.not_a_real_method }.to raise_error(NoMethodError)
41
- end
42
- end
43
-
44
- describe 'generation config' do
45
- it 'forwards Deimos.config.avrogen settings to AvroGen.config' do
46
- Deimos.configure do |config|
47
- config.avrogen.generated_class_path = 'custom/classes'
48
- config.avrogen.use_full_namespace = true
49
- end
50
-
51
- expect(AvroGen.config.generated_class_path).to eq('custom/classes')
52
- expect(AvroGen.config.use_full_namespace).to be(true)
53
- end
54
-
55
- it 'still honors the legacy schema setting, deprecating it in favour of avrogen' do
56
- allow(Deimos::Logging).to receive(:deprecate)
57
-
58
- Deimos.configure { |config| config.schema.generated_class_path = 'legacy/classes' }
59
-
60
- expect(AvroGen.config.generated_class_path).to eq('legacy/classes')
61
- expect(Deimos::Logging).to have_received(:deprecate).
62
- with(a_string_matching(/Deimos\.config\.schema\.generated_class_path is deprecated.*avrogen/))
63
- end
64
- end
65
- end