smql 0.0.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/AUTHORS +1 -0
- data/LICENSE +674 -0
- data/README.md +27 -0
- data/TODO +1 -0
- data/VERSION +1 -0
- data/lib/smql.rb +7 -0
- data/lib/smql_to_ar/condition_types.rb +327 -0
- data/lib/smql_to_ar/query_builder.rb +160 -0
- data/lib/smql_to_ar.rb +213 -0
- metadata +113 -0
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Idea
|
2
|
+
====
|
3
|
+
|
4
|
+
Similar to MQL: SMQL allowes SQL-queries on your database but in a JSON-based language.
|
5
|
+
|
6
|
+
This query language is SQL-injection-safe.
|
7
|
+
Only expencive queries can slow down your machine.
|
8
|
+
|
9
|
+
Usage
|
10
|
+
=====
|
11
|
+
|
12
|
+
Easy query in ruby:
|
13
|
+
User is a AR-Model and has a column username.
|
14
|
+
We want to find all users which has the username "auser".
|
15
|
+
|
16
|
+
require 'smql'
|
17
|
+
|
18
|
+
SmqlToAR.to_ar User, '{"username": "auser"}' # Query in JSON
|
19
|
+
SmqlToAR.to_ar User, username: "auser" # Query in Ruby
|
20
|
+
|
21
|
+
In Rails:
|
22
|
+
|
23
|
+
SmqlToAR.to_ar User, params[:smql]
|
24
|
+
|
25
|
+
Don't forget to add gem to Gemfile:
|
26
|
+
|
27
|
+
gem 'smql'
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/lib/smql.rb
ADDED
@@ -0,0 +1,327 @@
|
|
1
|
+
# SmqlToAR - Parser: Converts SMQL to ActiveRecord
|
2
|
+
# Copyright (C) 2011 Denis Knauf
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
class SmqlToAR
|
18
|
+
#############################################################################
|
19
|
+
# Alle Subklassen (qualitativ: ConditionTypes::*), die als Superklasse Condition haben,
|
20
|
+
# stellen eine Regel dar, unter diesen sie das gesuchte Objekt annehmen.
|
21
|
+
# Nimmt eine solche Klasse ein Object nicht an, so wird die naechste Klasse ausprobiert.
|
22
|
+
# Es wird in der Reihenfolge abgesucht, in der #constants die Klassen liefert,
|
23
|
+
# wobei angenommen wird, dass diese nach dem Erstellungszeitpunkt sortiert sind,
|
24
|
+
# aeltere zuerst.
|
25
|
+
# Nimmt eine Klasse ein Objekt an, so soll diese Klasse instanziert werden.
|
26
|
+
# Alles weitere siehe Condition.
|
27
|
+
module ConditionTypes
|
28
|
+
class <<self
|
29
|
+
# Ex: 'givenname|surname|nick' => [:givenname, :surname, :nick]
|
30
|
+
def split_keys k
|
31
|
+
k.split( '|').collect &:to_sym
|
32
|
+
end
|
33
|
+
|
34
|
+
# Eine Regel parsen.
|
35
|
+
# Ex: Person, "givenname=", "Peter"
|
36
|
+
def try_parse_it model, colop, val
|
37
|
+
r = nil
|
38
|
+
#p :try_parse => { :model => model, :colop => colop, :value => val }
|
39
|
+
constants.each do |c|
|
40
|
+
next if :Condition == c
|
41
|
+
c = const_get c
|
42
|
+
next if Condition === c
|
43
|
+
raise UnexpectedColOpError.new( model, colop, val) unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/
|
44
|
+
col, op = $1, $2
|
45
|
+
col = split_keys( col).collect {|c| Column.new model, c }
|
46
|
+
r = c.try_parse model, col, op, val
|
47
|
+
break if r
|
48
|
+
end
|
49
|
+
raise UnexpectedError.new( model, colop, val) unless r
|
50
|
+
r
|
51
|
+
end
|
52
|
+
|
53
|
+
# Alle Regeln parsen. Die Regeln sind in einem Hash der Form {colop => val}
|
54
|
+
# Ex: Person, {"givenname=", "Peter", "surname=", "Mueller"}
|
55
|
+
def try_parse model, colopvals
|
56
|
+
colopvals.collect do |colop, val|
|
57
|
+
#p :try_parse => { colop: colop, val: val, model: model }
|
58
|
+
try_parse_it model, colop, val
|
59
|
+
end
|
60
|
+
rescue SMQLError => e
|
61
|
+
raise SubSMQLError.new( colopvals, model, e)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Erstellt eine Condition fuer eine Regel.
|
65
|
+
def simple_condition superclass, op = nil, where = nil, expected = nil
|
66
|
+
cl = Class.new superclass
|
67
|
+
cl.const_set :Operator, op if op
|
68
|
+
cl.const_set :Where, where if where
|
69
|
+
cl.const_set :Expected, expected if expected
|
70
|
+
cl
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Condition
|
75
|
+
attr_reader :value, :cols
|
76
|
+
Operator = nil
|
77
|
+
Expected = []
|
78
|
+
Where = nil
|
79
|
+
|
80
|
+
# Versuche das Objekt zu erkennen. Operator und Expected muessen passen.
|
81
|
+
# Passt das Object, die Klasse instanzieren.
|
82
|
+
def self.try_parse model, cols, op, val
|
83
|
+
#p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name
|
84
|
+
new model, cols, val if self::Operator === op and self::Expected.any? {|i| i === val }
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize model, cols, val
|
88
|
+
@model, @cols = model, cols
|
89
|
+
@value = case val
|
90
|
+
when Hash, Range then val
|
91
|
+
else Array.wrap val
|
92
|
+
end
|
93
|
+
verify
|
94
|
+
end
|
95
|
+
|
96
|
+
def verify
|
97
|
+
@cols.each do |col|
|
98
|
+
verify_column col
|
99
|
+
verify_allowed col
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Gibt es eine Spalte diesen Namens?
|
104
|
+
# Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall)
|
105
|
+
def verify_column col
|
106
|
+
raise NonExistingColumnError.new( %w[Column], col) unless col.exist_in?
|
107
|
+
end
|
108
|
+
|
109
|
+
# Modelle koennen Spalten/Relationen verbieten mit Model#smql_protected.
|
110
|
+
# Dieses muss ein Object mit #include?( name_als_string) zurueckliefern,
|
111
|
+
# welches true fuer verboten und false fuer, erlaubt steht.
|
112
|
+
def verify_allowed col
|
113
|
+
raise ProtectedColumnError.new( col) if col.protected?
|
114
|
+
end
|
115
|
+
|
116
|
+
# Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
|
117
|
+
# wobei builder.join, builder.select, builder.where und builder.wobs von interesse sind.
|
118
|
+
# mehrere Schluessel bedeuten, dass die Values _alle_ zutreffen muessen, wobei die Schluessel geodert werden.
|
119
|
+
# Ex:
|
120
|
+
# 1) {"givenname=", "Peter"} #=> givenname = 'Peter'
|
121
|
+
# 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' )
|
122
|
+
# 3) {"givenname|surname=", ["Peter", "Mueller"]}
|
123
|
+
# #=> ( givenname = 'Peter' OR surname = 'Peter' ) AND ( givenname = 'Mueller' OR surname = 'Mueller' )
|
124
|
+
def build builder, table
|
125
|
+
values = Hash[ @value.collect {|value| [ builder.vid, value ] } ]
|
126
|
+
values.each {|k, v| builder.wobs k.sym => v }
|
127
|
+
if 1 == @cols.length
|
128
|
+
@cols.each do |col|
|
129
|
+
col.joins builder, table
|
130
|
+
col = builder.column table+col.path, col.col
|
131
|
+
builder.where *values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] }
|
132
|
+
end
|
133
|
+
else
|
134
|
+
values.keys.each do |vid|
|
135
|
+
builder.where *@cols.collect {|col|
|
136
|
+
col.joins builder, table
|
137
|
+
col = builder.column table+col.path, col.col
|
138
|
+
self.class::Where % [ col, vid.to_s ]
|
139
|
+
}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
self
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class NotInRange < Condition
|
147
|
+
Operator = '!..'
|
148
|
+
Where = "%s NOT BETWEEN %s AND %s"
|
149
|
+
Expected = [Range, lambda {|val| Array === val && 2 == val.length } ]
|
150
|
+
|
151
|
+
def initialze model, cols, val
|
152
|
+
if Array === val && 2 == val.length
|
153
|
+
f, l = val
|
154
|
+
f, l = Time.parse(f), Time.parse(l) if f.kind_of? String
|
155
|
+
val = f..l
|
156
|
+
end
|
157
|
+
super model, cols, val
|
158
|
+
end
|
159
|
+
|
160
|
+
def build builder, table
|
161
|
+
builder.wobs (v1 = builder.vid) => @value.begin, (v2 = builder.vid) => @value.end
|
162
|
+
@cols.each do |col|
|
163
|
+
col.joins builder, table
|
164
|
+
builder.where self.class::Where % [ builder.column( table+col.path, col.col), v1, v2]
|
165
|
+
end
|
166
|
+
self
|
167
|
+
end
|
168
|
+
end
|
169
|
+
InRange = simple_condition NotInRange, '..', "%s BETWEEN %s AND %s"
|
170
|
+
|
171
|
+
class NotIn < Condition
|
172
|
+
Operator = '!|='
|
173
|
+
Where = "%s NOT IN (%s)"
|
174
|
+
Expected = [Array]
|
175
|
+
|
176
|
+
def build builder, table
|
177
|
+
builder.wobs (v = builder.vid).to_sym => @value
|
178
|
+
@cols.each do |col|
|
179
|
+
col.joins builder, table
|
180
|
+
builder.where self.class::Where % [ builder.column( table, col), v.to_s]
|
181
|
+
end
|
182
|
+
self
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
In = simple_condition NotIn, '|=', '%s IN (%s)', [Array]
|
187
|
+
In2 = simple_condition In, '', nil, [Array]
|
188
|
+
NotEqual = simple_condition Condition, /\!=|<>/, "%s <> %s", [Array, String, Numeric]
|
189
|
+
GreaterThanOrEqual = simple_condition Condition, '>=', "%s >= %s", [Array, Numeric]
|
190
|
+
LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric]
|
191
|
+
class EqualJoin <Condition
|
192
|
+
Operator = '='
|
193
|
+
Expected = [Hash]
|
194
|
+
|
195
|
+
def initialize *pars
|
196
|
+
super( *pars)
|
197
|
+
cols = {}
|
198
|
+
@cols.each do |col|
|
199
|
+
col_model = SmqlToAR.model_of col.last_model, col.col
|
200
|
+
#p col_model: col_model.to_s, value: @value
|
201
|
+
cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value)
|
202
|
+
end
|
203
|
+
@cols = cols
|
204
|
+
end
|
205
|
+
|
206
|
+
def verify_column col
|
207
|
+
refl = SmqlToAR.model_of col.last_model, col.col
|
208
|
+
#p refl: refl, model: @model.name, col: col, :reflections => @model.reflections.keys
|
209
|
+
raise NonExistingRelationError.new( %w[Relation], col) unless refl
|
210
|
+
end
|
211
|
+
|
212
|
+
def build builder, table
|
213
|
+
@cols.each do |col, sub|
|
214
|
+
t = table + col.path + [col.col]
|
215
|
+
#p sub: sub
|
216
|
+
p col: col, joins: col.joins
|
217
|
+
col.joins.each {|j, m| builder.join table+j, m }
|
218
|
+
builder.join t, SmqlToAR.model_of( col.last_model, col.col)
|
219
|
+
sub[1..-1].each {|one| one.build builder, t }
|
220
|
+
end
|
221
|
+
self
|
222
|
+
end
|
223
|
+
end
|
224
|
+
Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric]
|
225
|
+
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
|
226
|
+
GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
|
227
|
+
LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric]
|
228
|
+
NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String]
|
229
|
+
Ilike = simple_condition Condition, '~', "%s ILIKE %s", [Array, String]
|
230
|
+
|
231
|
+
####### No Operator #######
|
232
|
+
Join = simple_condition EqualJoin, '', nil, [Hash]
|
233
|
+
InRange2 = simple_condition InRange, '', nil, [Range]
|
234
|
+
class Select < Condition
|
235
|
+
Operator = ''
|
236
|
+
Expected = [nil]
|
237
|
+
|
238
|
+
def verify_column col
|
239
|
+
raise NonExistingSelectableError.new( col) unless col.exist_in? or SmqlToAR.model_of( col.last_model, col.col)
|
240
|
+
end
|
241
|
+
|
242
|
+
def build builder, table
|
243
|
+
@cols.each do |col|
|
244
|
+
if col.exist_in?
|
245
|
+
col.joins builder, table
|
246
|
+
builder.select table+col.to_a
|
247
|
+
else
|
248
|
+
col.joins {|j, m| builder.includes table+j }
|
249
|
+
builder.includes table+col.to_a
|
250
|
+
end
|
251
|
+
end
|
252
|
+
self
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class Functions < Condition
|
257
|
+
Operator = ':'
|
258
|
+
Expected = [String, Array, Hash, Numeric, nil]
|
259
|
+
|
260
|
+
class Function
|
261
|
+
Name = nil
|
262
|
+
Expected = []
|
263
|
+
attr_reader :model, :func, :args
|
264
|
+
|
265
|
+
def self.try_parse model, func, args
|
266
|
+
SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
|
267
|
+
self.new model, func, args if self::Name === func and self::Expected.any? {|e| e === args }
|
268
|
+
end
|
269
|
+
|
270
|
+
def initialize model, func, args
|
271
|
+
@model, @func, @args = model, func, args
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
class Order < Function
|
276
|
+
Name = :order
|
277
|
+
Expected = [String, Array, Hash, nil]
|
278
|
+
|
279
|
+
def initialize model, func, args
|
280
|
+
SmqlToAR.logger.info( {args: args}.inspect)
|
281
|
+
args = case args
|
282
|
+
when String then [args]
|
283
|
+
when Array, Hash then args.to_a
|
284
|
+
when nil then nil
|
285
|
+
else raise 'Oops'
|
286
|
+
end
|
287
|
+
SmqlToAR.logger.info( {args: args}.inspect)
|
288
|
+
args.andand.collect! do |o|
|
289
|
+
o = Array.wrap o
|
290
|
+
col = Column.new model, o.first
|
291
|
+
o = 'desc' == o.last.to_s.downcase ? :DESC : :ASC
|
292
|
+
raise NonExistingColumnError.new( [:Column], col) unless col.exist_in?
|
293
|
+
[col, o]
|
294
|
+
end
|
295
|
+
SmqlToAR.logger.info( {args: args}.inspect)
|
296
|
+
super model, func, args
|
297
|
+
end
|
298
|
+
|
299
|
+
def build builder, table
|
300
|
+
return if @args.blank?
|
301
|
+
@args.each do |o|
|
302
|
+
col, o = o
|
303
|
+
col.joins builder, table
|
304
|
+
t = table + col.path
|
305
|
+
raise OnlyOrderOnBaseError.new( t) unless 1 == t.length
|
306
|
+
builder.order t, col.col, o
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def self.new model, col, val
|
312
|
+
SmqlToAR.logger.info( { function: col.first.to_sym }.inspect)
|
313
|
+
r = nil
|
314
|
+
constants.each do |c|
|
315
|
+
next if [:Function, :Where, :Expected, :Operator].include? c
|
316
|
+
c = const_get c
|
317
|
+
next if Function === c or not c.respond_to?( :try_parse)
|
318
|
+
SmqlToAR.logger.info( {f: c}.inspect)
|
319
|
+
r = c.try_parse model, col.first.to_sym, val
|
320
|
+
SmqlToAR.logger.info( {r: r}.inspect)
|
321
|
+
break if r
|
322
|
+
end
|
323
|
+
r
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# SmqlToAR - Builds AR-querys: Converts SMQL to ActiveRecord
|
2
|
+
# Copyright (C) 2011 Denis Knauf
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
class SmqlToAR
|
18
|
+
#######################################################################################
|
19
|
+
# Baut die Queries zusammen.
|
20
|
+
class QueryBuilder
|
21
|
+
# Erzeugt einen eindeutigen Identikator "cX", wobei X iteriert wird.
|
22
|
+
class Vid
|
23
|
+
attr_reader :vid
|
24
|
+
def initialize( vid) @vid = vid end
|
25
|
+
def to_s() ":c#{@vid}" end
|
26
|
+
def to_sym() "c#{@vid}".to_sym end
|
27
|
+
alias sym to_sym
|
28
|
+
def to_i() @vid end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins
|
32
|
+
attr_accessor :logger
|
33
|
+
@@logger = SmqlToAR.logger
|
34
|
+
|
35
|
+
def initialize model
|
36
|
+
@logger = @@logger
|
37
|
+
@table_alias = Hash.new do |h, k|
|
38
|
+
k = Array.wrap k
|
39
|
+
h[k] = "smql,#{k.join(',')}"
|
40
|
+
end
|
41
|
+
@_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
|
42
|
+
@base_table = [model.table_name.to_sym]
|
43
|
+
@table_alias[ @base_table] = @base_table.first
|
44
|
+
t = quote_table_name @table_alias[ @base_table]
|
45
|
+
@_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
|
46
|
+
@table_model = {@base_table => @model}
|
47
|
+
end
|
48
|
+
|
49
|
+
def vid() Vid.new( @_vid+=1) end
|
50
|
+
|
51
|
+
# Jede via where uebergebene Condition wird geodert und alle zusammen werden geundet.
|
52
|
+
# "Konjunktive Normalform". Allerdings duerfen Conditions auch Komplexe Abfragen enthalten.
|
53
|
+
# Ex: builder.where( 'a = a', 'b = c').where( 'c = d', 'e = e').where( 'x = y').where( '( m = n AND o = p )', 'f = g')
|
54
|
+
# #=> WHERE ( a = a OR b = c ) AND ( c = d OR e = e ) AND x = y ( ( m = n AND o = p ) OR f = g )
|
55
|
+
def where *cond
|
56
|
+
@_where.push cond
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def wobs vals
|
61
|
+
@_wobs.update vals
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def quote_column_name name
|
66
|
+
@quoter.quote_column_name( name).gsub /"\."/, ','
|
67
|
+
end
|
68
|
+
|
69
|
+
def quote_table_name name
|
70
|
+
@quoter.quote_table_name( name).gsub /"\."/, ','
|
71
|
+
end
|
72
|
+
|
73
|
+
def column table, name
|
74
|
+
"#{quote_table_name table.kind_of?(String) ? table : @table_alias[table]}.#{quote_column_name name}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_join orig, pretable, table, prekey, key
|
78
|
+
" JOIN #{quote_table_name orig.to_sym} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
|
79
|
+
end
|
80
|
+
|
81
|
+
def join table, model
|
82
|
+
return self if @_joined.include? table # Already joined
|
83
|
+
pretable = table[0...-1]
|
84
|
+
@table_model[ table] = model
|
85
|
+
premodel = @table_model[ pretable]
|
86
|
+
t = @table_alias[ table]
|
87
|
+
pt = quote_table_name @table_alias[ table[ 0...-1]]
|
88
|
+
refl = premodel.reflections[table.last]
|
89
|
+
case refl.macro
|
90
|
+
when :has_many
|
91
|
+
@_joins += build_join model.table_name, pretable, t, premodel.primary_key, refl.primary_key_name
|
92
|
+
when :belongs_to
|
93
|
+
@_joins += build_join model.table_name, pretable, t, refl.primary_key_name, premodel.primary_key
|
94
|
+
when :has_and_belongs_to_many
|
95
|
+
jointable = [','] + table
|
96
|
+
@_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name
|
97
|
+
@_joins += build_join model.table_name, jointable, t, refl.association_foreign_key, refl.association_primary_key
|
98
|
+
else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
|
99
|
+
end
|
100
|
+
@_joined.push table
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
def includes table
|
105
|
+
@_includes.push table
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def select col
|
110
|
+
@_select.push quote_column_name( @table_alias[col])
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
def order table, col, o
|
115
|
+
@_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}"
|
116
|
+
end
|
117
|
+
|
118
|
+
class Dummy
|
119
|
+
def method_missing m, *a, &e
|
120
|
+
#p :dummy => m, :pars => a, :block => e
|
121
|
+
self
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_ar
|
126
|
+
where_str = @_where.collect do |w|
|
127
|
+
w = Array.wrap w
|
128
|
+
1 == w.length ? w.first : "( #{w.join( ' OR ')} )"
|
129
|
+
end.join ' AND '
|
130
|
+
incls = {}
|
131
|
+
@_includes.each do |inc|
|
132
|
+
b = incls
|
133
|
+
inc[1..-1].collect {|rel| b = b[rel] ||= {} }
|
134
|
+
end
|
135
|
+
@logger.debug incls: incls, joins: @_joins
|
136
|
+
@model = @model.
|
137
|
+
select( @_select.join( ', ')).
|
138
|
+
joins( @_joins).
|
139
|
+
where( where_str, @_wobs).
|
140
|
+
order( @_order.join( ', ')).
|
141
|
+
includes( incls)
|
142
|
+
end
|
143
|
+
|
144
|
+
def fix_calculate
|
145
|
+
def @model.calculate operation, column_name, options = nil
|
146
|
+
options = options.try(:dup) || {}
|
147
|
+
options[:distinct] = true unless options.except(:distinct).present?
|
148
|
+
column_name = klass.primary_key unless column_name.present?
|
149
|
+
super operation, column_name, options
|
150
|
+
end
|
151
|
+
self
|
152
|
+
end
|
153
|
+
|
154
|
+
def to_ar
|
155
|
+
build_ar
|
156
|
+
fix_calculate
|
157
|
+
@model
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|