parse_my_sql 0.1.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gem "treetop"
4
+
5
+ group :development, :test do
6
+ gem "rspec"
7
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ polyglot (0.3.1)
6
+ rspec (2.0.1)
7
+ rspec-core (~> 2.0.1)
8
+ rspec-expectations (~> 2.0.1)
9
+ rspec-mocks (~> 2.0.1)
10
+ rspec-core (2.0.1)
11
+ rspec-expectations (2.0.1)
12
+ diff-lcs (>= 1.1.2)
13
+ rspec-mocks (2.0.1)
14
+ rspec-core (~> 2.0.1)
15
+ rspec-expectations (~> 2.0.1)
16
+ treetop (1.4.8)
17
+ polyglot (>= 0.3.1)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ rspec
24
+ treetop
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Leonardo Bessa
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,22 @@
1
+ = parse_my_sql
2
+
3
+ Transforms sql query strings in a parse tree.
4
+
5
+ == Credits
6
+
7
+ This source code and grammar are based on sql_parser project from privotalrb.
8
+ More info at http://pivotalrb.rubyforge.org/svn/sql_parser and http://pivotalrb.rubyforge.org/ .
9
+
10
+ == Note on Patches/Pull Requests
11
+
12
+ * Fork the project.
13
+ * Make your feature addition or bug fix.
14
+ * Add tests for it. This is important so I don't break it in a
15
+ future version unintentionally.
16
+ * Commit, do not mess with rakefile, version, or history.
17
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
18
+ * Send me a pull request. Bonus points for topic branches.
19
+
20
+ == Copyright
21
+
22
+ Copyright (c) 2010 Leonardo Bessa. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "parse_my_sql"
8
+ gem.summary = %Q{SQL parser}
9
+ gem.description = %Q{Transforms sql query strings in a parse tree}
10
+ gem.email = "leobessa@gmail.com"
11
+ gem.homepage = "http://github.com/leobessa/parse_my_sql"
12
+ gem.authors = ["Leonardo Bessa"]
13
+ gem.add_development_dependency "rspec", ">= 2.0.1"
14
+ gem.add_runtime_dependency "treetop", ">= 1.4.8"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rspec/core'
23
+ require 'rspec/core/rake_task'
24
+ RSpec::Core::RakeTask.new(:spec) do |t|
25
+ t.pattern = "./spec/**/*_spec.rb"
26
+ end
27
+
28
+ RSpec::Core::RakeTask.new(:rcov) do |t|
29
+ t.pattern = "./spec/**/*_spec.rb"
30
+ t.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "parse_my_sql #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,2 @@
1
+ require "treetop"
2
+ require "sql_parser"
data/lib/sql_parser.rb ADDED
@@ -0,0 +1,17 @@
1
+ Treetop.load File.join(File.dirname(__FILE__), 'sql_parser.treetop')
2
+
3
+ class SqlParser::ItemsNode < Treetop::Runtime::SyntaxNode
4
+ def values
5
+ items.values.unshift(item.value)
6
+ end
7
+ end
8
+
9
+ class SqlParser::ItemNode < Treetop::Runtime::SyntaxNode
10
+ def values
11
+ [value]
12
+ end
13
+
14
+ def value
15
+ text_value.to_sym
16
+ end
17
+ end
@@ -0,0 +1,183 @@
1
+ grammar Sql
2
+ rule statement
3
+ select from where {
4
+ def tree
5
+ {
6
+ :operator => operator,
7
+ :set_quantifier => set_quantifier,
8
+ :fields => fields,
9
+ :tables => tables,
10
+ :conditions => conditions
11
+ }
12
+ end
13
+
14
+ def operator
15
+ :select
16
+ end
17
+
18
+ def set_quantifier
19
+ select.set_quantifier
20
+ end
21
+
22
+ def fields
23
+ select.fields
24
+ end
25
+
26
+ def tables
27
+ from.tables
28
+ end
29
+
30
+ def conditions
31
+ where.conditions
32
+ end
33
+ }
34
+ end
35
+
36
+ rule select
37
+ "select" quantifier:set_quantifier items {
38
+ def fields
39
+ items.values
40
+ end
41
+
42
+ def set_quantifier
43
+ quantifier.value
44
+ end
45
+ }
46
+ end
47
+
48
+ rule set_quantifier
49
+ (
50
+ required_space 'distinct' required_space
51
+ /
52
+ required_space 'all' required_space
53
+ ) {
54
+ def value
55
+ text_value.strip.downcase.to_sym
56
+ end
57
+ }
58
+ /
59
+ required_space {
60
+ def value
61
+ nil
62
+ end
63
+ }
64
+ end
65
+
66
+ rule from
67
+ required_space "from" required_space items {
68
+ def tables
69
+ items.values
70
+ end
71
+ }
72
+ /
73
+ space {
74
+ def tables
75
+ []
76
+ end
77
+ }
78
+ end
79
+
80
+ rule where
81
+ required_space "where" required_space conditional_items {
82
+ def conditions
83
+ conditional_items.node
84
+ end
85
+ }
86
+ /
87
+ space {
88
+ def conditions
89
+ []
90
+ end
91
+ }
92
+ end
93
+
94
+ rule conditional_items
95
+ conditional_item required_space boolean_join required_space conditional_items {
96
+ def node
97
+ all_nodes = []
98
+ all_nodes.concat(conditional_item.node)
99
+ all_nodes << {:operator => boolean_join.operator}
100
+ all_nodes.concat(conditional_items.node)
101
+ all_nodes
102
+ end
103
+ }
104
+ /
105
+ conditional_item
106
+ end
107
+
108
+ rule boolean_join
109
+ ('and' / 'or') {
110
+ def operator
111
+ text_value.to_sym
112
+ end
113
+ }
114
+ end
115
+
116
+ rule conditional_item
117
+ assignment_field space conditional_operator space assignment_value {
118
+ def node
119
+ [{
120
+ :operator => conditional_operator.value,
121
+ :field => assignment_field.text_value.to_sym,
122
+ :value => assignment_value.value
123
+ }]
124
+ end
125
+ }
126
+ end
127
+
128
+ rule conditional_operator
129
+ ('<>' / '>=' / '<=' / '=' / '>' / '<') {
130
+ def value
131
+ text_value.to_sym
132
+ end
133
+ }
134
+ end
135
+
136
+ rule assignment_field
137
+ !keyword [a-zA-Z_] [a-zA-Z0-9_]*
138
+ end
139
+
140
+ rule assignment_value
141
+ string_assignment_value
142
+ /
143
+ numeric_assignment_value
144
+ end
145
+
146
+ rule string_assignment_value
147
+ "'" [^']* "'" {
148
+ def value
149
+ text_value[1..-2]
150
+ end
151
+ }
152
+ end
153
+
154
+ rule numeric_assignment_value
155
+ !keyword [0-9]+ {
156
+ def value
157
+ Integer(text_value)
158
+ end
159
+ }
160
+ end
161
+
162
+ rule items
163
+ item space [,]+ space items <SqlParser::ItemsNode>
164
+ /
165
+ item
166
+ end
167
+
168
+ rule item
169
+ !keyword [a-z_0-9*]+ <SqlParser::ItemNode>
170
+ end
171
+
172
+ rule keyword
173
+ 'select' / 'from'
174
+ end
175
+
176
+ rule required_space
177
+ ' '+
178
+ end
179
+
180
+ rule space
181
+ ' '*
182
+ end
183
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'parse_my_sql'
@@ -0,0 +1,206 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe SqlParser do
4
+
5
+ context "when conditions are not joined with an :and or :or," do
6
+ it "does not succeed" do
7
+ subject.parse("select first_name from users where first_name='joe' last_name='bob'").should be_nil
8
+ end
9
+ end
10
+
11
+ context "#tree when parsing select statement" do
12
+ it "parses a multi field, table, and where clause statement" do
13
+ subject.parse("select distinct *, first_name, last_name, middle_name from users, accounts, logins where first_name='joe' and last_name='bob' or age > 25").tree.should == {
14
+ :operator => :select,
15
+ :set_quantifier => :distinct,
16
+ :fields => [:'*', :first_name, :last_name, :middle_name],
17
+ :tables => [:users, :accounts, :logins],
18
+ :conditions => [
19
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
20
+ {:operator => :and},
21
+ {:operator => :'=', :field => :last_name, :value => 'bob'},
22
+ {:operator => :or},
23
+ {:operator => :'>', :field => :age, :value => 25}
24
+ ]
25
+ }
26
+ end
27
+ end
28
+
29
+ context "#operator when parsing select statement" do
30
+ it "returns :select" do
31
+ subject.parse("select first_name").operator.should == :select
32
+ end
33
+ end
34
+
35
+ context "#set_quantifier when parsing select statement" do
36
+
37
+ it "when parsing distinct, returns :distinct" do
38
+ subject.parse("select distinct first_name").set_quantifier.should == :distinct
39
+ end
40
+
41
+ it "when parsing all, returns :all" do
42
+ subject.parse("select all first_name").set_quantifier.should == :all
43
+ end
44
+
45
+ end
46
+
47
+ context "#fields when parsing select statement" do
48
+
49
+ it "returns the fields in the statement" do
50
+ subject.parse("select first_name").fields.should == [:first_name]
51
+ subject.parse("select first_name, last_name, middle_name").fields.should == [
52
+ :first_name, :last_name, :middle_name
53
+ ]
54
+ end
55
+
56
+ it "when receiving *, returns * in the fields list" do
57
+ subject.parse("select *").fields.should == [:'*']
58
+ end
59
+ end
60
+
61
+ context "#tables when parsing select statement" do
62
+
63
+ it "returns tables from the statement" do
64
+ subject.parse("select first_name from users").tables.should == [:users]
65
+ subject.parse("select first_name from users, accounts, logins").tables.should == [
66
+ :users, :accounts, :logins
67
+ ]
68
+ end
69
+ end
70
+
71
+ context "#conditions when parsing select statement" do
72
+
73
+ it "when no where conditions, returns empty hash" do
74
+ subject.parse("select first_name from users").conditions.should == []
75
+ end
76
+
77
+ it "returns equality conditions from the statement" do
78
+ subject.parse("select first_name from users where id=3").conditions.should == [
79
+ { :operator => :'=', :field => :id, :value => 3 }
80
+ ]
81
+ subject.parse("select first_name from users where first_name='joe'").conditions.should == [
82
+ { :operator => :'=', :field => :first_name, :value => 'joe' }
83
+ ]
84
+ subject.parse("select first_name from users where first_name='joe' and last_name='bob'").conditions.should == [
85
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
86
+ {:operator => :and},
87
+ {:operator => :'=', :field => :last_name, :value => 'bob'}
88
+ ]
89
+ end
90
+
91
+ it "returns greater than conditions from the statement" do
92
+ subject.parse("select first_name from users where id>3").conditions.should == [
93
+ { :operator => :'>', :field => :id, :value => 3 }
94
+ ]
95
+
96
+ subject.parse("select first_name from users where id>3 and age>25").conditions.should == [
97
+ {:operator => :'>', :field => :id, :value => 3},
98
+ {:operator => :and},
99
+ {:operator => :'>', :field => :age, :value => 25}
100
+ ]
101
+ end
102
+
103
+ it "returns less than conditions from the statement" do
104
+ subject.parse("select first_name from users where id<3").conditions.should == [
105
+ { :operator => :'<', :field => :id, :value => 3 }
106
+ ]
107
+
108
+ subject.parse("select first_name from users where id<3 and age<25").conditions.should == [
109
+ {:operator => :'<', :field => :id, :value => 3},
110
+ {:operator => :and},
111
+ {:operator => :'<', :field => :age, :value => 25}
112
+ ]
113
+ end
114
+
115
+ it "returns greater than or equal to conditions from the statement" do
116
+ subject.parse("select first_name from users where id>=3").conditions.should == [
117
+ { :operator => :'>=', :field => :id, :value => 3 }
118
+ ]
119
+
120
+ subject.parse("select first_name from users where id>=3 and age>=25").conditions.should == [
121
+ {:operator => :'>=', :field => :id, :value => 3},
122
+ {:operator => :and},
123
+ {:operator => :'>=', :field => :age, :value => 25}
124
+ ]
125
+ end
126
+
127
+ it "returns less than or equal to conditions from the statement" do
128
+ subject.parse("select first_name from users where id<=3").conditions.should == [
129
+ { :operator => :'<=', :field => :id, :value => 3 }
130
+ ]
131
+
132
+ subject.parse("select first_name from users where id<=3 and age<=25").conditions.should == [
133
+ {:operator => :'<=', :field => :id, :value => 3},
134
+ {:operator => :and},
135
+ {:operator => :'<=', :field => :age, :value => 25}
136
+ ]
137
+ end
138
+
139
+ it "returns not equal to conditions from the statement" do
140
+ subject.parse("select first_name from users where id<>3").conditions.should == [
141
+ { :operator => :'<>', :field => :id, :value => 3 }
142
+ ]
143
+
144
+ subject.parse("select first_name from users where id<>3 and age<>25").conditions.should == [
145
+ {:operator => :'<>', :field => :id, :value => 3},
146
+ {:operator => :and},
147
+ {:operator => :'<>', :field => :age, :value => 25}
148
+ ]
149
+ end
150
+ end
151
+
152
+ context "#conditions when parsing select statement with :and operators" do
153
+
154
+ it "returns single level :and operation" do
155
+ subject.parse("select first_name from users where first_name='joe' and last_name='bob'").conditions.should == [
156
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
157
+ {:operator => :and},
158
+ {:operator => :'=', :field => :last_name, :value => 'bob'}
159
+ ]
160
+ end
161
+
162
+ it "returns nested :and operations from the statement" do
163
+ subject.parse("select first_name from users where first_name='joe' and last_name='bob' and middle_name='pat'").conditions.should == [
164
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
165
+ {:operator => :and},
166
+ {:operator => :'=', :field => :last_name, :value => 'bob'},
167
+ {:operator => :and},
168
+ {:operator => :'=', :field => :middle_name, :value => 'pat'}
169
+ ]
170
+ end
171
+ end
172
+
173
+ context "#conditions when parsing select statement with :or operators" do
174
+
175
+ it "returns single level :and operation" do
176
+ subject.parse("select first_name from users where first_name='joe' or last_name='bob'").conditions.should == [
177
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
178
+ {:operator => :or},
179
+ {:operator => :'=', :field => :last_name, :value => 'bob'}
180
+ ]
181
+ end
182
+
183
+ it "returns nested :or operations from the statement" do
184
+ subject.parse("select first_name from users where first_name='joe' or last_name='bob' or middle_name='pat'").conditions.should == [
185
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
186
+ {:operator => :or},
187
+ {:operator => :'=', :field => :last_name, :value => 'bob'},
188
+ {:operator => :or},
189
+ {:operator => :'=', :field => :middle_name, :value => 'pat'}
190
+ ]
191
+ end
192
+ end
193
+
194
+ context "#conditions when parsing select statement with :and and :or operators" do
195
+ it "returns :and having precedence over :or" do
196
+ subject.parse("select first_name from users where age > 25 and first_name='joe' or last_name='bob'").conditions.should == [
197
+ {:operator => :'>', :field => :age, :value => 25},
198
+ {:operator => :and},
199
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
200
+ {:operator => :or},
201
+ {:operator => :'=', :field => :last_name, :value => 'bob'}
202
+ ]
203
+ end
204
+ end
205
+
206
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parse_my_sql
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Leonardo Bessa
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-21 00:00:00 -02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 2
32
+ - 0
33
+ - 1
34
+ version: 2.0.1
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: treetop
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 23
46
+ segments:
47
+ - 1
48
+ - 4
49
+ - 8
50
+ version: 1.4.8
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ description: Transforms sql query strings in a parse tree
54
+ email: leobessa@gmail.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - LICENSE
61
+ - README.rdoc
62
+ files:
63
+ - .document
64
+ - .gitignore
65
+ - .rspec
66
+ - Gemfile
67
+ - Gemfile.lock
68
+ - LICENSE
69
+ - README.rdoc
70
+ - Rakefile
71
+ - VERSION
72
+ - lib/parse_my_sql.rb
73
+ - lib/sql_parser.rb
74
+ - lib/sql_parser.treetop
75
+ - spec/spec_helper.rb
76
+ - spec/sql_parser_spec.rb
77
+ has_rdoc: true
78
+ homepage: http://github.com/leobessa/parse_my_sql
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options:
83
+ - --charset=UTF-8
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.3.7
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: SQL parser
111
+ test_files:
112
+ - spec/spec_helper.rb
113
+ - spec/sql_parser_spec.rb