sql_parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sql_parser.gemspec
4
+ gemspec
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ require "./version"
2
+
3
+ module .
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,21 @@
1
+ require "rubygems"
2
+ require "treetop"
3
+
4
+ dir = File.expand_path(File.dirname(__FILE__))
5
+ Treetop.load File.expand_path("#{dir}/sql_parser/sql_parser")
6
+
7
+ class SqlParser::ItemsNode < Treetop::Runtime::SyntaxNode
8
+ def values
9
+ items.values.unshift(item.value)
10
+ end
11
+ end
12
+
13
+ class SqlParser::ItemNode < Treetop::Runtime::SyntaxNode
14
+ def values
15
+ [value]
16
+ end
17
+
18
+ def value
19
+ text_value.to_sym
20
+ end
21
+ 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
+ class SqlParser
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift("#{dir}/../lib")
3
+ require "sql_parser"
4
+
5
+ # require "spec"
@@ -0,0 +1,231 @@
1
+ dir = File.dirname(__FILE__)
2
+ require File.expand_path("#{dir}/spec_helper")
3
+
4
+ shared_context "SqlParser" do
5
+ before do
6
+ @parser = SqlParser.new
7
+ end
8
+
9
+ def parse(input)
10
+ result = @parser.parse(input)
11
+ if !result
12
+ puts result.failure_reason
13
+ end
14
+ result.should be
15
+ result
16
+ end
17
+ end
18
+
19
+ describe SqlParser, "parsing" do
20
+ include_context "SqlParser"
21
+
22
+ it "when conditions are not joined with an :and or :or, does not succeed" do
23
+ @parser.parse("select first_name from users where first_name='joe' last_name='bob'").should be_nil
24
+ end
25
+ end
26
+
27
+ describe SqlParser, "#tree when parsing select statement" do
28
+ include_context "SqlParser"
29
+
30
+ it "parses a multi field, table, and where clause statement" do
31
+ 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 == {
32
+ :operator => :select,
33
+ :set_quantifier => :distinct,
34
+ :fields => [:'*', :first_name, :last_name, :middle_name],
35
+ :tables => [:users, :accounts, :logins],
36
+ :conditions => [
37
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
38
+ {:operator => :and},
39
+ {:operator => :'=', :field => :last_name, :value => 'bob'},
40
+ {:operator => :or},
41
+ {:operator => :'>', :field => :age, :value => 25}
42
+ ]
43
+ }
44
+ end
45
+ end
46
+
47
+ describe SqlParser, "#operator when parsing select statement" do
48
+ include_context "SqlParser"
49
+
50
+ it "returns :select" do
51
+ parse("select first_name").operator.should == :select
52
+ end
53
+ end
54
+
55
+ describe SqlParser, "#set_quantifier when parsing select statement" do
56
+ include_context "SqlParser"
57
+
58
+ it "when parsing distinct, returns :distinct" do
59
+ parse("select distinct first_name").set_quantifier.should == :distinct
60
+ end
61
+
62
+ it "when parsing all, returns :all" do
63
+ parse("select all first_name").set_quantifier.should == :all
64
+ end
65
+ end
66
+
67
+ describe SqlParser, "#fields when parsing select statement" do
68
+ include_context "SqlParser"
69
+
70
+ it "returns the fields in the statement" do
71
+ parse("select first_name").fields.should == [:first_name]
72
+ parse("select first_name, last_name, middle_name").fields.should == [
73
+ :first_name, :last_name, :middle_name
74
+ ]
75
+ end
76
+
77
+ it "when receiving *, returns * in the fields list" do
78
+ parse("select *").fields.should == [:'*']
79
+ end
80
+ end
81
+
82
+ describe SqlParser, "#tables when parsing select statement" do
83
+ include_context "SqlParser"
84
+
85
+ it "returns tables from the statement" do
86
+ parse("select first_name from users").tables.should == [:users]
87
+ parse("select first_name from users, accounts, logins").tables.should == [
88
+ :users, :accounts, :logins
89
+ ]
90
+ end
91
+ end
92
+
93
+ describe SqlParser, "#conditions when parsing select statement" do
94
+ include_context "SqlParser"
95
+
96
+ it "when no where conditions, returns empty hash" do
97
+ parse("select first_name from users").conditions.should == []
98
+ end
99
+
100
+ it "returns equality conditions from the statement" do
101
+ parse("select first_name from users where id=3").conditions.should == [
102
+ { :operator => :'=', :field => :id, :value => 3 }
103
+ ]
104
+ parse("select first_name from users where first_name='joe'").conditions.should == [
105
+ { :operator => :'=', :field => :first_name, :value => 'joe' }
106
+ ]
107
+ parse("select first_name from users where first_name='joe' and last_name='bob'").conditions.should == [
108
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
109
+ {:operator => :and},
110
+ {:operator => :'=', :field => :last_name, :value => 'bob'}
111
+ ]
112
+ end
113
+
114
+ it "returns greater than conditions from the statement" do
115
+ parse("select first_name from users where id>3").conditions.should == [
116
+ { :operator => :'>', :field => :id, :value => 3 }
117
+ ]
118
+
119
+ parse("select first_name from users where id>3 and age>25").conditions.should == [
120
+ {:operator => :'>', :field => :id, :value => 3},
121
+ {:operator => :and},
122
+ {:operator => :'>', :field => :age, :value => 25}
123
+ ]
124
+ end
125
+
126
+ it "returns less than conditions from the statement" do
127
+ parse("select first_name from users where id<3").conditions.should == [
128
+ { :operator => :'<', :field => :id, :value => 3 }
129
+ ]
130
+
131
+ parse("select first_name from users where id<3 and age<25").conditions.should == [
132
+ {:operator => :'<', :field => :id, :value => 3},
133
+ {:operator => :and},
134
+ {:operator => :'<', :field => :age, :value => 25}
135
+ ]
136
+ end
137
+
138
+ it "returns greater than or equal to conditions from the statement" do
139
+ parse("select first_name from users where id>=3").conditions.should == [
140
+ { :operator => :'>=', :field => :id, :value => 3 }
141
+ ]
142
+
143
+ parse("select first_name from users where id>=3 and age>=25").conditions.should == [
144
+ {:operator => :'>=', :field => :id, :value => 3},
145
+ {:operator => :and},
146
+ {:operator => :'>=', :field => :age, :value => 25}
147
+ ]
148
+ end
149
+
150
+ it "returns less than or equal to conditions from the statement" do
151
+ parse("select first_name from users where id<=3").conditions.should == [
152
+ { :operator => :'<=', :field => :id, :value => 3 }
153
+ ]
154
+
155
+ parse("select first_name from users where id<=3 and age<=25").conditions.should == [
156
+ {:operator => :'<=', :field => :id, :value => 3},
157
+ {:operator => :and},
158
+ {:operator => :'<=', :field => :age, :value => 25}
159
+ ]
160
+ end
161
+
162
+ it "returns not equal to conditions from the statement" do
163
+ parse("select first_name from users where id<>3").conditions.should == [
164
+ { :operator => :'<>', :field => :id, :value => 3 }
165
+ ]
166
+
167
+ parse("select first_name from users where id<>3 and age<>25").conditions.should == [
168
+ {:operator => :'<>', :field => :id, :value => 3},
169
+ {:operator => :and},
170
+ {:operator => :'<>', :field => :age, :value => 25}
171
+ ]
172
+ end
173
+ end
174
+
175
+ describe SqlParser, "#conditions when parsing select statement with :and operators" do
176
+ include_context "SqlParser"
177
+
178
+ it "returns single level :and operation" do
179
+ parse("select first_name from users where first_name='joe' and last_name='bob'").conditions.should == [
180
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
181
+ {:operator => :and},
182
+ {:operator => :'=', :field => :last_name, :value => 'bob'}
183
+ ]
184
+ end
185
+
186
+ it "returns nested :and operations from the statement" do
187
+ parse("select first_name from users where first_name='joe' and last_name='bob' and middle_name='pat'").conditions.should == [
188
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
189
+ {:operator => :and},
190
+ {:operator => :'=', :field => :last_name, :value => 'bob'},
191
+ {:operator => :and},
192
+ {:operator => :'=', :field => :middle_name, :value => 'pat'}
193
+ ]
194
+ end
195
+ end
196
+
197
+ describe SqlParser, "#conditions when parsing select statement with :or operators" do
198
+ include_context "SqlParser"
199
+
200
+ it "returns single level :and operation" do
201
+ parse("select first_name from users where first_name='joe' or last_name='bob'").conditions.should == [
202
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
203
+ {:operator => :or},
204
+ {:operator => :'=', :field => :last_name, :value => 'bob'}
205
+ ]
206
+ end
207
+
208
+ it "returns nested :or operations from the statement" do
209
+ parse("select first_name from users where first_name='joe' or last_name='bob' or middle_name='pat'").conditions.should == [
210
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
211
+ {:operator => :or},
212
+ {:operator => :'=', :field => :last_name, :value => 'bob'},
213
+ {:operator => :or},
214
+ {:operator => :'=', :field => :middle_name, :value => 'pat'}
215
+ ]
216
+ end
217
+ end
218
+
219
+ describe SqlParser, "#conditions when parsing select statement with :and and :or operators" do
220
+ include_context "SqlParser"
221
+
222
+ it "returns :and having precedence over :or" do
223
+ parse("select first_name from users where age > 25 and first_name='joe' or last_name='bob'").conditions.should == [
224
+ {:operator => :'>', :field => :age, :value => 25},
225
+ {:operator => :and},
226
+ {:operator => :'=', :field => :first_name, :value => 'joe'},
227
+ {:operator => :or},
228
+ {:operator => :'=', :field => :last_name, :value => 'bob'}
229
+ ]
230
+ end
231
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "sql_parser/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "sql_parser"
7
+ s.version = SqlParser::VERSION
8
+ s.authors = ["Jingwen Owen Ou"]
9
+ s.email = ["jingweno@gmail.com"]
10
+ s.homepage = "https://github.com/jingweno/sql_parser"
11
+ s.summary = %q{A Ruby SQL parser based on Treetop.}
12
+ s.description = %q{A Ruby SQL parser based on Treetop.}
13
+
14
+ s.rubyforge_project = "."
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_runtime_dependency "treetop"
23
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sql_parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jingwen Owen Ou
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-19 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70225085717560 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70225085717560
25
+ - !ruby/object:Gem::Dependency
26
+ name: treetop
27
+ requirement: &70225085717080 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70225085717080
36
+ description: A Ruby SQL parser based on Treetop.
37
+ email:
38
+ - jingweno@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - Gemfile
45
+ - Rakefile
46
+ - lib/..rb
47
+ - lib/sql_parser.rb
48
+ - lib/sql_parser/sql_parser.treetop
49
+ - lib/sql_parser/version.rb
50
+ - spec/spec_helper.rb
51
+ - spec/sql_parser_spec.rb
52
+ - sql_parser.gemspec
53
+ homepage: https://github.com/jingweno/sql_parser
54
+ licenses: []
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project: .
73
+ rubygems_version: 1.8.6
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: A Ruby SQL parser based on Treetop.
77
+ test_files:
78
+ - spec/spec_helper.rb
79
+ - spec/sql_parser_spec.rb