Skip to content

Commit 39996ce

Browse files
committed
Add files and license
1 parent c65d019 commit 39996ce

File tree

15 files changed

+630
-0
lines changed

15 files changed

+630
-0
lines changed

.github/workflows/ci.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: CI
2+
on: [push, pull_request]
3+
jobs:
4+
test:
5+
strategy:
6+
fail-fast: false
7+
matrix:
8+
ruby: ['3.0']
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v2
12+
- uses: ruby/setup-ruby@v1
13+
with:
14+
ruby-version: ${{ matrix.ruby }}
15+
bundler-cache: true
16+
# - run: bundle exec rake

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/.bundle/
2+
/.yardoc
3+
/_yardoc/
4+
/coverage/
5+
/doc/
6+
/pkg/
7+
/spec/reports/
8+
/tmp/
9+
10+
# rspec failure tracking
11+
.rspec_status

.rspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--format documentation
2+
--color
3+
--require spec_helper

Gemfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
gemspec

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2021 Qualified, Inc
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# SqlSpecHelper
2+
3+
`spec/spec_helper.rb`
4+
5+
```ruby
6+
require 'sql_spec_helper'
7+
8+
# Add `$sql`, `DB`, `run_sql`, `compare_with`, and `Display` to main for backwards compatibility.
9+
$sql_spec_helper = SqlSpecHelper.new('/workspace/solution.txt')
10+
$sql = $sql_spec_helper.sql
11+
DB = $sql_spec_helper.db
12+
def run_sql(...)
13+
$sql_spec_helper.run_sql(...)
14+
end
15+
def compare_with(...)
16+
$sql_spec_helper.compare_with(...)
17+
end
18+
Display = SqlSpecHelper::Display
19+
```

Rakefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
require "bundler/gem_tasks"
4+
require "rspec/core/rake_task"
5+
6+
RSpec::Core::RakeTask.new(:spec)
7+
8+
task default: :spec

bin/console

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require "bundler/setup"
5+
require "sql_spec_helper"
6+
7+
# You can add fixtures and/or initialization code here to make experimenting
8+
# with your gem easier. You can also use a different console, if you like.
9+
10+
# (If you use this, don't forget to add pry to your Gemfile!)
11+
# require "pry"
12+
# Pry.start
13+
14+
require "irb"
15+
IRB.start(__FILE__)

bin/setup

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
set -vx
5+
6+
bundle install
7+
8+
# Do any other automated setup that you need to do here

lib/sql_spec_helper.rb

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# frozen_string_literal: true
2+
3+
require 'sequel'
4+
5+
require_relative 'sql_spec_helper/display'
6+
require_relative 'sql_spec_helper/daff_wrapper'
7+
require_relative 'sql_spec_helper/compare'
8+
9+
class SqlSpecHelper
10+
attr_reader :sql, :db
11+
12+
def initialize(solution_path)
13+
@sql = File.read(solution_path)
14+
@commands = sql_commands(@sql)
15+
@db = connect
16+
end
17+
18+
# `show_daff_table: true` display diff table
19+
# `daff_csv_show_index: true` include index in test output
20+
def compare_with(expected, limit: 100, collapsed: false, show_daff_table: true, daff_csv_show_index: false, &block)
21+
sql_compare = SqlCompare.new(
22+
self,
23+
expected,
24+
cmds: @commands,
25+
limit: limit,
26+
collapsed: collapsed,
27+
show_daff_table: show_daff_table,
28+
daff_csv_show_index: daff_csv_show_index
29+
)
30+
sql_compare.instance_eval(&block) if block
31+
32+
sql_compare.spec
33+
sql_compare.actual
34+
end
35+
36+
# The main method used when running user's code.
37+
# Returns an Array of `Sequel::Adapter::Dataset` unless commands contained only one `SELECT`.
38+
# Returns `nil` if no `SELECT`.
39+
def run_sql(cmds: nil, limit: 100, print: true, label: 'SELECT Results', collapsed: false, &block)
40+
Display.status("Running sql commands...")
41+
cmds ||= @commands
42+
results = Array(cmds).each_with_object([]) do |cmd, results|
43+
dataset = run_cmd(cmd) || []
44+
result = dataset.to_a
45+
next if result.empty?
46+
47+
lbl = label
48+
lbl += " (Top #{limit} of #{result.size})" if result.size > limit
49+
lbl = "-" + lbl if collapsed
50+
51+
block.call(dataset, lbl) if block
52+
53+
Display.table(result.take(limit), label: lbl, allow_preview: true) if print
54+
results.push(dataset)
55+
end
56+
57+
if results.length > 1
58+
results
59+
else
60+
results.first
61+
end
62+
63+
rescue Sequel::DatabaseError => ex
64+
Display.print("ERROR", ex.message.strip)
65+
end
66+
67+
private
68+
69+
# Connect the database
70+
def connect
71+
Display.status "Connecting to database..."
72+
case ENV['DATABASE_TYPE']
73+
when 'sqlite'
74+
Sequel.sqlite
75+
when 'postgres'
76+
Sequel.connect(
77+
adapter: 'postgres',
78+
host: ENV['PGHOST'],
79+
user: ENV['PGUSER'],
80+
port: ENV['PGPORT'],
81+
database: ENV['DATABASE_NAME'] || ENV['PGDATABASE'],
82+
)
83+
when 'mssql'
84+
Sequel.connect(
85+
adapter: 'tinytds',
86+
host: ENV['MSSQL_HOST'],
87+
port: ENV['MSSQL_PORT'],
88+
user: ENV['MSSQL_USER'],
89+
password: ENV['MSSQL_PASS'],
90+
)
91+
else
92+
raise "Unknown database type #{ENV['DATABASE_TYPE']}"
93+
end
94+
end
95+
96+
def sql_commands(sql)
97+
split_sql_commands(clean_sql(sql))
98+
end
99+
100+
def run_cmd(cmd)
101+
select_cmd?(cmd) ? @db[cmd] : @db.run(cmd)
102+
end
103+
104+
def clean_sql(sql)
105+
sql.gsub(/(\/\*([\s\S]*?)\*\/|--.*)/, "")
106+
end
107+
108+
def select_cmd?(cmd)
109+
(cmd.strip =~ /^(SELECT|WITH)/i) == 0
110+
end
111+
112+
def split_sql_commands(sql)
113+
# first we want to seperate select statements into chunks
114+
chunks = sql.split(/;[ \n\r]*$/i).select {|s| !s.empty?}.chunk {|s| select_cmd?(s)}
115+
# select statements need to stay individual so that we can return individual datasets, but we can group other statements together
116+
chunks.each_with_object([]) do |(select, cmds), final|
117+
if select
118+
final.concat(cmds)
119+
else
120+
final.push(cmds.join(";\n"))
121+
end
122+
end
123+
end
124+
end

0 commit comments

Comments
 (0)