lib/rom/plugins/relation/sql/instrumentation.rb in rom-sql-2.2.0 vs lib/rom/plugins/relation/sql/instrumentation.rb in rom-sql-2.2.1
- old
+ new
@@ -1,23 +1,86 @@
-require 'rom/plugins/relation/instrumentation'
-
module ROM
module Plugins
module Relation
module SQL
- # @api private
+ # Instrumentation for relations and commands
+ #
+ # This plugin allows configuring a notification system, that will be used
+ # to instrument interactions with databases, it's based on an abstract API
+ # so it should work with any instrumentation object that provides
+ # `instrument(identifier, payload)` method.
+ #
+ # By default, instrumentation is triggered with following arguments:
+ # - `identifier` is set to `:sql`
+ # - `payload` is set to a hash with following keys:
+ # - `:name` database type, ie `:sqlite`, `:postgresql` etc.
+ # - `:query` a string with an SQL statement that was executed
+ #
+ # @example configuring notifications
+ # config = ROM::Configuration.new(:sqlite, 'sqlite::memory')
+ #
+ # config.plugin(:sql, relations: :instrumentation) do |c|
+ # c.notifications = MyNotifications.new
+ # end
+ #
+ # @api public
module Instrumentation
- def self.included(klass)
- super
+ extend Notifications::Listener
- klass.class_eval do
- include ROM::Plugins::Relation::Instrumentation
+ subscribe('configuration.relations.registry.created') do |event|
+ registry = event[:registry]
- # @api private
- def notification_payload(relation)
- super.merge(query: relation.dataset.sql)
+ relations = registry.select { |_, r| r.adapter == :sql && r.respond_to?(:notifications) }.to_h
+ db_notifications = relations.values.map { |r| [r.dataset.db, r.notifications] }.uniq.to_h
+
+ db_notifications.each do |db, notifications|
+ instrumenter = Instrumenter.new(db.database_type, notifications)
+ db.extend(instrumenter)
+ end
+ end
+
+ # This stateful module is used to extend database connection objects
+ # and monkey-patches `log_connection_yield` method, which unfortunately
+ # is the only way to provide instrumentation on the sequel side.
+ #
+ # @api private
+ class Instrumenter < Module
+ # @!attribute [r] name
+ # @return [Symbol] database type
+ attr_reader :name
+
+ # @!attribute [r] notifications
+ # @return [Object] any object that responds to `instrument`
+ attr_reader :notifications
+
+ # @api private
+ def initialize(name, notifications)
+ @name = name
+ @notifications = notifications
+ define_log_connection_yield
+ end
+
+ private
+
+ # @api private
+ def define_log_connection_yield
+ name = self.name
+ notifications = self.notifications
+
+ define_method(:log_connection_yield) do |*args, &block|
+ notifications.instrument(:sql, name: name, query: args[0]) do
+ super(*args, &block)
+ end
end
end
+ end
+
+ # Add `:notifications` option to a relation
+ #
+ # @api private
+ def self.included(klass)
+ super
+ klass.option :notifications
end
end
end
end
end