{"record":{"id":"9563fa733f3e6b60","repo":"ankane/searchkick","slug":"need-primary-key-to-load-records","errorCode":null,"errorMessage":"Need primary key to load records","messagePattern":"Need primary key to load records","errorType":"exception","errorClass":"Searchkick::Error","httpStatus":null,"severity":"error","filePath":"lib/searchkick.rb","lineNumber":289,"sourceCode":"        redis.with do |r|\n          yield r\n        end\n      else\n        yield redis\n      end\n    end\n  end\n\n  def self.warn(message)\n    super(\"[searchkick] WARNING: #{message}\")\n  end\n\n  # private\n  def self.load_records(relation, ids)\n    relation =\n      if relation.respond_to?(:primary_key)\n        primary_key = relation.primary_key\n        raise Error, \"Need primary key to load records\" if !primary_key\n\n        relation.where(primary_key => ids)\n      elsif relation.respond_to?(:queryable)\n        relation.queryable.for_ids(ids)\n      end\n\n    raise Error, \"Not sure how to load records\" if !relation\n\n    relation\n  end\n\n  # public (for reindexing conversions)\n  def self.load_model(class_name, allow_child: false)\n    model = class_name.safe_constantize\n    raise Error, \"Could not find class: #{class_name}\" unless model\n    if allow_child\n      unless model.respond_to?(:searchkick_klass)\n        raise Error, \"#{class_name} is not a searchkick model\"","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/ankane/searchkick/blob/93e901a75b11a25101668a616e006b158251b16e/lib/searchkick.rb#L271-L307","documentation":"For exact kNN on OpenSearch, Searchkick translates the distance metric into a knn_score script space_type and only recognizes cosine (cosinesimil), euclidean (l2), taxicab (l1), inner_product (innerproduct) and chebyshev (linf). Any other string raises ArgumentError 'Unknown distance: ...' at query-build time, before a request is sent.","triggerScenarios":"Product.search('*', knn: {field: :embedding, vector: vec, distance: 'dot_product', exact: true}) against OpenSearch; passing Elasticsearch-style names like 'l2_norm' or 'cosinesimil' instead of Searchkick's own names.","commonSituations":"Porting metric names from other libraries (Faiss uses ip/l2; Elasticsearch knn uses dot_product/cosine/l2_norm); typos or wrong casing such as 'Cosine'; using a metric supported only by the Elasticsearch branch of the code.","solutions":["Use one of Searchkick's names: 'cosine', 'euclidean', 'taxicab', 'inner_product' or 'chebyshev'","If you meant Elasticsearch's dot_product, use 'inner_product' and normalize your vectors beforehand","Check casing and whitespace - values are matched as exact lowercase strings"],"exampleFix":"# before\nProduct.search('*', knn: {field: :v, vector: vec, distance: 'dot_product', exact: true})\n# => ArgumentError: Unknown distance: dot_product\n\n# after\nProduct.search('*', knn: {field: :v, vector: vec, distance: 'inner_product', exact: true})","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"OPENSEARCH_EXACT_DISTANCES = %w[cosine euclidean taxicab inner_product chebyshev].freeze\n\ndef valid_opensearch_exact_distance?(value)\n  OPENSEARCH_EXACT_DISTANCES.include?(value)\nend\n\nraise ArgumentError, \"bad distance: #{d}\" unless valid_opensearch_exact_distance?(d)\nProduct.search('*', knn: {field: :v, vector: vec, distance: d, exact: true})","tryCatchPattern":"begin\n  Product.search('*', knn: opts)\nrescue ArgumentError => e\n  raise unless e.message.start_with?('Unknown distance')\n  opts[:distance] = 'cosine' # safe default\n  Product.search('*', knn: opts)\nend","preventionTips":["Keep a frozen constant of supported metric names per server flavor and validate user-provided metrics against it","Do not accept metric names directly from API consumers - map them through your own vocabulary","Add a unit test listing every metric your app passes to knn"],"tags":["searchkick","opensearch","knn","distance-metric","argumenterror"],"backgroundTag":"invalid-enum-value","analyzedSha":"93e901a75b11a25101668a616e006b158251b16e","analyzedAt":"2026-08-21T19:06:23.767Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}