sql_wrangler 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +1 -0
- data/lib/sql_wrangler.rb +149 -0
- data/test/sql_wrangler_tests.rb +168 -0
- metadata +58 -0
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Makes it easier to deal with SQL queries and handle their results.
|
data/lib/sql_wrangler.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
|
3
|
+
module SqlWrangler
|
4
|
+
|
5
|
+
class SqlConnection
|
6
|
+
|
7
|
+
def query(sql_string)
|
8
|
+
Query.new self, sql_string
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
class SqLiteConnection < SqlConnection
|
14
|
+
|
15
|
+
def initialize(db_path)
|
16
|
+
@db = SQLite3::Database.new db_path
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute_sql(sql_string)
|
20
|
+
@db.execute2(sql_string)
|
21
|
+
end
|
22
|
+
|
23
|
+
def command(sql_string)
|
24
|
+
@db.execute(sql_string)
|
25
|
+
end
|
26
|
+
|
27
|
+
def close
|
28
|
+
@db.close
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class Query
|
34
|
+
|
35
|
+
attr_reader :sql_string
|
36
|
+
attr_reader :groupings
|
37
|
+
attr_reader :conn
|
38
|
+
|
39
|
+
def initialize(conn, sql_string)
|
40
|
+
@conn = conn
|
41
|
+
@sql_string = sql_string
|
42
|
+
@groupings = []
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute
|
46
|
+
raw_result = @conn.execute_sql(@sql_string)
|
47
|
+
init_groups_for_execution raw_result[0]
|
48
|
+
return format_query_result(raw_result)
|
49
|
+
end
|
50
|
+
|
51
|
+
def format_query_result(raw_result)
|
52
|
+
formatted_result = []
|
53
|
+
columns_by_index = get_columns_by_index raw_result[0]
|
54
|
+
|
55
|
+
raw_result[1,raw_result.length-1].each do |raw_row|
|
56
|
+
merge_row raw_row, @groupings, formatted_result, columns_by_index
|
57
|
+
end
|
58
|
+
|
59
|
+
return formatted_result
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_columns_by_index columns
|
63
|
+
columns_by_index = {}
|
64
|
+
(0..columns.length-1).each { |i| columns_by_index[i] = columns[i] }
|
65
|
+
return columns_by_index
|
66
|
+
end
|
67
|
+
|
68
|
+
def init_groups_for_execution columns
|
69
|
+
used_indexes = []
|
70
|
+
@groupings.each do |group|
|
71
|
+
group.group_indexes = []
|
72
|
+
group.content_indexes = []
|
73
|
+
(0..(columns.length-1)).each do |i|
|
74
|
+
if group.columns.any? { |c| c == columns[i] }
|
75
|
+
used_indexes << i
|
76
|
+
group.group_indexes << i
|
77
|
+
elsif not used_indexes.any? { |used_index| used_index == i }
|
78
|
+
group.content_indexes << i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def merge_row(row, groups, grouped_data, columns_by_index)
|
85
|
+
|
86
|
+
if not @groupings.any?
|
87
|
+
flat_row = {}
|
88
|
+
columns_by_index.each { |index, column| flat_row[column] = row[index] }
|
89
|
+
grouped_data << flat_row
|
90
|
+
elsif groups.any?
|
91
|
+
this_group = groups[0]
|
92
|
+
remaining_groups = groups[1, groups.length-1]
|
93
|
+
grouped_vals = {}
|
94
|
+
this_group.group_indexes.each { |index| grouped_vals[columns_by_index[index]] = row[index] }
|
95
|
+
|
96
|
+
if grouped_vals.values.any? { |value| value != nil }
|
97
|
+
grouped_row = get_existing_grouped_row grouped_vals, grouped_data
|
98
|
+
|
99
|
+
if grouped_row == nil
|
100
|
+
grouped_row = grouped_vals
|
101
|
+
grouped_row[this_group.name] = [] if this_group != nil
|
102
|
+
grouped_data << grouped_row
|
103
|
+
end
|
104
|
+
|
105
|
+
merge_row(row, remaining_groups, grouped_row[this_group.name], columns_by_index)
|
106
|
+
end
|
107
|
+
|
108
|
+
else
|
109
|
+
leaf_data = {}
|
110
|
+
@groupings.last.content_indexes.each { |i| leaf_data[columns_by_index[i]] = row[i] }
|
111
|
+
grouped_data << leaf_data if leaf_data.values.any? { |value| value != nil }
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_existing_grouped_row grouped_vals, grouped_data
|
117
|
+
grouped_data.each do |grouped_row|
|
118
|
+
if not grouped_vals.keys.any? { |key| grouped_vals[key] != grouped_row[key] }
|
119
|
+
return grouped_row
|
120
|
+
end
|
121
|
+
end
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def group(name, columns)
|
126
|
+
new_grouping = QueryGrouping.new(name, columns)
|
127
|
+
new_grouping.level = @groupings.length
|
128
|
+
@groupings << new_grouping
|
129
|
+
return self
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
class QueryGrouping
|
135
|
+
|
136
|
+
attr_reader :name
|
137
|
+
attr_reader :columns
|
138
|
+
attr_accessor :content_indexes
|
139
|
+
attr_accessor :group_indexes
|
140
|
+
attr_accessor :level
|
141
|
+
|
142
|
+
def initialize(name, columns)
|
143
|
+
@name = name
|
144
|
+
@columns = columns
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'fas_test'
|
2
|
+
require './lib/sql_wrangler'
|
3
|
+
|
4
|
+
class SqLiteConnectionTests < FasTest::TestClass
|
5
|
+
|
6
|
+
def class_setup
|
7
|
+
@conn = SqlWrangler::SqLiteConnection.new ":memory:"
|
8
|
+
end
|
9
|
+
|
10
|
+
def class_teardown
|
11
|
+
@conn.close
|
12
|
+
end
|
13
|
+
|
14
|
+
def test__command
|
15
|
+
begin
|
16
|
+
@conn.command("CREATE TABLE users (id int PRIMARY KEY, username VARCHAR(100), password VARCHAR(100))")
|
17
|
+
@conn.command("INSERT INTO users VALUES (1, 'username1', 'password1')")
|
18
|
+
result = @conn.execute_sql("SELECT * FROM users")
|
19
|
+
assert_equal(2, result.length)
|
20
|
+
ensure
|
21
|
+
@conn.command("DROP TABLE users")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test__query__without_executing_it
|
26
|
+
query = @conn.query("SELECT * FROM users")
|
27
|
+
assert_equal("SELECT * FROM users", query.sql_string)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class QueryTests < FasTest::TestClass
|
33
|
+
|
34
|
+
def class_setup
|
35
|
+
@conn = SqlWrangler::SqLiteConnection.new ":memory:"
|
36
|
+
@conn.execute_sql("CREATE TABLE users (username VARCHAR(100), password VARCHAR(100))")
|
37
|
+
@conn.execute_sql("CREATE TABLE groups (group_name VARCHAR(100))")
|
38
|
+
@conn.execute_sql("CREATE TABLE users_groups (username VARCHAR(100), group_name VARCHAR(100))")
|
39
|
+
|
40
|
+
@conn.execute_sql("create table articles (id int primary key, title varchar(100), content varchar(100), author varchar(100))")
|
41
|
+
@conn.execute_sql("create table comments (id int primary key, content text, article_id int)")
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_setup
|
45
|
+
@conn.execute_sql("INSERT INTO users VALUES ('username1', 'password1');")
|
46
|
+
@conn.execute_sql("INSERT INTO users VALUES ('username2', 'password2');")
|
47
|
+
@conn.execute_sql("INSERT INTO groups VALUES ('group one')")
|
48
|
+
@conn.execute_sql("INSERT INTO groups VALUES ('group two')")
|
49
|
+
@conn.execute_sql("INSERT INTO users_groups VALUES ('username1', 'group one')")
|
50
|
+
@conn.execute_sql("INSERT INTO users_groups VALUES ('username1', 'group two')")
|
51
|
+
@conn.execute_sql("INSERT INTO users_groups VALUES ('username2', 'group one')")
|
52
|
+
|
53
|
+
@conn.execute_sql("insert into articles values (1, 'article1', 'content1', 'username1')")
|
54
|
+
@conn.execute_sql("insert into comments values (1, 'comment on article1 #1', 1)")
|
55
|
+
@conn.execute_sql("insert into comments values (2, 'comment on article1 #2', 1)")
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_teardown
|
59
|
+
@conn.execute_sql("delete from users")
|
60
|
+
@conn.execute_sql("delete from groups")
|
61
|
+
@conn.execute_sql("delete from users_groups")
|
62
|
+
@conn.execute_sql("delete from articles")
|
63
|
+
@conn.execute_sql("delete from comments")
|
64
|
+
end
|
65
|
+
|
66
|
+
def class_teardown
|
67
|
+
@conn.close
|
68
|
+
end
|
69
|
+
|
70
|
+
def test__execute__has_correct_values_on_simple_query
|
71
|
+
result = @conn.query("SELECT * FROM users").execute
|
72
|
+
assert_equal(2, result.length)
|
73
|
+
assert_equal("username1", result[0]['username'])
|
74
|
+
assert_equal("password1", result[0]['password'])
|
75
|
+
assert_equal("username2", result[1]['username'])
|
76
|
+
assert_equal("password2", result[1]['password'])
|
77
|
+
end
|
78
|
+
|
79
|
+
def test__execute__has_correct_columns_on_simple_query
|
80
|
+
first = @conn.query("SELECT * FROM users").execute[0]
|
81
|
+
assert_true(first.keys.any? { |m| m == 'username' })
|
82
|
+
assert_true(first.keys.any? { |m| m == 'password' })
|
83
|
+
end
|
84
|
+
|
85
|
+
def test__execute__works_with_a_more_complex_query
|
86
|
+
result = @conn.query("
|
87
|
+
select u.username, g.group_name
|
88
|
+
from groups g
|
89
|
+
inner join users_groups ug on ug.group_name = g.group_name
|
90
|
+
inner join users u on u.username = ug.username
|
91
|
+
order by u.username, g.group_name").execute
|
92
|
+
assert_equal(3, result.length)
|
93
|
+
assert_equal("username1", result[0]["username"])
|
94
|
+
assert_equal("group one", result[0]["group_name"])
|
95
|
+
assert_equal("username1", result[1]["username"])
|
96
|
+
assert_equal("group two", result[1]["group_name"])
|
97
|
+
assert_equal("username2", result[2]["username"])
|
98
|
+
assert_equal("group one", result[2]["group_name"])
|
99
|
+
end
|
100
|
+
|
101
|
+
def test__execute__works_with_a_simple_grouping
|
102
|
+
result = @conn.query("
|
103
|
+
select u.username, g.group_name
|
104
|
+
from groups g
|
105
|
+
inner join users_groups ug on ug.group_name = g.group_name
|
106
|
+
inner join users u on u.username = ug.username
|
107
|
+
order by u.username, g.group_name").group("users", ["group_name"]).execute
|
108
|
+
assert_equal(2, result.length)
|
109
|
+
assert_equal("group one", result[0]["group_name"])
|
110
|
+
assert_equal(2, result[0]["users"].length)
|
111
|
+
assert_equal("username1", result[0]["users"][0]["username"])
|
112
|
+
assert_equal("username2", result[0]["users"][1]["username"])
|
113
|
+
assert_equal("group two", result[1]["group_name"])
|
114
|
+
assert_equal(1, result[1]["users"].length)
|
115
|
+
assert_equal("username1", result[1]["users"][0]["username"])
|
116
|
+
end
|
117
|
+
|
118
|
+
def test__group__modifies_query_object_correctly_with_single_grouping
|
119
|
+
query = @conn.query("
|
120
|
+
select u.username, g.group_name
|
121
|
+
from groups g
|
122
|
+
inner join users_groups ug on ug.group_name = g.group_name
|
123
|
+
inner join users u on u.username = ug.username
|
124
|
+
order by u.username, g.group_name").group("users", ["group_name"])
|
125
|
+
assert_equal(1, query.groupings.count)
|
126
|
+
assert_equal("users", query.groupings[0].name)
|
127
|
+
assert_equal(1, query.groupings[0].columns.length)
|
128
|
+
assert_equal("group_name", query.groupings[0].columns[0])
|
129
|
+
end
|
130
|
+
|
131
|
+
def test__execute__works_with_multi_level_grouping
|
132
|
+
result = @conn.query("
|
133
|
+
select u.username, g.group_name, a.title, a.content article_content, c.id comment_id, c.content comment_content
|
134
|
+
from groups g
|
135
|
+
join users_groups ug on ug.group_name = g.group_name
|
136
|
+
join users u on u.username = ug.username
|
137
|
+
left join articles a on a.author = u.username
|
138
|
+
left join comments c on c.article_id = a.id
|
139
|
+
order by g.group_name, u.username, a.title, c.content") \
|
140
|
+
.group("users", ["group_name"]) \
|
141
|
+
.group("articles", ["username"]) \
|
142
|
+
.group("comments", ["article_content", "title"]) \
|
143
|
+
.execute
|
144
|
+
|
145
|
+
assert_equal(2, result.length)
|
146
|
+
assert_equal("group one", result[0]["group_name"])
|
147
|
+
assert_equal(2, result[0]["users"].length)
|
148
|
+
assert_equal("username1", result[0]["users"][0]["username"])
|
149
|
+
|
150
|
+
assert_equal(1, result[0]["users"][0]["articles"].length)
|
151
|
+
assert_equal("article1", result[0]["users"][0]["articles"][0]["title"])
|
152
|
+
assert_equal("content1", result[0]["users"][0]["articles"][0]["article_content"])
|
153
|
+
|
154
|
+
assert_equal(2, result[0]["users"][0]["articles"][0]["comments"].length)
|
155
|
+
assert_equal(1, result[0]["users"][0]["articles"][0]["comments"][0]["comment_id"])
|
156
|
+
assert_equal("comment on article1 #1", result[0]["users"][0]["articles"][0]["comments"][0]["comment_content"])
|
157
|
+
assert_equal(2, result[0]["users"][0]["articles"][0]["comments"][1]["comment_id"])
|
158
|
+
assert_equal("comment on article1 #2", result[0]["users"][0]["articles"][0]["comments"][1]["comment_content"])
|
159
|
+
assert_equal("username2", result[0]["users"][1]["username"])
|
160
|
+
assert_equal(0, result[0]["users"][1]["articles"].length)
|
161
|
+
|
162
|
+
assert_equal("group two", result[1]["group_name"])
|
163
|
+
assert_equal(1, result[1]["users"].length)
|
164
|
+
assert_equal("username1", result[1]["users"][0]["username"])
|
165
|
+
assert_equal(1, result[1]["users"][0]["articles"].length)
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sql_wrangler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Graeme Hill
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-28 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Makes it easier to deal with SQL queries and handle their results.
|
18
|
+
email: graemekh@gmail.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README
|
25
|
+
files:
|
26
|
+
- README
|
27
|
+
- lib/sql_wrangler.rb
|
28
|
+
- test/sql_wrangler_tests.rb
|
29
|
+
has_rdoc: true
|
30
|
+
homepage: https://github.com/graeme-hill/sql_wrangler
|
31
|
+
licenses: []
|
32
|
+
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
requirements: []
|
51
|
+
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.5.2
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: A simple ORM alternative that makes it easier to deal with SQL queries in ruby.
|
57
|
+
test_files:
|
58
|
+
- test/sql_wrangler_tests.rb
|