sql_wrangler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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