@@ -51,7 +51,9 @@ module DB
5151 # :nodoc:
5252 getter connection
5353
54- def initialize (@connection : Connection )
54+ getter command : String
55+
56+ def initialize (@connection : Connection , @command : String )
5557 end
5658
5759 def release_connection
@@ -79,13 +81,17 @@ module DB
7981 end
8082
8183 private def perform_exec_and_release (args : Enumerable ) : ExecResult
82- return perform_exec(args)
84+ around_query_or_exec(args) do
85+ perform_exec(args)
86+ end
8387 ensure
8488 release_connection
8589 end
8690
8791 private def perform_query_with_rescue (args : Enumerable ) : ResultSet
88- return perform_query(args)
92+ around_query_or_exec(args) do
93+ perform_query(args)
94+ end
8995 rescue e : Exception
9096 # Release connection only when an exception occurs during the query
9197 # execution since we need the connection open while the ResultSet is open
@@ -95,5 +101,90 @@ module DB
95101
96102 protected abstract def perform_query (args : Enumerable ) : ResultSet
97103 protected abstract def perform_exec (args : Enumerable ) : ExecResult
104+
105+ # This method is called when executing the statement. Although it can be
106+ # redefined, it is recommended to use the `def_around_query_or_exec` macro
107+ # to be able to add new behaviors without loosing prior existing ones.
108+ protected def around_query_or_exec (args : Enumerable )
109+ yield
110+ end
111+
112+ # This macro allows injecting code to be run before and after the execution
113+ # of the request. It should return the yielded value. It must be called with 1
114+ # block argument that will be used to pass the `args : Enumerable`.
115+ #
116+ # ```
117+ # class DB::Statement
118+ # def_around_query_or_exec do |args|
119+ # # do something before query or exec
120+ # res = yield
121+ # # do something after query or exec
122+ # res
123+ # end
124+ # end
125+ # ```
126+ macro def_around_query_or_exec (& block )
127+ protected def around_query_or_exec (% args : Enumerable )
128+ previous_def do
129+ {% if block.args.size != 1 % }
130+ {% raise " Wrong number of block arguments (given #{ block.args.size } , expected: 1)" % }
131+ {% end % }
132+
133+ {{ block.args.first.id }} = % args
134+ {{ block.body }}
135+ end
136+ end
137+ end
138+
139+ def_around_query_or_exec do |args |
140+ emit_log(args)
141+ yield
142+ end
143+
144+ protected def emit_log (args : Enumerable )
145+ Log .debug & .emit(" Executing query" , query: command, args: MetadataValueConverter .arg_to_log(args))
146+ end
147+ end
148+
149+ # This module converts DB supported values to `::Log::Metadata::Value`
150+ #
151+ # ### Note to implementors
152+ #
153+ # If the driver defines custom types to be used as arguments the default behavior
154+ # will be converting the value via `#to_s`. Otherwise you can define overloads to
155+ # change this behaviour.
156+ #
157+ # ```
158+ # module DB::MetadataValueConverter
159+ # def self.arg_to_log(arg : PG::Geo::Point)
160+ # ::Log::Metadata::Value.new("(#{arg.x}, #{arg.y})::point")
161+ # end
162+ # end
163+ # ```
164+ module MetadataValueConverter
165+ # Returns *arg* encoded as a `::Log::Metadata::Value`.
166+ def self.arg_to_log (arg ) : ::Log ::Metadata ::Value
167+ ::Log ::Metadata ::Value .new(arg.to_s)
168+ end
169+
170+ # :ditto:
171+ def self.arg_to_log (arg : Enumerable ) : ::Log ::Metadata ::Value
172+ ::Log ::Metadata ::Value .new(arg.to_a.map { |a | arg_to_log(a).as(::Log ::Metadata ::Value ) })
173+ end
174+
175+ # :ditto:
176+ def self.arg_to_log (arg : Int ) : ::Log ::Metadata ::Value
177+ ::Log ::Metadata ::Value .new(arg.to_i64)
178+ end
179+
180+ # :ditto:
181+ def self.arg_to_log (arg : UInt64 ) : ::Log ::Metadata ::Value
182+ ::Log ::Metadata ::Value .new(arg.to_s)
183+ end
184+
185+ # :ditto:
186+ def self.arg_to_log (arg : Nil | Bool | Int32 | Int64 | Float32 | Float64 | String | Time ) : ::Log ::Metadata ::Value
187+ ::Log ::Metadata ::Value .new(arg)
188+ end
98189 end
99190end
0 commit comments