smql 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|