From db75acbcd816a600dbf60d32bf6811080178856f Mon Sep 17 00:00:00 2001
From: Maikel Linke <maikel@email.org.au>
Date: Fri, 1 Mar 2024 09:42:49 +1100
Subject: [PATCH] Add query summary matching

This has been copied from the openfoodnetwork repository and adapted
slightly. It also needed one little fix.

This approach is quite different to our previous one and I'm wondering
if we use `User` and `Create` instead of `users` and `insert` to stay on
the higher level of Active Record. But for now this is can be a simple
replacement for the openfoodnetwork spec helper and we can evolve it
from there.
---
 README.md                      | 10 +++++++-
 lib/rspec/sql.rb               |  8 ++++++
 lib/rspec/sql/query_summary.rb | 47 ++++++++++++++++++++++++++++++++++
 rspec-sql.gemspec              |  2 +-
 spec/lib/rspec/sql_spec.rb     |  9 +++++++
 5 files changed, 74 insertions(+), 2 deletions(-)
 create mode 100644 lib/rspec/sql/query_summary.rb

diff --git a/README.md b/README.md
index 45823dc..5e9b4c8 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ expect { nil }.to_not query_database
 expect { User.last }.to query_database 1
 expect { User.create }.to query_database 3.times
 
-# Assert specific queries:
+# Assert a specific query list:
 expect { User.last }.to query_database ["User Load"]
 
 expect { User.create!.update(name: "Jane") }.to query_database [
@@ -33,6 +33,14 @@ expect { User.create!.update(name: "Jane") }.to query_database [
   "User Update",
   "TRANSACTION",
 ]
+
+# Assert a specific query summary:
+expect { User.create!.update(name: "Jane") }.to query_database(
+  {
+    insert: { users: 1 },
+    update: { users: 1 },
+  }
+)
 ```
 
 ## Alternatives
diff --git a/lib/rspec/sql.rb b/lib/rspec/sql.rb
index b384bb6..4602e13 100644
--- a/lib/rspec/sql.rb
+++ b/lib/rspec/sql.rb
@@ -3,6 +3,8 @@
 require "active_support"
 require "rspec"
 
+require_relative "sql/query_summary"
+
 module RSpec
   module Sql; end
 
@@ -18,6 +20,8 @@ module Sql; end
         @queries.size == expected.size
       elsif expected.is_a?(Array)
         query_names == expected
+      elsif expected.is_a?(Hash)
+        query_summary == expected
       else
         raise "What are you expecting?"
       end
@@ -52,6 +56,10 @@ def query_descriptions
       @queries.map { |q| "#{q[:name]}  #{q[:sql]}" }
     end
 
+    def query_summary
+      Sql::QuerySummary.new(@queries).summary
+    end
+
     def scribe_queries(&)
       queries = []
 
diff --git a/lib/rspec/sql/query_summary.rb b/lib/rspec/sql/query_summary.rb
new file mode 100644
index 0000000..9d6d038
--- /dev/null
+++ b/lib/rspec/sql/query_summary.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module RSpec
+  module Sql
+    class QuerySummary
+      QUERY_TYPES = [:delete, :insert, :select, :update].freeze
+
+      attr_reader :summary
+
+      def initialize(queries)
+        @summary = {}
+        queries.each do |payload|
+          type = get_type(payload[:sql])
+          next if QUERY_TYPES.exclude?(type) || pg_query?(payload[:sql])
+
+          table = get_table(payload[:sql])
+          @summary[type] ||= {}
+          @summary[type][table] ||= 0
+          @summary[type][table] += 1
+        end
+      end
+
+      private
+
+      def get_table(sql)
+        sql_parts = sql.split
+        case get_type(sql)
+        when :insert
+          sql_parts[2]
+        when :update
+          sql_parts[1]
+        else
+          table_index = sql_parts.index("FROM")
+          sql_parts[table_index + 1]
+        end.gsub(/(\\|")/, "").to_sym
+      end
+
+      def get_type(sql)
+        sql.split[0].downcase.to_sym
+      end
+
+      def pg_query?(sql)
+        sql.include?("SELECT a.attname") || sql.include?("pg_attribute")
+      end
+    end
+  end
+end
diff --git a/rspec-sql.gemspec b/rspec-sql.gemspec
index 83fbd1b..59e0455 100644
--- a/rspec-sql.gemspec
+++ b/rspec-sql.gemspec
@@ -3,7 +3,7 @@ Gem::Specification.new do |s|
   s.version     = "0.0.0"
   s.summary     = "RSpec::Sql matcher"
   s.description = "RSpec matcher for database queries."
-  s.authors     = ["Maikel Linke"]
+  s.authors     = ["Maikel Linke", "Open Food Network contributors"]
   s.email       = "maikel@openfoodnetwork.org.au"
   s.files       = Dir["lib/**/*.rb"]
   s.homepage    = "https://github.com/openfoodfoundation/rspec-sql"
diff --git a/spec/lib/rspec/sql_spec.rb b/spec/lib/rspec/sql_spec.rb
index 7178458..ab1110c 100644
--- a/spec/lib/rspec/sql_spec.rb
+++ b/spec/lib/rspec/sql_spec.rb
@@ -57,4 +57,13 @@
       "TRANSACTION",
     ]
   end
+
+  it "expects a summary of queries" do
+    expect { User.create!.update(name: "Jane") }.to query_database(
+      {
+        insert: { users: 1 },
+        update: { users: 1 },
+      }
+    )
+  end
 end