native-query 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ require 'rake'
15
+ require 'jeweler'
16
+
17
+ Jeweler::Tasks.new do |gem|
18
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
19
+ gem.name = "native-query"
20
+ gem.homepage = "http://github.com/martinkozak/native-query"
21
+ gem.license = "MIT"
22
+ gem.summary = 'Cool way how to speak with database server. It\'s ellegant and very ruby SQL query helper which works by similar way as Arel or another ORM selecting logic. It\'s derived from Dibi database layer in its ideas, so is much more simple and (of sure) much more KISS, readable and straightforward.'
23
+ gem.email = "martinkozak@martinkozak.net"
24
+ gem.authors = ["Martin Kozák"]
25
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
26
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
27
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
28
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
29
+ end
30
+ Jeweler::RubygemsDotOrgTasks.new
31
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.0
@@ -0,0 +1,318 @@
1
+ # encoding: utf-8
2
+ require "native-query/query"
3
+ require "hash-utils/object" # >= 0.17.0
4
+ require "hash-utils/array"
5
+ require "hash-utils/string" # >= 0.18.0
6
+
7
+ module NativeQuery
8
+
9
+ ##
10
+ # Represents join request.
11
+ #
12
+
13
+ class Join
14
+
15
+ ##
16
+ # Indicates table to join from.
17
+ #
18
+
19
+ @original
20
+
21
+ ##
22
+ # Indicates table to join.
23
+ #
24
+
25
+ @table
26
+
27
+ ##
28
+ # Indicates fields for select.
29
+ #
30
+
31
+ @fields
32
+
33
+ ##
34
+ # Holds joins specifiaction.
35
+ #
36
+
37
+ @joins
38
+ attr_reader :joins
39
+
40
+ ##
41
+ # Holds join conditions.
42
+ #
43
+
44
+ @where
45
+
46
+ ##
47
+ # Indicates type of joining.
48
+ # Possible values are:
49
+ #
50
+ # * :indirect for M:N relation,
51
+ # * :direct for 1:N relation
52
+ #
53
+
54
+ @type
55
+
56
+ ##
57
+ # Contains indirect joining manual specification.
58
+ #
59
+
60
+ @indirect
61
+
62
+ ##
63
+ # Holds specificarion of direct joining.
64
+ #
65
+
66
+ @direct
67
+
68
+ ##
69
+ # Constructor.
70
+ #
71
+
72
+ def initialize(original, table)
73
+ @table = table
74
+ @original = original
75
+ @fields = [ ]
76
+ @where = [ ]
77
+ @joins = [ ]
78
+ @type = :direct
79
+
80
+ @indirect_source = original
81
+ end
82
+
83
+ ##
84
+ # Sets hash for select from joined table.
85
+ # Without arguments returns fields list.
86
+ #
87
+
88
+ def fields(*args)
89
+ if args.empty?
90
+ result = { }
91
+ @fields.each do |i|
92
+ if not i.kind_of? Hash
93
+ i = {i => i}
94
+ end
95
+
96
+ i.each_pair do |from, to|
97
+ result[__fix_field(from)] = @table.to_s << "_" << to.to_s
98
+ end
99
+ end
100
+
101
+ return result
102
+ else
103
+ @fields += args
104
+ return self
105
+ end
106
+ end
107
+
108
+ ##
109
+ # Selects where conditions to load.
110
+ #
111
+
112
+ def where(*args)
113
+ @where << args
114
+ return self
115
+ end
116
+
117
+ ##
118
+ # Indicates indirect joining. (M:N)
119
+ #
120
+
121
+ def indirect(*args)
122
+ @type = :indirect
123
+ if args.first.array? and (args.first.first == :backward) and (args.first.second.array?)
124
+ @indirect = args.first
125
+ else
126
+ @indirect = args
127
+ end
128
+
129
+ return self
130
+ end
131
+
132
+ ##
133
+ # Indicates direct joining. (1:M)
134
+ #
135
+
136
+ def direct(*args)
137
+ @type = :direct
138
+ if args.first.array? and (args.first.first == :backward) and (args.first.second.array?)
139
+ @direct = args.first
140
+ else
141
+ @direct = args
142
+ end
143
+
144
+ return self
145
+ end
146
+
147
+ ##
148
+ # Indicates backward joining.
149
+ #
150
+
151
+ def backward(*args)
152
+ [:backward, args]
153
+ end
154
+
155
+ ##
156
+ # Builds ON join string.
157
+ #
158
+
159
+ def build
160
+ result = nil
161
+
162
+ case @type
163
+ when :indirect
164
+ result = __indirect
165
+ when :direct
166
+ result = __direct
167
+ end
168
+
169
+ return result
170
+ end
171
+
172
+ ##
173
+ # Return wheres.
174
+ #
175
+
176
+ def wheres
177
+ self._fix_where
178
+ end
179
+
180
+ ##
181
+ # Calls mapping to joins specification. Call name is name of
182
+ # the target table.
183
+ #
184
+ # Block works by the same way as query, but for join. But
185
+ # intra-join calls doesn't work because it returns Query too.
186
+ #
187
+
188
+ def method_missing(sym, *args, &block)
189
+ join = self.class::new(@table, sym)
190
+
191
+ if args and not args.empty?
192
+ join.fields(*args)
193
+ end
194
+
195
+ join.instance_eval(&block)
196
+ @joins << join
197
+
198
+ return self
199
+ end
200
+
201
+ ##
202
+ # Fixes field name. Joins table name if SQL table joining
203
+ # required.
204
+ #
205
+
206
+ private
207
+ def __fix_field(name, formatted = false)
208
+ NativeQuery::Query::fix_field(name, @table, formatted)
209
+ end
210
+
211
+
212
+ ##
213
+ # Fixes where specification(s) if it's hash with symbol key.
214
+ #
215
+
216
+ protected
217
+ def _fix_where
218
+ NativeQuery::Query::fix_conditions(@where) do |args|
219
+ args.each do |i|
220
+ __fix_field(i)
221
+ end
222
+ end
223
+ end
224
+
225
+ ##
226
+ # Builds indirect join.
227
+ #
228
+
229
+ private
230
+ def __indirect
231
+
232
+ if (@indirect.first == :backward) and (@indirect.second.array?)
233
+ backward = true
234
+ indirect = @indirect.second
235
+ else
236
+ backward = false
237
+ indirect = @indirect
238
+ end
239
+
240
+ ##
241
+
242
+ result = { }
243
+ to = @table.to_s
244
+ from = @original.to_s
245
+ arg1, arg2, arg3 = indirect
246
+
247
+ # automatic joining
248
+ if indirect.empty?
249
+ from.swap_with(to) if backward
250
+ through = from + "_" + to
251
+ joining_table = through.to_sym
252
+ result[joining_table] = "[" << from << ".id] = [" << through << "." << from << "_id]"
253
+ result[@table] = "[" << through << "." << to << "_id] = [" << to << ".id]"
254
+
255
+ # standard specification (semiautomatic joining)
256
+ elsif arg1.symbol? and arg2.hash?
257
+ through = arg1.to_s
258
+ joining_table = arg1
259
+ result[joining_table] = "[" << from << "." << arg2.keys.first.to_s << "] = [" << through << "." << from << "_id]"
260
+ result[@table] = "[" << through << "." << to << "_id] = [" << to << "." << arg2.values.first.to_s << "]"
261
+
262
+ # fluent query specification (manual joining)
263
+ elsif arg1.symbol? and arg2.string? and arg3.string?
264
+ joining_table = arg1
265
+ result[joining_table] = arg2
266
+ result[@table] = arg3
267
+
268
+ # error
269
+ else
270
+ raise Exception::new("Symbol and Hash or Symbol and two Strings expected.")
271
+
272
+ end
273
+
274
+ return result
275
+ end
276
+
277
+ ##
278
+ # Builds direct join.
279
+ #
280
+
281
+ private
282
+ def __direct
283
+
284
+ if (@direct.first == :backward) and (@direct.second.array?)
285
+ backward = true
286
+ direct = @direct.second
287
+ else
288
+ backward = false
289
+ direct = @direct
290
+ end
291
+
292
+ ##
293
+
294
+ empty = direct.empty?
295
+ direct = direct.first
296
+ from = @original.to_s
297
+ to = @table.to_s
298
+ result = { }
299
+
300
+ # automatic joining
301
+ if empty
302
+ from.swap_with(to) if backward
303
+ result[@table] = "[" << from << ".id] = [" << to << "." << from << "_id]"
304
+ # manual joining
305
+ elsif direct.hash?
306
+ result[@table] = "[" << from << "." << direct.keys.first.to_s << "] = [" << to << "." << direct.values.first.to_s << "]"
307
+ # special joining
308
+ elsif direct.string?
309
+ result[@table] = direct
310
+ # error
311
+ else
312
+ raise Exception::new("Hash or String expected.")
313
+ end
314
+
315
+ return result
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+ require "fluent-query/connection"
3
+ require "native-query/query"
4
+
5
+ module NativeQuery
6
+
7
+ ##
8
+ # Represents instance of ORM model.
9
+ #
10
+
11
+ class Model
12
+
13
+ ##
14
+ # Brings database connection object.
15
+ #
16
+
17
+ @connection
18
+
19
+ ##
20
+ # Brings driver setting.
21
+ #
22
+
23
+ @driver
24
+
25
+ ##
26
+ # Brings database connection configuration setting.
27
+ #
28
+
29
+ @configuration
30
+
31
+ ##
32
+ # Constructor.
33
+ #
34
+
35
+ def initialize(driver, configuration)
36
+ @driver = driver
37
+ @configuration = configuration
38
+ end
39
+
40
+ ##
41
+ # Returns connection.
42
+ #
43
+
44
+ def connection
45
+ if not @connection
46
+ @connection = FluentQuery::Connection::new(@driver, @configuration)
47
+ end
48
+
49
+ @connection # returns
50
+ end
51
+
52
+ ##
53
+ # Maps missing calls to tables.
54
+ #
55
+ # Arguments are expected to be field names, so given to
56
+ # field query method.
57
+ #
58
+
59
+ def method_missing(sym, *args, &block)
60
+ query = Query::new(self.connection, sym)
61
+
62
+ if args and not args.empty?
63
+ query.fields(*args)
64
+ end
65
+
66
+ if block
67
+ result = query.instance_eval(&block)
68
+ else
69
+ result = query
70
+ end
71
+
72
+ return result
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,430 @@
1
+ # encoding: utf-8
2
+
3
+ require "native-query/result"
4
+ require "native-query/join"
5
+ require "hash-utils/object"
6
+ require "hash-utils/array"
7
+ require "hash-utils/hash"
8
+
9
+ module NativeQuery
10
+
11
+ ##
12
+ # Represents ORM query.
13
+ #
14
+
15
+ class Query
16
+
17
+ ##
18
+ # Brings database connection object.
19
+ #
20
+
21
+ @connection
22
+
23
+ ##
24
+ # Indicates upon which table query works.
25
+ #
26
+
27
+ @table
28
+
29
+ ##
30
+ # Holds list of fields to load.
31
+ #
32
+
33
+ @fields
34
+
35
+ ##
36
+ # Holds list of where conditions.
37
+ #
38
+
39
+ @where
40
+
41
+ ##
42
+ # Holds list of having conditions.
43
+ #
44
+
45
+ @having
46
+
47
+ ##
48
+ # Holds order specification.
49
+ #
50
+
51
+ @order
52
+
53
+ ##
54
+ # Joins for the query.
55
+ #
56
+
57
+ @joins
58
+
59
+ ##
60
+ # Indicates limit to load.
61
+ #
62
+
63
+ @limit
64
+
65
+ ##
66
+ # Indicates offset to load.
67
+ #
68
+
69
+ @offset
70
+
71
+ ##
72
+ # Holds group by settings.
73
+ #
74
+
75
+ @group
76
+
77
+ ##
78
+ # Constructor.
79
+ #
80
+
81
+ def initialize(connection, table, &block)
82
+ @connection = connection
83
+ @table = table
84
+ @fields = [ ]
85
+ @where = [ ]
86
+ @having = [ ]
87
+ @joins = [ ]
88
+ @order = [ ]
89
+ @group = [ ]
90
+ end
91
+
92
+ ##
93
+ # Calls mapping to joins specification. Call name is name of
94
+ # the target table.
95
+ #
96
+ # Block works by the same way as query, but for join. But
97
+ # intra-join calls doesn't work because it returns Query too.
98
+ #
99
+
100
+ def method_missing(sym, *args, &block)
101
+ join = Join::new(@table, sym)
102
+
103
+ if args and not args.empty?
104
+ join.fields(*args)
105
+ end
106
+
107
+ if not block.nil?
108
+ join.instance_eval(&block)
109
+ end
110
+
111
+ @joins << join
112
+ return self
113
+ end
114
+
115
+ ##
116
+ # Selects fields which to load.
117
+ #
118
+
119
+ def fields(*args)
120
+ @fields += args
121
+ return self
122
+ end
123
+
124
+ ##
125
+ # Selects where conditions to load.
126
+ #
127
+
128
+ def where(*args)
129
+ @where << args
130
+ return self
131
+ end
132
+
133
+ ##
134
+ # Selects having conditions to load.
135
+ #
136
+
137
+ def having(*args)
138
+ @having << args
139
+ return self
140
+ end
141
+
142
+ ##
143
+ # Selects fields for ordering according them.
144
+ # Default order is :asc.
145
+ #
146
+ # Expects [:<field>, [:asc|:desc]]+ arguments.
147
+ #
148
+
149
+ def order(*args)
150
+ @order += args
151
+ return self
152
+ end
153
+
154
+ ##
155
+ # Sets limit to load.
156
+ #
157
+
158
+ def limit(limit)
159
+ @limit = limit
160
+ return self
161
+ end
162
+
163
+ ##
164
+ # Sets offset to load.
165
+ #
166
+
167
+ def offset(offset)
168
+ @offset = offset
169
+ return self
170
+ end
171
+
172
+ ##
173
+ # Sets group by to load.
174
+ #
175
+
176
+ def group(*fields)
177
+ @group = fields
178
+ return self
179
+ end
180
+
181
+ ##
182
+ # Returns result object.
183
+ #
184
+
185
+ def get
186
+
187
+ # Builds query
188
+
189
+ # Process joins
190
+ join_fields, wheres, append_joins = self._process_joins
191
+
192
+ # Checkouts regular fields
193
+ if @joins.empty?
194
+ fields = @fields
195
+ hashes = [ ]
196
+ group = @group
197
+ else
198
+ fields = @fields.map { |i| __fix_field(i) }
199
+ hashes = fields.reject { |i| not i.hash? }
200
+ fields -= hashes
201
+ end
202
+
203
+
204
+ query = @connection.query
205
+
206
+ if not fields.empty?
207
+ query.select(fields)
208
+ end
209
+
210
+ if not join_fields.empty?
211
+ query.select(join_fields)
212
+ end
213
+
214
+ if not hashes.empty?
215
+ hashes.each do |hash|
216
+ query.select(hash)
217
+ end
218
+ end
219
+
220
+
221
+ query.from(@table)
222
+
223
+ # Appends joins
224
+ append_joins.call(query)
225
+
226
+ # Where conditions
227
+ wheres += self._fix_where
228
+ wheres.each { |i| query.where(*i) }
229
+
230
+ # Where conditions
231
+ havings = self._fix_having
232
+ havings.each { |i| query.having(*i) }
233
+
234
+ # Grouping, ordering and having settings
235
+ self._process_grouping(query)
236
+ self._process_ordering(query)
237
+
238
+ # Limit and offset
239
+ if not @limit.nil?
240
+ query.limit(@limit)
241
+ end
242
+
243
+ if not @offset.nil?
244
+ query.offset(@offset)
245
+ end
246
+
247
+ # Returns
248
+ return Result::new(query)
249
+
250
+ end
251
+
252
+ ##
253
+ # Builds itself to string.
254
+ #
255
+
256
+ def build
257
+ self.get.query.build
258
+ end
259
+
260
+ alias :"build!" :build
261
+
262
+ ##
263
+ # Process joins.
264
+ #
265
+
266
+ protected
267
+ def _process_joins
268
+ fields = { }
269
+ wheres = [ ]
270
+ specs = { }
271
+ joins = [ ]
272
+
273
+ new_joins = @joins.dup
274
+ add_joins = [ ]
275
+
276
+ # Agregates subjoins (e.g. joins from joins)
277
+ while not new_joins.empty?
278
+ new_joins.each do |join|
279
+ add_joins += join.joins
280
+ end
281
+
282
+ joins += new_joins
283
+ new_joins = add_joins
284
+ add_joins = [ ]
285
+ end
286
+
287
+ # Process joins
288
+ joins.each do |join|
289
+ fields.merge! join.fields
290
+ wheres += join.wheres
291
+ specs.merge! join.build
292
+ end
293
+
294
+ callback = Proc::new do |query|
295
+ specs.each_pair do |join, on|
296
+ query.join(join).on(on)
297
+ end
298
+ end
299
+
300
+ return fields, wheres, callback
301
+ end
302
+
303
+ ##
304
+ # Process ordering settngs.
305
+ #
306
+
307
+ protected
308
+ def _process_ordering(query)
309
+ # Ordering settings
310
+ if not @order.nil?
311
+ @order.each do |i|
312
+ case i
313
+ when :asc
314
+ query.asc
315
+ when :desc
316
+ query.desc
317
+ else
318
+ if i.array?
319
+ query.orderBy("[" << i.first.to_s << "." << i.second.to_s << "]")
320
+ else
321
+ query.orderBy(__fix_field(i, true))
322
+ end
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+
329
+ ##
330
+ # Process ordering settngs.
331
+ #
332
+
333
+ protected
334
+ def _process_grouping(query)
335
+ # Grouping settings
336
+ @group.each do |i|
337
+ if i.array?
338
+ query.groupBy("[" << i.first.to_s << "." << i.second.to_s << "]")
339
+ else
340
+ query.groupBy(__fix_field(i, true))
341
+ end
342
+ end
343
+ end
344
+
345
+ ##
346
+ # Fixes field name. Joins table name if SQL table joining
347
+ # required.
348
+ #
349
+
350
+ private
351
+ def __fix_field(name, formatted = false)
352
+ if not @joins.empty?
353
+ result = self.class::fix_field(name, @table, formatted)
354
+ else
355
+ result = name
356
+ end
357
+
358
+ return result
359
+ end
360
+
361
+ ##
362
+ # Fixes field name. Joins table name if SQL table joining
363
+ # required.
364
+ #
365
+
366
+ def self.fix_field(name, table, formatted = false)
367
+ if name.hash?
368
+ result = name.map_keys { |k| self.fix_field(k, table, formatted) }
369
+ else
370
+ result = table.to_s + "." + name.to_s
371
+ end
372
+
373
+ if formatted
374
+ result = "[" << result << "]"
375
+ end
376
+
377
+ return result
378
+ end
379
+
380
+ ##
381
+ # Fixes where specification(s) if it's hash with symbol key.
382
+ #
383
+
384
+ protected
385
+ def _fix_where
386
+ Query::fix_conditions(@where) do |args|
387
+ args.each do |i|
388
+ __fix_field(i)
389
+ end
390
+ end
391
+ end
392
+
393
+ ##
394
+ # Fixes having specification(s) if it's hash with symbol key.
395
+ #
396
+
397
+ protected
398
+ def _fix_having
399
+ Query::fix_conditions(@having) do |args|
400
+ args.each do |i|
401
+ __fix_field(i)
402
+ end
403
+ end
404
+ end
405
+
406
+ ##
407
+ # Fixes where specification(s) if it's hash with symbol key.
408
+ # Block is fixer.
409
+ #
410
+
411
+ def self.fix_conditions(where, &block)
412
+ where.map do |specification|
413
+ if specification.hash?
414
+ new = specification.map_keys do |k|
415
+ if k.symbol?
416
+ block.call(k)
417
+ else
418
+ k
419
+ end
420
+ end
421
+ else
422
+ new = specification
423
+ end
424
+
425
+ new # returns
426
+ end
427
+ end
428
+
429
+ end
430
+ end