activeitem 0.0.15 → 0.0.16

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2706d94d7f88c19fdb9fc14d8a9408d1f8dc088a5eabf2e583d60351173b9870
4
- data.tar.gz: 41c8e00d8b5a45ec26b58123efc86437795e1921c59dc8c44f3a770727496702
3
+ metadata.gz: 6460db8a74ca7bcb5d28c0bfdb7c4a6c1eba5c5d68318245612dad78d1d1d282
4
+ data.tar.gz: 9fe2d9707efa0a41b74fa1c07c5c24453605baa5d24e3f1880c248c92c5a03b0
5
5
  SHA512:
6
- metadata.gz: 41689642db2d18371ae7f9c7987a72d001f3aa18d5e7bb3f0a7248100e5de2a8aa6cb039742057c176bfb6f9eeaa02a240847d50c64f6f02c54372da6c069020
7
- data.tar.gz: 5c1a35c85c0b6d641fd630681fccb4995637eb733c50eada9a197a1c2fd503cc62855c1ad14eb81e19ccf324e1da2941ab93cbf87b72e5116285b8b5e49ed7a8
6
+ metadata.gz: 61ad96dc3dd1a28bda5a9af288bd17e1eef4f11b450f08efae26c1ea8f8060819727dde8d855d562e0fbbf2e6012096e159952d44c0d22255abc2384d85d80a9
7
+ data.tar.gz: 741d0e3a257199f62373c16960bea2d90fe26e37e9f4dcb8516cfb049170caac8a237c90ef1ba17dde88b356218b12f7e5e7d5f4a9d96abee7f64225639762f8
@@ -547,7 +547,8 @@ module ActiveItem
547
547
  if effective_index && normalized_conditions.any? && normalized_conditions.values.first.is_a?(Array)
548
548
  index_config = resolved_model.indexes[effective_index] || {}
549
549
  ruby_partition_key = normalized_conditions.keys.first.to_s
550
- dynamo_partition_key = index_config[:partition_key]&.to_s || resolved_model.to_dynamo_key(ruby_partition_key)
550
+ raw_pk = index_config[:partition_key]&.to_s || ruby_partition_key
551
+ dynamo_partition_key = resolved_model.to_dynamo_key(raw_pk)
551
552
  return fanout_paginated_query(effective_index, normalized_conditions, index_config, dynamo_partition_key,
552
553
  normalized_conditions.values.first, cursor, per_page)
553
554
  end
@@ -559,6 +560,12 @@ module ActiveItem
559
560
  else
560
561
  paginated_scan_with_conditions(normalized_conditions, exclusive_start_key, per_page)
561
562
  end
563
+ rescue Aws::DynamoDB::Errors::ValidationException => e
564
+ raise unless e.message.include?('specified index')
565
+
566
+ warn "[ActiveItem] WARNING: Index not found on table '#{resolved_model.table_name}'. " \
567
+ 'Falling back to table scan. Create the index: belt setup tables && belt deploy'
568
+ paginated_scan_with_conditions(normalized_conditions, exclusive_start_key, per_page)
562
569
  rescue Aws::DynamoDB::Errors::AccessDeniedException => e
563
570
  raise ActiveItem::AccessDeniedError.new(model_name: resolved_model.name, table: resolved_model.table_name,
564
571
  operation: 'PaginatedQuery', original_error: e)
@@ -606,7 +613,8 @@ module ActiveItem
606
613
  partition_value = normalized_conditions.values.first
607
614
 
608
615
  index_config = resolved_model.indexes[idx_name] || {}
609
- dynamo_partition_key = index_config[:partition_key]&.to_s || resolved_model.to_dynamo_key(ruby_partition_key)
616
+ raw_pk = index_config[:partition_key]&.to_s || ruby_partition_key
617
+ dynamo_partition_key = resolved_model.to_dynamo_key(raw_pk)
610
618
 
