sql_wrangler 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README ADDED
@@ -0,0 +1 @@
1
+ Makes it easier to deal with SQL queries and handle their results.
@@ -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