This file is indexed.

/usr/lib/ruby/vendor_ruby/sequel/plugins/identity_map.rb is in ruby-sequel 3.36.1-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
module Sequel
  module Plugins
    # The identity_map plugin allows the user to create temporary identity maps
    # via the with_identity_map method, which takes a block.  Inside the block,
    # objects have a 1-1 correspondence with rows in the database.
    #
    # For example, the following is true, and wouldn't be true if you weren't
    # using the identity map:
    #   Sequel::Model.with_identity_map do
    #     Album.filter{(id > 0) & (id < 2)}.first.object_id == Album.first(:id=>1).object_id
    #   end
    #
    # In addition to providing a 1-1 correspondence, the identity_map plugin
    # also provides a cached looked up of records in two cases:
    # * Model.[] (e.g. Album[1])
    # * Model.many_to_one accessor methods (e.g. album.artist)
    #
    # If the object you are looking up, using one of those two methods, is already
    # in the identity map, the record is returned without a database query being
    # issued.
    #
    # Identity maps are thread-local and only persist for the duration of the block,
    # so they should only be considered as a possible performance enhancer.
    #
    # The identity_map plugin is not compatible with the eager loading in the +rcte_tree+ plugin.
    #
    # Usage:
    #
    #   # Use an identity map that will affect all model classes (called before loading subclasses)
    #   Sequel::Model.plugin :identity_map
    #
    #   # Use an identity map just for the Album class
    #   Album.plugin :identity_map
    #   # would need to do Album.with_identity_map{} to use the identity map
    module IdentityMap
      module ClassMethods
        # Override the default :eager_loader option for many_*_many associations to work
        # with an identity_map.  If the :eager_graph association option is used, you'll probably have to use
        # :uniq=>true on the current association amd :cartesian_product_number=>2 on the association
        # mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
        # getting called multiple times for the same object.  If you do have duplicates and you use :eager_graph,
        # they'll probably be lost.  Making that work correctly would require changing a lot of the core
        # architecture, such as how graphing and eager graphing work.
        def associate(type, name, opts = {}, &block)
          if opts[:eager_loader]
            super
          elsif type == :many_to_many
            opts = super
            el = opts[:eager_loader] 
            model = self
            left_pk = opts[:left_primary_key]
            uses_lcks = opts[:uses_left_composite_keys]
            uses_rcks = opts[:uses_right_composite_keys]
            right = opts[:right_key]
            join_table = opts[:join_table]
            left = opts[:left_key]
            lcks = opts[:left_keys]
            left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
            opts[:eager_loader] = lambda do |eo|
              return el.call(eo) unless model.identity_map
              h = eo[:key_hash][left_pk]
              eo[:rows].each{|object| object.associations[name] = []}
              r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
              l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]

              # Replace the row proc to remove the left key alias before calling the previous row proc.
              # Associate the value of the left key alias with the associated object (through its object_id).
              # When loading the associated objects, lookup the left key alias value and associate the
              # associated objects to the main objects if the left key alias value matches the left primary key
              # value of the main object.
              # 
              # The deleting of the left key alias from the hash before calling the previous row proc
              # is necessary when an identity map is used, otherwise if the same associated object is returned more than
              # once for the association, it won't know which of current objects to associate it to.
              ds = opts.associated_class.inner_join(join_table, r + l)
              pr = ds.row_proc
              h2 = {}
              ds.row_proc = proc do |hash|
                hash_key = if uses_lcks
                  left_key_alias.map{|k| hash.delete(k)}
                else
                  hash.delete(left_key_alias)
                end
                obj = pr.call(hash)
                (h2[obj.object_id] ||= []) << hash_key
                obj
              end
              model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
                if hash_keys = h2.delete(assoc_record.object_id)
                  hash_keys.each do |hash_key|
                    if objects = h[hash_key]
                      objects.each{|object| object.associations[name].push(assoc_record)}
                    end
                  end
                end
              end
            end
            opts
          elsif type == :many_through_many
            opts = super
            el = opts[:eager_loader] 
            model = self
            left_pk = opts[:left_primary_key]
            left_key = opts[:left_key]
            uses_lcks = opts[:uses_left_composite_keys]
            left_keys = Array(left_key)
            left_key_alias = opts[:left_key_alias]
            opts[:eager_loader] = lambda do |eo|
              return el.call(eo) unless model.identity_map
              h = eo[:key_hash][left_pk]
              eo[:rows].each{|object| object.associations[name] = []}
              ds = opts.associated_class 
              opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
              ft = opts.final_reverse_edge
              conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]

              # See above comment in many_to_many eager_loader
              ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
              pr = ds.row_proc
              h2 = {}
              ds.row_proc = proc do |hash|
                hash_key = if uses_lcks
                  left_key_alias.map{|k| hash.delete(k)}
                else
                  hash.delete(left_key_alias)
                end
                obj = pr.call(hash)
                (h2[obj.object_id] ||= []) << hash_key
                obj
              end
              model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
                if hash_keys = h2.delete(assoc_record.object_id)
                  hash_keys.each do |hash_key|
                    if objects = h[hash_key]
                      objects.each{|object| object.associations[name].push(assoc_record)}
                    end
                  end
                end
              end
            end
            opts
          else
            super
          end
        end
          
        # Returns the current thread-local identity map.  Should be a hash if
        # there is an active identity map, and nil otherwise.
        def identity_map
          Thread.current[:sequel_identity_map]
        end

        # The identity map key for an object of the current class with the given pk.
        # May not always be correct for a class which uses STI.
        def identity_map_key(pk)
          "#{self}:#{pk ? Array(pk).join(',') : "nil:#{rand}"}"
        end

        # If the identity map is in use, check it for a current copy of the object.
        # If a copy does not exist, create a new object and add it to the identity map.
        # If a copy exists, add any values in the given row that aren't currently
        # in the object to the object's values.  This allows you to only request
        # certain fields in an initial query, make modifications to some of those
        # fields and request other, potentially overlapping fields in a new query,
        # and not have the second query override fields you modified.
        def call(row)
          return super unless idm = identity_map
          if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})]
            o.merge_db_update(row)
          else
            o = super
            idm[identity_map_key(o.pk)] = o
          end
          o
        end

        # Take a block and inside that block use an identity map to ensure a 1-1
        # correspondence of objects to the database row they represent.
        def with_identity_map
          return yield if identity_map
          begin
            self.identity_map = {}
            yield
          ensure
            self.identity_map = nil
          end
        end

        private

        # Set the thread local identity map to the given value.
        def identity_map=(v)
          Thread.current[:sequel_identity_map] = v
        end

        # Check the current identity map if it exists for the object with
        # the matching pk.  If one is found, return it, otherwise call super.
        def primary_key_lookup(pk)
          (idm = identity_map and o = idm[identity_map_key(pk)]) ? o : super
        end
      end

      module InstanceMethods
        # Remove instances from the identity map cache if they are deleted.
        def delete
          super
          if idm = model.identity_map
            idm.delete(model.identity_map_key(pk))
          end
          self
        end

        # Merge the current values into the values provided in the row, ensuring
        # that current values are not overridden by new values.
        def merge_db_update(row)
          @values = row.merge(@values)
        end

        private

        # The primary keys values of the associated object, given the foreign
        # key columns(s).
        def _associated_object_pk(fk)
          fk.is_a?(Array) ? fk.map{|c| send(c)} : send(fk)
        end

        # If the association is a many_to_one and it has a :key option and the
        # key option has a value and the association uses the primary key of
        # the associated class as the :primary_key option, check the identity
        # map for the associated object and return it if present.
        def _load_associated_object(opts, dynamic_opts)
          klass = opts.associated_class
          cache_lookup = opts.fetch(:idm_cache_lookup) do 
            opts[:idm_cache_lookup] = klass.respond_to?(:identity_map) &&
              opts[:type] == :many_to_one &&
              opts[:key] &&
              opts.primary_key == klass.primary_key
          end
          if cache_lookup &&
            !dynamic_opts[:callback] &&
            (idm = klass.identity_map) &&
            (o = idm[klass.identity_map_key(_associated_object_pk(opts[:key]))])
            o
          else
            super
          end
        end
      end
    end
  end
end