611
619
  params = {
612
620
  table_name: resolved_model.table_name,
@@ -788,7 +796,7 @@ module ActiveItem
788
796
  effective_index = index_name || resolved_model.send(:detect_index_for_conditions, normalized_conditions)
789
797
 
790
798
  if effective_index && normalized_conditions.any?
791
- query_with_index_normalized(effective_index, normalized_conditions)
799
+ query_with_index_or_fallback(effective_index, normalized_conditions)
792
800
  else
793
801
  scan_with_conditions_normalized(normalized_conditions)
794
802
  end
@@ -838,6 +846,13 @@ module ActiveItem
838
846
  end
839
847
 
840
848
  limit_value ? [total, limit_value].min : total
849
+ rescue Aws::DynamoDB::Errors::ValidationException => e
850
+ raise unless e.message.include?('specified index')
851
+
852
+ warn "[ActiveItem] WARNING: Index not found on table '#{resolved_model.table_name}'. " \
853
+ 'Falling back to table scan for count. Create the index: belt setup tables && belt deploy'
854
+ # Retry without the index
855
+ count_via_scan(normalized_conditions)
841
856
  rescue Aws::DynamoDB::Errors::AccessDeniedException => e
842
857
  raise ActiveItem::AccessDeniedError.new(model_name: resolved_model.name, table: resolved_model.table_name,
843
858
  operation: 'Count', original_error: e)
@@ -848,7 +863,8 @@ module ActiveItem
848
863
  partition_value = normalized_conditions.values.first
849
864
 
850
865
  index_config = resolved_model.indexes[idx_name] || {}
851
- dynamo_partition_key = index_config[:partition_key]&.to_s || resolved_model.to_dynamo_key(ruby_partition_key)
866
+ raw_partition_key = index_config[:partition_key]&.to_s || ruby_partition_key
867
+ dynamo_partition_key = resolved_model.to_dynamo_key(raw_partition_key)
852
868
 
853
869
  params = {
854
870
  table_name: resolved_model.table_name,
@@ -911,6 +927,26 @@ module ActiveItem
911
927
  params
912
928
  end
913
929
 
930
+ # Fallback count via scan when an index is missing
931
+ def count_via_scan(normalized_conditions)
932
+ total = 0
933
+ exclusive_start_key = nil
934
+
935
+ loop do
936
+ params = build_count_scan_params(normalized_conditions)
937
+ params[:exclusive_start_key] = exclusive_start_key if exclusive_start_key
938
+ params[:limit] = limit_value if limit_value
939
+
940
+ response = resolved_model.dynamodb.scan(params)
941
+ total += response.count
942
+ exclusive_start_key = response.last_evaluated_key
943
+ break unless exclusive_start_key
944
+ break if limit_value && total >= limit_value
945
+ end
946
+
947
+ limit_value ? [total, limit_value].min : total
948
+ end
949
+
914
950
  # Build scan params for a count-only scan
915
951
  def build_count_scan_params(normalized_conditions)
916
952
  filter_parts = []
@@ -944,7 +980,8 @@ module ActiveItem
944
980
  partition_value = normalized_conditions.values.first
945
981
 
946
982
  index_config = resolved_model.indexes[idx_name] || {}
947
- dynamo_partition_key = index_config[:partition_key]&.to_s || resolved_model.to_dynamo_key(ruby_partition_key)
983
+ raw_pk = index_config[:partition_key]&.to_s || ruby_partition_key
984
+ dynamo_partition_key = resolved_model.to_dynamo_key(raw_pk)
948
985
 
949
986
  params = {
950
987
  table_name: resolved_model.table_name,
@@ -1082,6 +1119,18 @@ module ActiveItem
1082
1119
  foreign_key.to_s == attr_name ? nil : foreign_key.to_s
1083
1120
  end
1084
1121
 
1122
+ # Attempt a GSI query, falling back to a filtered scan if the index doesn't exist.
1123
+ # Logs a warning when falling back so the developer knows to create the index.
1124
+ def query_with_index_or_fallback(idx_name, normalized_conditions)
1125
+ query_with_index_normalized(idx_name, normalized_conditions)
1126
+ rescue Aws::DynamoDB::Errors::ValidationException => e
1127
+ raise unless e.message.include?('specified index')
1128
+
1129
+ warn "[ActiveItem] WARNING: Index '#{idx_name}' not found on table '#{resolved_model.table_name}'. " \
1130
+ 'Falling back to table scan. Create the index for better performance: belt setup tables && belt deploy'
1131
+ scan_with_conditions_normalized(normalized_conditions)
1132
+ end
1133
+
1085
1134
  def query_with_index_normalized(idx_name, normalized_conditions)
1086
1135
  # Get the partition key from conditions (Ruby snake_case)
1087
1136
  ruby_partition_key = normalized_conditions.keys.first.to_s
@@ -1090,7 +1139,9 @@ module ActiveItem
1090
1139
  # Get the actual DynamoDB partition key name from the index definition
1091
1140
  # The index definition stores the DynamoDB key name (which may be camelCase)
1092
1141
  index_config = resolved_model.indexes[idx_name] || {}
1093
- dynamo_partition_key = index_config[:partition_key]&.to_s || resolved_model.to_dynamo_key(ruby_partition_key)
1142
+ raw_partition_key = index_config[:partition_key]&.to_s || ruby_partition_key
1143
+ # Always convert to DynamoDB key format (camelCase) for the expression
1144
+ dynamo_partition_key = resolved_model.to_dynamo_key(raw_partition_key)
1094
1145
 
1095
1146
  return fanout_query(idx_name, normalized_conditions, index_config, dynamo_partition_key, partition_value) if partition_value.is_a?(Array)
1096
1147
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveItem
4
- VERSION = '0.0.15'
4
+ VERSION = '0.0.16'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeitem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.15
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis