sql_parser 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.
@@ -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