yql-query 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/License.md +20 -0
- data/Rakefile +21 -0
- data/Readme.md +94 -0
- data/lib/yql-query.rb +1 -0
- data/lib/yql_query.rb +4 -0
- data/lib/yql_query/builder.rb +244 -0
- data/lib/yql_query/query.rb +70 -0
- data/lib/yql_query/source.rb +17 -0
- data/lib/yql_query/version.rb +4 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/yql_query_builder_spec.rb +330 -0
- data/spec/yql_query_source_spec.rb +11 -0
- data/yql-query.gemspec +30 -0
- metadata +150 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/License.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Steve Agalloco
|
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/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
task :test => :spec
|
9
|
+
|
10
|
+
namespace :doc do
|
11
|
+
require 'yard'
|
12
|
+
YARD::Rake::YardocTask.new do |task|
|
13
|
+
task.files = ['lib/**/*.rb']
|
14
|
+
task.options = [
|
15
|
+
'--protected',
|
16
|
+
'--output-dir', 'doc/yard',
|
17
|
+
'--markup', 'markdown',
|
18
|
+
'--readme', 'Readme.md'
|
19
|
+
]
|
20
|
+
end
|
21
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
YqlQuery
|
2
|
+
========
|
3
|
+
|
4
|
+
A simple [YQL query](http://developer.yahoo.com/yql/guide/index.html) generation library written in ruby, providing a chainable query builder capable of generating the most complex query conditions you can throw at it.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
(sudo) gem install yql-query
|
10
|
+
|
11
|
+
Documentation
|
12
|
+
-------------
|
13
|
+
|
14
|
+
[http://rdoc.info/gems/yql-query](http://rdoc.info/gems/yql-query)
|
15
|
+
|
16
|
+
Usage
|
17
|
+
-----
|
18
|
+
|
19
|
+
yql-query's primary interface is the Builder class. Once you've instantiated a builder, you can pretty much generate whatever query you'd like using it.
|
20
|
+
|
21
|
+
builder = YqlQuery::Builder.new
|
22
|
+
builder.table('music.artists')
|
23
|
+
|
24
|
+
to generate a query, the Builder class provides a number of methods to construct it's arguments:
|
25
|
+
|
26
|
+
builder.table('music.artists')
|
27
|
+
builder.select('name, genre')
|
28
|
+
builder.conditions("bands = 'false'")
|
29
|
+
builder.sort('age')
|
30
|
+
|
31
|
+
conditions are also aliased as 'where':
|
32
|
+
|
33
|
+
#using 'where'
|
34
|
+
builder.where("bands = 'true'")
|
35
|
+
|
36
|
+
# is the same thing as
|
37
|
+
builder.conditions("bands = 'true'")
|
38
|
+
|
39
|
+
conditions can be passed as either a string, an array or
|
40
|
+
|
41
|
+
builder.conditions(["name = 'Erykah Badu'", "release_year > '2005'"])
|
42
|
+
|
43
|
+
methods can be chained together:
|
44
|
+
|
45
|
+
builder.table('music.artists').select('name').where("genre = 'jazz'")
|
46
|
+
|
47
|
+
to generate the query, just call to_s or to_query:
|
48
|
+
|
49
|
+
builder.to_s
|
50
|
+
# => 'select * from tablename...'
|
51
|
+
|
52
|
+
builder.to_query
|
53
|
+
# => 'select * from tablename...'
|
54
|
+
|
55
|
+
passing a hash with a Builder instance to creates sub-select:
|
56
|
+
|
57
|
+
guid_query = Builder.new.table('users').select('guid').where("role = 'admin'")
|
58
|
+
|
59
|
+
builder = Builder.new.table('actions).where(:guid => guid_query)
|
60
|
+
builder.to_s
|
61
|
+
# => "select * from actions where guid in (select guid from users where role = 'admin')"
|
62
|
+
|
63
|
+
The full list of methods available:
|
64
|
+
|
65
|
+
table('music.albums')
|
66
|
+
use('http://somedomain.com/table.xml', 'othersource')
|
67
|
+
select('name')
|
68
|
+
conditions("genre = 'jazz'")
|
69
|
+
sort('albumName')
|
70
|
+
sort_descending('albumName')
|
71
|
+
limit(5)
|
72
|
+
offset(10)
|
73
|
+
tail(5)
|
74
|
+
truncate(10)
|
75
|
+
unique('format')
|
76
|
+
sanitize('description')
|
77
|
+
|
78
|
+
Refer to the [documentation](http://rdoc.info/gems/yql-query) for complete usage and more examples.
|
79
|
+
|
80
|
+
Note on Patches/Pull Requests
|
81
|
+
-----------------------------
|
82
|
+
|
83
|
+
* Fork the project.
|
84
|
+
* Make your feature addition or bug fix.
|
85
|
+
* Add tests for it. This is important so I don't break it in a
|
86
|
+
future version unintentionally.
|
87
|
+
* Commit, do not mess with rakefile, version, or history.
|
88
|
+
(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)
|
89
|
+
* Send me a pull request. Bonus points for topic branches.
|
90
|
+
|
91
|
+
Copyright
|
92
|
+
---------
|
93
|
+
|
94
|
+
Copyright (c) 2011 Steve Agalloco. See LICENSE for details.
|
data/lib/yql-query.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'yql_query'
|
data/lib/yql_query.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
module YqlQuery
|
2
|
+
|
3
|
+
# The primary query builder class, wraps a {Query} object and provides methods to assign query arguments.
|
4
|
+
class Builder
|
5
|
+
|
6
|
+
attr_accessor :query
|
7
|
+
|
8
|
+
# Instantiates a new Builder instance.
|
9
|
+
# @param
|
10
|
+
def initialize(options={})
|
11
|
+
self.query = Query.new
|
12
|
+
self.query.table = options.delete(:table)
|
13
|
+
self.query.limit = options.delete(:limit)
|
14
|
+
self.query.offset = options.delete(:offset)
|
15
|
+
self.query.select = options.delete(:select)
|
16
|
+
self.query.conditions = options.delete(:conditions) || []
|
17
|
+
self.query.uses = options.delete(:uses) || []
|
18
|
+
self.query.tail = options.delete(:tail)
|
19
|
+
self.query.truncate = options.delete(:truncate)
|
20
|
+
self.query.reverse = options.delete(:reverse)
|
21
|
+
self.query.unique = options.delete(:unique)
|
22
|
+
self.query.sanitize = options.delete(:sanitize)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Assigns the table for the query being constructed
|
26
|
+
#
|
27
|
+
# @param [String] table The name of the table.
|
28
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the table assigned.
|
29
|
+
def table(table)
|
30
|
+
self.query.table = table
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Assigns the limit for the query being constructed
|
35
|
+
#
|
36
|
+
# @param [Object] limit The limit for the query.
|
37
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the limit assigned.
|
38
|
+
#
|
39
|
+
# @example The limit may be passed as either a string or fixnum:
|
40
|
+
# base = Builder.new.limit(5)
|
41
|
+
# base = Builder.new.limit('5')
|
42
|
+
def limit(limit)
|
43
|
+
self.query.limit = limit
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Assigns the offset for the query being constructed
|
48
|
+
#
|
49
|
+
# @param [Object] offset The offset for the query.
|
50
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the offset assigned.
|
51
|
+
#
|
52
|
+
# @example The offset may be passed as either a string or fixnum:
|
53
|
+
# base = Builder.new.offset(5)
|
54
|
+
# base = Builder.new.offset('5')
|
55
|
+
def offset(offset)
|
56
|
+
self.query.offset = offset
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# Assigns the columns to select for the query being constructed
|
61
|
+
#
|
62
|
+
# @param [Object] select The select arguments for the query.
|
63
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the select arguments assigned.
|
64
|
+
#
|
65
|
+
# @example The select may be passed as either a string or an array:
|
66
|
+
# base = Builder.new.select('name')
|
67
|
+
# base = Builder.new.select('name, age')
|
68
|
+
# base = Builder.new.select(['name', 'age'])
|
69
|
+
def select(select)
|
70
|
+
self.query.select = select
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Assigns additional datatable sources for use with the query being constructed
|
75
|
+
#
|
76
|
+
# @param [String] source The url of the data source.
|
77
|
+
# @param [String] as The name the data source will be referenced as in queries.
|
78
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the data source arguments assigned.
|
79
|
+
#
|
80
|
+
# @example The select may be passed as either a string or an array:
|
81
|
+
# base = Builder.new.use('http://anothersource/sometable.xml', 'sometable')
|
82
|
+
# base.to_s
|
83
|
+
# # => "use http://anothersource/sometable.xml as sometable; select * from tablename"
|
84
|
+
def use(source, as)
|
85
|
+
self.query.uses << Source.new(source, as)
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
# Assigns additional datatable sources for use with the query being constructed.
|
90
|
+
# Conditions are combined. Hashes assume equivalency when generating queries, except when passed a
|
91
|
+
# {Builder} instance in which case a sub-select is assumed using an 'in'.
|
92
|
+
#
|
93
|
+
# @param [Objct] conditions The conditions of the query.
|
94
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the data conditions assigned.
|
95
|
+
#
|
96
|
+
# @example The conditions may be passed as either a string, array or a hash:
|
97
|
+
# base = Builder.new.conditions("sub_genre = 'bebop'")
|
98
|
+
# base.to_s
|
99
|
+
# # => "select * from tablename where sub_genre = 'bebop'"
|
100
|
+
#
|
101
|
+
# base.conditions("genre = 'jazz'").conditions("sub_genre = 'bebop'")
|
102
|
+
# base.to_s
|
103
|
+
# # => "select * from tablename where genre = 'jazz' and sub_genre = 'bebop'"
|
104
|
+
#
|
105
|
+
# base.conditions(["genre = 'jazz'", "sub_genre = 'bebop'"])
|
106
|
+
# # => "select * from tablename where genre = 'jazz' and sub_genre = 'bebop'"
|
107
|
+
#
|
108
|
+
# # conditions are also aliased as 'where'
|
109
|
+
# base.where("genre = 'jazz'")
|
110
|
+
#
|
111
|
+
# guid_query = Builder.new.table('users').select('guid').where("role = 'admin'")
|
112
|
+
# base = Builder.new.table('actions).where(:guid => guid_query)
|
113
|
+
# base.to_s
|
114
|
+
# => "select * from actions where guid in (select guid from users where role = 'admin')"
|
115
|
+
def conditions(conditions)
|
116
|
+
if conditions.kind_of?(String)
|
117
|
+
self.query.conditions << conditions
|
118
|
+
elsif conditions.kind_of?(Array)
|
119
|
+
self.query.conditions += conditions
|
120
|
+
elsif conditions.kind_of?(Hash)
|
121
|
+
conditions.each do |key, value|
|
122
|
+
if value.kind_of?(YqlQuery::Builder)
|
123
|
+
self.query.conditions << "#{key} in (#{value})"
|
124
|
+
else
|
125
|
+
self.query.conditions << "#{key} = '#{value}'"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
self
|
130
|
+
end
|
131
|
+
alias :where :conditions
|
132
|
+
|
133
|
+
# Assigns the field to be sorted for the query being constructed
|
134
|
+
#
|
135
|
+
# @param [Object] sort The column to sort for the query.
|
136
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the sort assigned.
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# base = Builder.new.sort('name)
|
140
|
+
def sort(sort)
|
141
|
+
self.query.sort = sort
|
142
|
+
self.query.sort_descending = false
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
# Assigns the field to be sorted for the query being constructed
|
147
|
+
#
|
148
|
+
# @param [Object] sort The column to sort descending for the query.
|
149
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the sort assigned.
|
150
|
+
#
|
151
|
+
# @example
|
152
|
+
# base = Builder.new.sort('name)
|
153
|
+
def sort_descending(sort)
|
154
|
+
self.query.sort = sort
|
155
|
+
self.query.sort_descending = true
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
# Assigns the tail argument for the query being constructed
|
160
|
+
#
|
161
|
+
# @param [Object] tail The tail argument for the query.
|
162
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the tail filter assigned.
|
163
|
+
#
|
164
|
+
# @example The tail argument may be passed as either a string or fixnum:
|
165
|
+
# base = Builder.new.tail(5)
|
166
|
+
# base = Builder.new.tail('5')
|
167
|
+
def tail(tail)
|
168
|
+
self.query.tail = tail
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
# Assigns the truncate argument for the query being constructed
|
173
|
+
#
|
174
|
+
# @param [Object] truncate The truncate argument for the query.
|
175
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the truncate filter assigned.
|
176
|
+
#
|
177
|
+
# @example The truncate argument may be passed as either a string or fixnum:
|
178
|
+
# base = Builder.new.truncate(5)
|
179
|
+
# base = Builder.new.truncate('5')
|
180
|
+
# base.to_s
|
181
|
+
# # => "select * from tablename | truncate(5)"
|
182
|
+
def truncate(truncate)
|
183
|
+
self.query.truncate = truncate
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
# Adds the reverse filter to the query being constructed
|
188
|
+
#
|
189
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the offset assigned.
|
190
|
+
#
|
191
|
+
# @example
|
192
|
+
# base = Builder.new.reverse
|
193
|
+
# base.to_s
|
194
|
+
# # => "select * from tablename | reverse"
|
195
|
+
def reverse
|
196
|
+
self.query.reverse = true
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
# Assigns the unique argument for the query being constructed
|
201
|
+
#
|
202
|
+
# @param [Object] truncate The unique argument for the query.
|
203
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the unique filter assigned.
|
204
|
+
#
|
205
|
+
# @example
|
206
|
+
# base = Builder.new.unique('genre')
|
207
|
+
# base.to_s
|
208
|
+
# # => "select * from tablename | unique(field='genre')"
|
209
|
+
def unique(unique)
|
210
|
+
self.query.unique = unique
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
# Assigns the sanitize argument for the query being constructed
|
215
|
+
#
|
216
|
+
# @param [Object] sanitize The sanitize argument for the query.
|
217
|
+
# @return [YqlQuery::Builder] YqlQuery::Builder instance reflecting the sanitize filter assigned.
|
218
|
+
#
|
219
|
+
# @example Sanitize accepts either a string representing the column name or a boolean 'true' to sanitize all
|
220
|
+
# base = Builder.new.sanitize('genre')
|
221
|
+
# base.to_s
|
222
|
+
# # => "select * from tablename | sanitize(field='genre')"
|
223
|
+
#
|
224
|
+
# base = Builder.new.sanitize(true)
|
225
|
+
# base.to_s
|
226
|
+
# # => "select * from tablename | sanitize()"
|
227
|
+
def sanitize(sanitize=true)
|
228
|
+
self.query.sanitize = sanitize
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns the generated YQL query based on the arguments provided
|
233
|
+
def to_s
|
234
|
+
self.query.to_s
|
235
|
+
end
|
236
|
+
alias :to_query :to_s
|
237
|
+
|
238
|
+
# Resets all arguments to the query
|
239
|
+
def reset
|
240
|
+
self.query = Query.new
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module YqlQuery
|
2
|
+
|
3
|
+
# The object underlying {Builder} which stores and generates the query.
|
4
|
+
class Query
|
5
|
+
attr_accessor :table, :limit, :offset, :select, :uses, :conditions
|
6
|
+
attr_accessor :sort, :tail, :truncate, :reverse, :unique, :sanitize
|
7
|
+
attr_accessor :sort_descending
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
self.conditions = []
|
11
|
+
self.uses = []
|
12
|
+
end
|
13
|
+
|
14
|
+
# generates a query based on it's attributes
|
15
|
+
def to_s
|
16
|
+
[use_statement, select_statement, conditions_statement, limit_offset_statement, filter_statement].join(' ').squeeze(' ').strip
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def use_statement
|
21
|
+
@uses.map { |use| "use #{use.source} as #{use.as};" }.join(' ')
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_statement
|
25
|
+
stmt = 'select '
|
26
|
+
stmt << case @select
|
27
|
+
when String
|
28
|
+
@select
|
29
|
+
when Array
|
30
|
+
@select.join(', ')
|
31
|
+
else
|
32
|
+
'*'
|
33
|
+
end
|
34
|
+
stmt << " from "
|
35
|
+
stmt << @table if @table
|
36
|
+
stmt
|
37
|
+
end
|
38
|
+
|
39
|
+
def conditions_statement
|
40
|
+
@conditions.uniq.any? ? "where #{@conditions.uniq.join(' and ')}" : ""
|
41
|
+
end
|
42
|
+
|
43
|
+
def limit_offset_statement
|
44
|
+
stmt = ''
|
45
|
+
stmt << "limit #{@limit}" if @limit
|
46
|
+
stmt << "offset #{@offset}" if @offset
|
47
|
+
stmt
|
48
|
+
end
|
49
|
+
|
50
|
+
def filter_statement
|
51
|
+
statements = []
|
52
|
+
if @sort && @sort_descending
|
53
|
+
statements << "sort(field='#{@sort}', descending='true')"
|
54
|
+
elsif @sort
|
55
|
+
statements << "sort(field='#{@sort}')"
|
56
|
+
end
|
57
|
+
|
58
|
+
statements << "tail(count=#{@tail})" if @tail
|
59
|
+
statements << "truncate(count=#{@truncate})" if @truncate
|
60
|
+
statements << "reverse()" if @reverse
|
61
|
+
statements << "unique(field='#{@unique}')" if @unique
|
62
|
+
if @sanitize && @sanitize == true
|
63
|
+
statements << "sanitize()"
|
64
|
+
elsif @sanitize
|
65
|
+
statements << "sanitize(field='#{@sanitize}')"
|
66
|
+
end
|
67
|
+
statements.any? ? "| #{statements.join(' | ')}" : ''
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module YqlQuery
|
2
|
+
|
3
|
+
# A class used by {Builder} to store additional data sources.
|
4
|
+
class Source
|
5
|
+
attr_accessor :source, :as
|
6
|
+
|
7
|
+
def initialize(source, as)
|
8
|
+
@source = source
|
9
|
+
@as = as
|
10
|
+
end
|
11
|
+
|
12
|
+
# Sources are equal if both their 'source' and 'as' attributes are equivalent.
|
13
|
+
def ==(b)
|
14
|
+
self.source == b.source && self.as == b.as
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,330 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe YqlQuery::Builder do
|
4
|
+
before(:each) do
|
5
|
+
@builder = YqlQuery::Builder.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#table()" do
|
9
|
+
it "should set the table" do
|
10
|
+
@builder.table('music.artists')
|
11
|
+
@builder.query.table.should == 'music.artists'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return the builder" do
|
15
|
+
@builder.table('music.artists').should be_kind_of(YqlQuery::Builder)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#limit()" do
|
20
|
+
it "should set the limit" do
|
21
|
+
@builder.limit(5)
|
22
|
+
@builder.query.limit.should == 5
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return the builder" do
|
26
|
+
@builder.limit(5).should be_kind_of(YqlQuery::Builder)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#offset()" do
|
31
|
+
it "should set the offset" do
|
32
|
+
@builder.offset(10)
|
33
|
+
@builder.query.offset.should == 10
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return the builder" do
|
37
|
+
@builder.offset(10).should be_kind_of(YqlQuery::Builder)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#select()" do
|
42
|
+
it "should set the select" do
|
43
|
+
@builder.select('name')
|
44
|
+
@builder.query.select.should == 'name'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should accept select as an array" do
|
48
|
+
@builder.select(['age', 'sex', 'language', 'other'])
|
49
|
+
@builder.query.select.should == ['age', 'sex', 'language', 'other']
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return the builder" do
|
53
|
+
@builder.select('name').should be_kind_of(YqlQuery::Builder)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#use()" do
|
58
|
+
it "should set the use" do
|
59
|
+
@builder.use('http://namedata.com', 'names')
|
60
|
+
@builder.query.uses.include?(YqlQuery::Source.new('http://namedata.com', 'names')).should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return the builder" do
|
64
|
+
@builder.use('http://namedata.com', 'names').should be_kind_of(YqlQuery::Builder)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#conditions()" do
|
69
|
+
it "should set the conditions" do
|
70
|
+
@builder.conditions("name = 'fred'")
|
71
|
+
@builder.query.conditions.include?("name = 'fred'").should be_true
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return the builder" do
|
75
|
+
@builder.conditions("name = 'fred'").should be_kind_of(YqlQuery::Builder)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should be aliased as #where()" do
|
79
|
+
@builder.where("name = 'jeff'")
|
80
|
+
@builder.query.conditions.include?("name = 'jeff'").should be_true
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should store all conditions" do
|
84
|
+
@builder.conditions("name = 'fred'")
|
85
|
+
@builder.conditions("age = 22")
|
86
|
+
@builder.query.conditions.include?("name = 'fred'").should be_true
|
87
|
+
@builder.query.conditions.include?("age = 22").should be_true
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should accept conditions as an array" do
|
91
|
+
@builder.conditions(["name = 'greg'", "age = 34"])
|
92
|
+
@builder.query.conditions.include?("name = 'greg'").should be_true
|
93
|
+
@builder.query.conditions.include?("age = 34").should be_true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should accept conditions as a hash" do
|
97
|
+
@builder.conditions({ :genre => 'jazz', :type => 'bebop'})
|
98
|
+
@builder.query.conditions.include?("genre = 'jazz'").should be_true
|
99
|
+
@builder.query.conditions.include?("type = 'bebop'").should be_true
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should accept conditions as a hash when passed another query" do
|
103
|
+
@builder.conditions({ :artist_id => YqlQuery::Builder.new.table('music.amg.artists').select('artist_id')})
|
104
|
+
@builder.query.conditions.include?("artist_id in (select artist_id from music.amg.artists)").should be_true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#sort()" do
|
109
|
+
it "should set the sort" do
|
110
|
+
@builder.sort('nickname')
|
111
|
+
@builder.query.sort.should == 'nickname'
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should return the builder" do
|
115
|
+
@builder.sort('nickname').should be_kind_of(YqlQuery::Builder)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#sort_descending()" do
|
120
|
+
it "should set the sort" do
|
121
|
+
@builder.sort_descending('nickname')
|
122
|
+
@builder.query.sort.should == 'nickname'
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should return the builder" do
|
126
|
+
@builder.sort('nickname').should be_kind_of(YqlQuery::Builder)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#tail()" do
|
131
|
+
it "should set the tail" do
|
132
|
+
@builder.tail(3)
|
133
|
+
@builder.query.tail.should == 3
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should return the builder" do
|
137
|
+
@builder.tail(3).should be_kind_of(YqlQuery::Builder)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#truncate()" do
|
142
|
+
it "should set the truncate option" do
|
143
|
+
@builder.truncate(3)
|
144
|
+
@builder.query.truncate.should == 3
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should return the builder" do
|
148
|
+
@builder.truncate(3).should be_kind_of(YqlQuery::Builder)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "#reverse" do
|
153
|
+
it "should set the reverse option" do
|
154
|
+
@builder.reverse
|
155
|
+
@builder.query.reverse.should be_true
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should return the builder" do
|
159
|
+
@builder.reverse.should be_kind_of(YqlQuery::Builder)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#unique()" do
|
164
|
+
it "should set the unique option" do
|
165
|
+
@builder.unique("Rating.AverageRating")
|
166
|
+
@builder.query.unique.should == "Rating.AverageRating"
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should return the builder" do
|
170
|
+
@builder.unique("Rating.AverageRating").should be_kind_of(YqlQuery::Builder)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe "#sanitize()" do
|
175
|
+
it "should set the unique option" do
|
176
|
+
@builder.sanitize("Rating.Description")
|
177
|
+
@builder.query.sanitize.should == "Rating.Description"
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should default sanitize to true" do
|
181
|
+
@builder.sanitize
|
182
|
+
@builder.query.sanitize.should be_true
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should return the builder" do
|
186
|
+
@builder.sanitize("Rating.Description").should be_kind_of(YqlQuery::Builder)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "chaining" do
|
191
|
+
it "should combine query options" do
|
192
|
+
@builder.table('music.artists').limit(5).conditions("name = 'Jose James'")
|
193
|
+
@builder.query.limit.should == 5
|
194
|
+
@builder.query.table.should == 'music.artists'
|
195
|
+
@builder.query.conditions.include?("name = 'Jose James'").should be_true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe "#to_s" do
|
200
|
+
it "should return the generated query" do
|
201
|
+
@builder.table('music.artists').limit(5).conditions("name = 'Jose James'")
|
202
|
+
@builder.to_s.should == "select * from music.artists where name = 'Jose James' limit 5"
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should be aliased as to_query" do
|
206
|
+
@builder.table('music.artists').limit(5).conditions("name = 'Jose James'")
|
207
|
+
@builder.to_s.should == @builder.to_query
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe "#reset" do
|
212
|
+
it "should reset all query conditions" do
|
213
|
+
@builder.table('Rating')
|
214
|
+
@builder.limit(5)
|
215
|
+
@builder.offset(5)
|
216
|
+
@builder.select('Rating.Description, Rating.SomethingElse')
|
217
|
+
@builder.use('http://namedata.com', 'names')
|
218
|
+
@builder.conditions("name = 'fred'")
|
219
|
+
@builder.sort('nickname')
|
220
|
+
@builder.tail(5)
|
221
|
+
@builder.truncate(3)
|
222
|
+
@builder.reverse
|
223
|
+
@builder.unique("Rating.AverageRating")
|
224
|
+
@builder.sanitize("Rating.Description")
|
225
|
+
|
226
|
+
@builder.reset
|
227
|
+
|
228
|
+
@builder.query.table.should be_nil
|
229
|
+
@builder.query.limit.should be_nil
|
230
|
+
@builder.query.offset.should be_nil
|
231
|
+
@builder.query.select.should be_nil
|
232
|
+
@builder.query.limit.should be_nil
|
233
|
+
@builder.query.uses.should be_empty
|
234
|
+
@builder.query.conditions.should be_empty
|
235
|
+
@builder.query.sort.should be_nil
|
236
|
+
@builder.query.tail.should be_nil
|
237
|
+
@builder.query.truncate.should be_nil
|
238
|
+
@builder.query.reverse.should be_nil
|
239
|
+
@builder.query.unique.should be_nil
|
240
|
+
@builder.query.sanitize.should be_nil
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
context "generating queries" do
|
245
|
+
before(:each) do
|
246
|
+
@builder.table('music.artists')
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should generate ther right query when given a different data source to use" do
|
250
|
+
@builder.conditions("name = 'Jose James'").use('http://musicbrainz.org/data.xml', 'musicBrainzArtists').table('musicBrainzArtists')
|
251
|
+
@builder.to_s.should == "use http://musicbrainz.org/data.xml as musicBrainzArtists; select * from musicBrainzArtists where name = 'Jose James'"
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should generate ther right query when given multiple data sources to use" do
|
255
|
+
@builder.conditions("name = 'Jose James'").use('http://musicbrainz.org/data.xml', 'musicBrainzArtists').use('http://musicbrainz.org/albums.xml', 'musicBrainzAlbums').table('musicBrainzArtists')
|
256
|
+
@builder.to_s.should == "use http://musicbrainz.org/data.xml as musicBrainzArtists; use http://musicbrainz.org/albums.xml as musicBrainzAlbums; select * from musicBrainzArtists where name = 'Jose James'"
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should generate the right query when given a single condition" do
|
260
|
+
@builder.conditions("name = 'Jose James'")
|
261
|
+
@builder.to_s.should == "select * from music.artists where name = 'Jose James'"
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should generate the right query when given multiple conditions" do
|
265
|
+
@builder.conditions("name = 'Jose James'").conditions("genre = 'Jazz'")
|
266
|
+
@builder.to_s.should == "select * from music.artists where name = 'Jose James' and genre = 'Jazz'"
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should generate the right query when given a limit" do
|
270
|
+
@builder.conditions("name = 'John'").limit(5)
|
271
|
+
@builder.to_s.should == "select * from music.artists where name = 'John' limit 5"
|
272
|
+
end
|
273
|
+
|
274
|
+
it "should generate the right query when given an offset" do
|
275
|
+
@builder.conditions("name = 'John'").offset(15)
|
276
|
+
@builder.to_s.should == "select * from music.artists where name = 'John' offset 15"
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should generate the right query when given a select" do
|
280
|
+
@builder.conditions("name = 'John'").select('Title, First Name, Email')
|
281
|
+
@builder.to_s.should == "select Title, First Name, Email from music.artists where name = 'John'"
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should generate the right query when given a select as an array" do
|
285
|
+
@builder.conditions("name = 'John'").select(['age', 'sex', 'language', 'other'])
|
286
|
+
@builder.to_s.should == "select age, sex, language, other from music.artists where name = 'John'"
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should generate the right query when given a sort" do
|
290
|
+
@builder.sort('popularity')
|
291
|
+
@builder.to_s.should == "select * from music.artists | sort(field='popularity')"
|
292
|
+
end
|
293
|
+
|
294
|
+
it "should generate the right query when given a sort descending" do
|
295
|
+
@builder.sort_descending('popularity')
|
296
|
+
@builder.to_s.should == "select * from music.artists | sort(field='popularity', descending='true')"
|
297
|
+
end
|
298
|
+
|
299
|
+
it "should generate the right query when given a tail" do
|
300
|
+
@builder.tail(5)
|
301
|
+
@builder.to_s.should == "select * from music.artists | tail(count=5)"
|
302
|
+
end
|
303
|
+
|
304
|
+
it "should generate the right query when given a truncate" do
|
305
|
+
@builder.truncate(5)
|
306
|
+
@builder.to_s.should == "select * from music.artists | truncate(count=5)"
|
307
|
+
end
|
308
|
+
|
309
|
+
it "should generate the right query when given a reverse" do
|
310
|
+
@builder.reverse
|
311
|
+
@builder.to_s.should == "select * from music.artists | reverse()"
|
312
|
+
end
|
313
|
+
|
314
|
+
it "should generate the right query when given a unique" do
|
315
|
+
@builder.unique('art')
|
316
|
+
@builder.to_s.should == "select * from music.artists | unique(field='art')"
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should generate the right query when given a sanitize" do
|
320
|
+
@builder.sanitize('art')
|
321
|
+
@builder.to_s.should == "select * from music.artists | sanitize(field='art')"
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should generate the right query when given a sanitize all fields" do
|
325
|
+
@builder.sanitize
|
326
|
+
@builder.to_s.should == "select * from music.artists | sanitize()"
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe YqlQuery::Source do
|
4
|
+
before(:each) do
|
5
|
+
@use_case = YqlQuery::Source.new('http://geodata.org/table.xml', 'geodata')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should be equivalent if both the source and as are equal" do
|
9
|
+
@use_case.should == YqlQuery::Source.new('http://geodata.org/table.xml', 'geodata')
|
10
|
+
end
|
11
|
+
end
|
data/yql-query.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "yql_query/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "yql-query"
|
7
|
+
s.version = YqlQuery::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Steve Agalloco"]
|
10
|
+
s.email = ["steve.agalloco@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/spagalloco/yql-query"
|
12
|
+
s.summary = %q{A YQL Query generator}
|
13
|
+
s.description = %q{A YQL Query generator}
|
14
|
+
|
15
|
+
s.rubyforge_project = "yql-query"
|
16
|
+
|
17
|
+
s.add_runtime_dependency('hashie', '~> 1.0.0')
|
18
|
+
|
19
|
+
s.add_development_dependency('bundler', '~> 1.0')
|
20
|
+
s.add_development_dependency('rake', '~> 0.8')
|
21
|
+
s.add_development_dependency('rspec', '~> 2.5.0')
|
22
|
+
s.add_development_dependency('yard', '~> 0.6')
|
23
|
+
s.add_development_dependency('maruku', '~> 0.6')
|
24
|
+
s.add_development_dependency('simplecov', '~> 0.3')
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
28
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yql-query
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Steve Agalloco
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-22 00:00:00 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: hashie
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.0.0
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "1.0"
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rake
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0.8"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rspec
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 2.5.0
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: yard
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0.6"
|
69
|
+
type: :development
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: maruku
|
73
|
+
prerelease: false
|
74
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ~>
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0.6"
|
80
|
+
type: :development
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: simplecov
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ~>
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0.3"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id007
|
93
|
+
description: A YQL Query generator
|
94
|
+
email:
|
95
|
+
- steve.agalloco@gmail.com
|
96
|
+
executables: []
|
97
|
+
|
98
|
+
extensions: []
|
99
|
+
|
100
|
+
extra_rdoc_files: []
|
101
|
+
|
102
|
+
files:
|
103
|
+
- .gitignore
|
104
|
+
- .rspec
|
105
|
+
- Gemfile
|
106
|
+
- License.md
|
107
|
+
- Rakefile
|
108
|
+
- Readme.md
|
109
|
+
- lib/yql-query.rb
|
110
|
+
- lib/yql_query.rb
|
111
|
+
- lib/yql_query/builder.rb
|
112
|
+
- lib/yql_query/query.rb
|
113
|
+
- lib/yql_query/source.rb
|
114
|
+
- lib/yql_query/version.rb
|
115
|
+
- spec/spec_helper.rb
|
116
|
+
- spec/yql_query_builder_spec.rb
|
117
|
+
- spec/yql_query_source_spec.rb
|
118
|
+
- yql-query.gemspec
|
119
|
+
has_rdoc: true
|
120
|
+
homepage: https://github.com/spagalloco/yql-query
|
121
|
+
licenses: []
|
122
|
+
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: "0"
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
none: false
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: "0"
|
140
|
+
requirements: []
|
141
|
+
|
142
|
+
rubyforge_project: yql-query
|
143
|
+
rubygems_version: 1.5.0
|
144
|
+
signing_key:
|
145
|
+
specification_version: 3
|
146
|
+
summary: A YQL Query generator
|
147
|
+
test_files:
|
148
|
+
- spec/spec_helper.rb
|
149
|
+
- spec/yql_query_builder_spec.rb
|
150
|
+
- spec/yql_query_source_spec.rb
|