File: sql_comments.rb

package info (click to toggle)
ruby-sequel 5.63.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 10,408 kB
  • sloc: ruby: 113,747; makefile: 3
file content (189 lines) | stat: -rw-r--r-- 6,985 bytes parent folder | download | duplicates (2)
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
# frozen-string-literal: true

module Sequel
  module Plugins
    # The sql_comments plugin will automatically use SQL comments on
    # queries for the model it is loaded into.  These comments will
    # show the related model, what type of method was called, and
    # the method name (or association name for queries to load
    # associations):
    #
    #   album = Album[1]
    #   # SELECT * FROM albums WHERE (id = 1) LIMIT 1
    #   # -- model:Album,method_type:class,method:[]
    #
    #   album.update(name: 'A')
    #   # UPDATE albums SET name = 'baz' WHERE (id = 1)
    #   # -- model:Album,method_type:instance,method:update
    #
    #   album.artist
    #   # SELECT * FROM artists WHERE (artists.id = 1)
    #   # -- model:Album,method_type:association_load,association:artist
    #
    #   Album.eager(:artists).all
    #   # SELECT * FROM albums
    #   # SELECT * FROM artists WHERE (artists.id IN (1))
    #   # -- model:Album,method_type:association_eager_load,association:artist
    #   
    #   Album.where(id: 1).delete
    #   # DELETE FROM albums WHERE (id = 1)
    #   # -- model:Album,method_type:dataset,method:delete
    #
    # This plugin automatically supports the class, instance, and dataset
    # methods are are supported by default in Sequel::Model.  To support
    # custom class, instance, and dataset methods, such as those added by
    # other plugins, you can use the appropriate <tt>sql_comments_*_methods</tt>
    # class method:
    #
    #   Album.sql_comments_class_methods :first_by_name # example from finder plugin, with :mod option
    #   Album.sql_comments_instance_methods :lazy_attribute_lookup # lazy_attributes plugin
    #   Album.sql_comments_dataset_methods :to_csv # csv_serializer plugin
    #
    # In order for the sql_comments plugin to work, the sql_comments
    # Database extension must be loaded into the model's database.
    #
    # Note that in order to make sure SQL comments are included, some
    # optimizations are disabled if this plugin is loaded.
    # 
    # Usage:
    #
    #   # Make all model subclasses support automatic SQL comments
    #   # (called before loading subclasses)
    #   Sequel::Model.plugin :sql_comments
    #
    #   # Make the Album class support automatic SQL comments
    #   Album.plugin :sql_comments
    module SqlComments
      # Define a method +meth+ on the given module +mod+ that will use automatic
      # SQL comments with the given model, method_type, and method.
      def self.def_sql_commend_method(mod, model, method_type, meth)
        mod.send(:define_method, meth) do |*a, &block|
          model.db.with_comments(:model=>model, :method_type=>method_type, :method=>meth) do
            super(*a, &block)
          end
        end
        # :nocov:
        mod.send(:ruby2_keywords, meth) if mod.respond_to?(:ruby2_keywords, true)
        # :nocov:
      end

      def self.configure(model)
        model.send(:reset_fast_pk_lookup_sql)
      end

      module ClassMethods
        # Use automatic SQL comments for the given class methods.
        def sql_comments_class_methods(*meths)
          _sql_comments_methods(singleton_class, :class, meths)
        end

        # Use automatic SQL comments for the given instance methods.
        def sql_comments_instance_methods(*meths)
          _sql_comments_methods(self, :instance, meths)
        end

        # Use automatic SQL comments for the given dataset methods.
        def sql_comments_dataset_methods(*meths)
          unless @_sql_comments_dataset_module
            dataset_module(@_sql_comments_dataset_module = Module.new)
          end
          _sql_comments_methods(@_sql_comments_dataset_module, :dataset, meths)
        end

        [:[], :create, :find, :find_or_create, :with_pk, :with_pk!].each do |meth|
          define_method(meth) do |*a, &block|
            db.with_comments(:model=>self, :method_type=>:class, :method=>meth) do
              super(*a, &block)
            end
          end
          # :nocov:
          ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
          # :nocov:
        end

        private

        # Don't optimize the fast PK lookups, as it uses static SQL that
        # won't support the SQL comments.
        def reset_fast_pk_lookup_sql
          @fast_pk_lookup_sql = @fast_instance_delete_sql = nil
        end

        # Define automatic SQL comment methods in +mod+ for each method in +meths+,
        # with the given +method_type+.
        def _sql_comments_methods(mod, method_type, meths)
          meths.each do |meth|
            SqlComments.def_sql_commend_method(mod, self, method_type, meth)
          end
        end
      end

      module InstanceMethods
        [:delete, :destroy, :lock!, :refresh, :save, :save_changes, :update, :update_fields].each do |meth|
          define_method(meth) do |*a, &block|
            t = Sequel.current
            return super(*a, &block) if (hash = Sequel.synchronize{db.comment_hashes[t]}) && hash[:model]

            db.with_comments(:model=>model, :method_type=>:instance, :method=>meth) do
              super(*a, &block)
            end
          end
          # :nocov:
          ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
          # :nocov:
        end

        private

        # Do not use a placeholder loader for associations.
        def _associated_object_loader(opts, dynamic_opts)
          nil
        end

        # Use SQL comments on normal association load queries, showing they are association loads.
        def _load_associated_objects(opts, dynamic_opts=OPTS)
          db.with_comments(:model=>model, :method_type=>:association_load, :association=>opts[:name]) do
            super
          end
        end
      end

      module DatasetMethods
        Dataset::ACTION_METHODS.each do |meth|
          define_method(meth) do |*a, &block|
            t = Sequel.current
            return super(*a, &block) if (hash = Sequel.synchronize{db.comment_hashes[t]}) && hash[:model]

            db.with_comments(:model=>model, :method_type=>:dataset, :method=>meth) do
              super(*a, &block)
            end
          end
          # :nocov:
          ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
          # :nocov:
        end

        private

        # Add the association name as part of the eager load data, so
        # perform_eager_load has access to it.
        def prepare_eager_load(a, reflections, eager_assoc)
          res = super
          
          reflections.each do |r|
            res[r[:eager_loader]][:association] = r[:name]
          end

          res
        end

        # Use SQL comments on eager load queries, showing they are eager loads.
        def perform_eager_load(loader, eo)
          db.with_comments(:model=>model, :method_type=>:association_eager_load, :method=>nil, :association=>eo[:association]) do
            super
          end
        end
      end
    end
  end
end