Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Brandon Joyce committed Mar 18, 2016
0 parents commit 66de7a7
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
footprint.sql
2 changes: 2 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--format documentation
--color
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in sql_footprint.gemspec
gemspec
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SqlFootprint

This gem allows you to keep a "footprint" of the sql queries that your application runs.
It's like logging all the sql you're executing except that we remove all the value parameters
and dedupe similar queries. This footprint should be valuable in determining if changes you've
made will significantly change the way you're querying the database.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'sql_footprint'
```

And then execute:

$ bundle

## Usage

Typically, you would want to run this while you're running your specs.
For example w/ RSpec:
```
RSpec.configure do |config|
config.before(:suite) { SqlFootprint.start }
config.after(:suite) { SqlFootprint.stop }
end
```

After running your specs you'll find a 'footprint.sql' file in your project.
Adding this to your Git repository can be very useful so you can include the diff of the footprint
as part of your code review.
6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
14 changes: 14 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env ruby

require "bundler/setup"
require "sql_footprint"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require "irb"
IRB.start
7 changes: 7 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

bundle install

# Do any other automated setup that you need to do here
81 changes: 81 additions & 0 deletions lib/sql_footprint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
require "sql_footprint/version"

module SqlFootprint
def self.start
@original_logger = ActiveRecord::Base.logger
@logger = Logger.new
ActiveRecord::Base.logger = @logger
end

def self.stop
ActiveRecord::Base.logger = @original_logger
File.open('footprint.sql', 'w') do |f|
@logger.logs.each do |log|
f.puts log
end
end
end

class Logger
def initialize
@logs = []
end

attr_reader :logs

def error param
fail param
end

def debug text
if sql? text
sql = format_sql(text)
logs << sql if !logs.include?(sql)
logs.sort!
end
end

def sql? text
/SQL/.match(text) ||
/Load\s\(/.match(text)
end

def format_sql text
strip_values(text).split("\e\[0m")
.select { |t| !t.include?('SQL') }
.select { |t| !/Load\s\(/.match(t) }
.first
.gsub(/\e\[1m/, '')
.strip
end

def strip_values text
text = text.gsub(/\[\[.*\]\]/, '')
text = strip_string_values(text)
text = strip_integer_values(text)
text = strip_in_clause_values(text)
end

def strip_in_clause_values text
text.gsub(/\sIN\s\((.*)\)/) do |match|
" IN (values-redacted)"
end
end

def strip_integer_values text
text.gsub(/\s\=\s([0-9]+)/) do |match|
" = number-redacted"
end
end

def strip_string_values text
text.gsub(/\s\=\s\'(.*)\'/) do |match|
" = 'value-redacted'"
end
end

def debug?
true
end
end
end
3 changes: 3 additions & 0 deletions lib/sql_footprint/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module SqlFootprint
VERSION = "0.1.0"
end
2 changes: 2 additions & 0 deletions spec/models.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Widget < ActiveRecord::Base
end
10 changes: 10 additions & 0 deletions spec/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ActiveRecord::Schema.define do
self.verbose = false

create_table :widgets, :force => true do |t|
t.string :name
t.integer :quantity

t.timestamps
end
end
11 changes: 11 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'bundler/setup'
Bundler.setup
require 'pry'
require 'sql_footprint'
require 'active_record'

ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'

load File.dirname(__FILE__) + '/schema.rb'
require File.dirname(__FILE__) + '/models.rb'
66 changes: 66 additions & 0 deletions spec/sql_footprint_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require 'spec_helper'

describe SqlFootprint do
it 'has a version number' do
expect(SqlFootprint::VERSION).not_to be nil
end

describe '.start' do
let!(:logger) { described_class.start }
let(:sql) { logger.logs.last }

it 'logs sql' do
Widget.create!
expect(logger.logs.length).to eq 1
end

it 'formats inserts' do
Widget.create!
expect(sql).to eq "INSERT INTO \"widgets\" (\"created_at\", \"updated_at\") VALUES (?, ?)"

end

it 'formats selects' do
Widget.where(name: SecureRandom.uuid, quantity: 1).last
expect(sql).to eq "SELECT \"widgets\".* FROM \"widgets\" WHERE \"widgets\".\"name\" = 'value-redacted' AND \"widgets\".\"quantity\" = number-redacted ORDER BY \"widgets\".\"id\" DESC LIMIT 1"
end

it 'formats IN clauses' do
Widget.where(name: [SecureRandom.uuid, SecureRandom.uuid]).last
expect(sql).to eq "SELECT \"widgets\".* FROM \"widgets\" WHERE \"widgets\".\"name\" IN (values-redacted) ORDER BY \"widgets\".\"id\" DESC LIMIT 1"
end

it 'dedupes the same sql' do
Widget.create!
Widget.create!
expect(logger.logs.length).to eq 1
end

it 'sorts the results' do
Widget.where(name: SecureRandom.uuid, quantity: 1).last
Widget.create!
expect(logger.logs.first).to include('INSERT INTO')
end
end

describe '.stop' do
it 'writes the footprint' do
described_class.start
Widget.create!
described_class.stop
log = File.read('footprint.sql')
expect(log).to include('INSERT INTO')
end

it 'removes old results' do
described_class.start
Widget.create!
described_class.stop
described_class.start
Widget.last
described_class.stop
log = File.read('footprint.sql')
expect(log).to_not include('INSERT INTO')
end
end
end
27 changes: 27 additions & 0 deletions sql_footprint.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'sql_footprint/version'

Gem::Specification.new do |spec|
spec.name = "sql_footprint"
spec.version = SqlFootprint::VERSION
spec.authors = ["Brandon Joyce"]
spec.email = ["[email protected]"]

spec.summary = %q{Keeps your DB guy happy.}
spec.description = %q{Check your footprint file into source control}
spec.homepage = "https://github.com/covermymeds/sql_footprint"

spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_development_dependency "bundler", "~> 1.10"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec"
spec.add_development_dependency "activerecord", "~> 4.0.0"
spec.add_development_dependency "pry"
spec.add_development_dependency "sqlite3"
end

0 comments on commit 66de7a7

Please sign in to comment.