kansas 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,361 @@
1
+ class Object
2
+ def expr_body
3
+ to_s
4
+ end
5
+ end
6
+
7
+ class Array
8
+ def expr_body
9
+ collect {|e| e.expr_body}.join(',')
10
+ end
11
+ end
12
+
13
+ class String
14
+ def expr_body
15
+ sql_escape(self)
16
+ end
17
+ end
18
+
19
+ class KSExpression
20
+ include DRbUndumped
21
+
22
+ class Context
23
+
24
+ attr_reader :tables, :joins, :select, :select_table, :sort_fields, :limits, :distinct
25
+
26
+ def initialize(*tables)
27
+ # @select = tables.collect {|t| t.table_name}
28
+ @select = tables[0].table_name
29
+ @select_table = tables[0]
30
+ @tables = []
31
+ @joins = []
32
+ @sort_fields = []
33
+ @limits = []
34
+ @distinct = {}
35
+ end
36
+ end
37
+
38
+ def select_sql
39
+ distinct_fields = []
40
+ fields = []
41
+ @context.select_table.fields.each_value do |f|
42
+ if @context.distinct[f]
43
+ distinct_fields.push "distinct(#{@context.select}.#{f})"
44
+ else
45
+ fields.push "#{@context.select}.#{f}"
46
+ end
47
+ end
48
+
49
+ fields = distinct_fields.concat(fields)
50
+
51
+ selectedTables = @context.tables.compact.flatten.uniq.join(',')
52
+ joinConstraints = @context.joins.compact.flatten.uniq.join(' AND ')
53
+ #selected_rows = @context.select.compact.flatten.uniq.collect {|t| "#{t}.*"}.join(',')
54
+
55
+ if joinConstraints != ""
56
+ joinConstraints << " AND "
57
+ end
58
+
59
+ #statement = "SELECT #{@context.select}.* FROM #{selectedTables} WHERE #{joinConstraints} #{expr_body}"
60
+ statement = "SELECT #{fields.join(',')} FROM #{selectedTables} WHERE #{joinConstraints} #{expr_body}"
61
+ statement << ' ORDER BY ' << @context.sort_fields.collect {|f| "#{f[0].respond_to?(:expr_body) ? f[0].expr_body : f[0].to_s} #{f[1]}"}.join(',') if @context.sort_fields.length > 0
62
+ statement << ' LIMIT ' << @context.limits.join(',') if @context.limits.length > 0
63
+
64
+ statement
65
+ end
66
+
67
+ alias :sql :select_sql
68
+
69
+ def count_sql
70
+ selectedTables = @context.tables.compact.flatten.uniq.join(',')
71
+ joinConstraints = @context.joins.compact.flatten.uniq.join(' AND ')
72
+ #selected_rows = @context.select.compact.flatten.uniq.collect {|t| "#{t}.*"}.join(',')
73
+
74
+ if joinConstraints != ""
75
+ joinConstraints << " AND "
76
+ end
77
+
78
+ statement = "SELECT count(*) FROM #{selectedTables} WHERE #{joinConstraints} #{expr_body}"
79
+ statement << ' ORDER BY ' << @context.sort_fields.collect {|f| "#{f[0].respond_to?(:expr_body) ? f[0].expr_body : f[0].to_s} #{f[1]}"}.join(',') if @context.sort_fields.length > 0
80
+ statement << ' LIMIT ' << @context.limits.join(',') if @context.limits.length > 0
81
+
82
+ statement
83
+ end
84
+
85
+ def delete_sql
86
+ selectedTables = @context.tables.compact.flatten.uniq.join(",")
87
+ joinConstraints = @context.joins.compact.flatten.uniq.join(" AND ")
88
+ if joinConstraints != ""
89
+ joinConstraints << " AND "
90
+ end
91
+
92
+ statement = "DELETE FROM #{selectedTables} WHERE #{joinConstraints} #{expr_body}"
93
+
94
+ statement
95
+ end
96
+
97
+ def KSExpression.operator(name, keyword, op=nil)
98
+ opClass = Class.new(KSBinaryOperator)
99
+ opClass.setKeyword(keyword)
100
+ const_set(name, opClass)
101
+
102
+ fn = op ? op : name
103
+ class_eval <<-EOS
104
+ define_method(:"#{fn}") {|val| #{name}.new(self, val, @context) }
105
+ EOS
106
+ alias_method name, op if op
107
+ end
108
+
109
+ def KSExpression.binary_function(name, keyword, op = nil, class_to_use = KSFunctionOperator)
110
+ opClass = Class.new(class_to_use)
111
+ opClass.setKeyword(keyword)
112
+ const_set(name,opClass)
113
+
114
+ class_eval <<-EOS
115
+ define_method(:"#{name}") {|*val| #{name}.new(self,val,@context) }
116
+ EOS
117
+ alias_method op, name if op
118
+ end
119
+
120
+ def KSExpression.unary_function(name, keyword, op = nil, class_to_use = KSUnaryFunction)
121
+ opClass = Class.new(KSUnaryFunction)
122
+ opClass.setKeyword(keyword)
123
+ const_set(name,opClass)
124
+
125
+ class_eval <<-EOS
126
+ define_method(:"#{name}") {|*val| #{name}.new(val, @context) }
127
+ EOS
128
+ alias_method op, name if op
129
+ end
130
+
131
+ def KSExpression.unary_operator(name, keyword, op=nil)
132
+ opClass = Class.new(KSUnaryOperator)
133
+ opClass.setKeyword(keyword)
134
+ const_set(name, opClass)
135
+
136
+ class_eval <<-EOS
137
+ define_method(:"#{name}") { #{name}.new(self, @context) }
138
+ EOS
139
+
140
+ alias_method op, name if op
141
+ end
142
+
143
+ class KSOperator < KSExpression
144
+
145
+ def KSOperator.setKeyword(k)
146
+ @keyword = k
147
+ end
148
+
149
+ def KSOperator.keyword
150
+ @keyword
151
+ end
152
+
153
+ def keyword
154
+ self.class.keyword
155
+ end
156
+ end
157
+
158
+ class KSBinaryOperator < KSOperator
159
+ def initialize(a, b, context)
160
+ @a, @b, @context = a, b, context
161
+ end
162
+
163
+ def expr_body
164
+ "(#{@a.expr_body} #{keyword} #{@b.expr_body})"
165
+ end
166
+ end
167
+
168
+ class KSUnaryOperator < KSOperator
169
+ def initialize(a, context)
170
+ @a, @context = a, context
171
+ end
172
+
173
+ def expr_body
174
+ "#{@a.expr_body} #{keyword}"
175
+ end
176
+ end
177
+
178
+ class KSUnaryFunction < KSOperator
179
+ def initialize(a, context)
180
+ @a, @context = a, context
181
+ end
182
+
183
+ def expr_body
184
+ "#{keyword}(#{@a.expr_body})"
185
+ end
186
+ end
187
+
188
+ class KSFunctionOperator < KSOperator
189
+ def initialize(a,b,context)
190
+ @a, @b, @context = a, b, context
191
+ end
192
+
193
+ def expr_body
194
+ "#{@a.expr_body} #{keyword}(#{@b.expr_body})"
195
+ end
196
+ end
197
+
198
+ class KSBetweenFunction < KSFunctionOperator
199
+ def expr_body
200
+ "#{@a.expr_body} #{keyword} #{@b[0].expr_body} AND #{@b[1].expr_body}"
201
+ end
202
+ end
203
+
204
+ end
205
+
206
+ class KSTableExpr < KSExpression
207
+
208
+ def initialize(table, context = nil)
209
+ @table = table
210
+ @context = context ? context : KSExpression::Context.new(table)
211
+ @context.tables << table.table_name
212
+ end
213
+
214
+ def sort_by(*exprs)
215
+ exprs.each do |sort_field|
216
+ if Hash === sort_field
217
+ sort_field.each_pair do |k,v|
218
+ /desc/i.match(v) ? 'DESC' : 'ASC'
219
+ @context.sort_fields.push [k,v]
220
+ end
221
+ else
222
+ @context.sort_fields.push [sort_field,'ASC']
223
+ end
224
+ end
225
+ KSTrueExpr.new(@context)
226
+ end
227
+ alias :order_by :sort_by
228
+
229
+ def limit(*exprs)
230
+ exprs.each do |e|
231
+ @context.limits.push e
232
+ end
233
+ KSTrueExpr.new(@context)
234
+ end
235
+
236
+ def distinct(*exprs)
237
+ exprs.each do |e|
238
+ @context.distinct[e.field] = true
239
+ end
240
+ KSTrueExpr.new(@context)
241
+ end
242
+
243
+ def field(name, *args, &block)
244
+ if match = /^_(.*)/.match(name.to_s)
245
+ func = match[1]
246
+ KSFuncExpr.new(@table,func,@context,args)
247
+ elsif field = @table.fields[name.to_s]
248
+ KSFieldExpr.new(@table, field, @context)
249
+ elsif @table.relations and relation = @table.relations[name.to_s]
250
+ @context.joins << relation.join
251
+ KSTableExpr.new(relation.foreignTable, @context)
252
+ else
253
+ meth = KSExpression.method(name.to_s)
254
+ meth.call(args, &block)
255
+ end
256
+ end
257
+
258
+ def respond_to?(method)
259
+ if match = /^_(.*)/.match(method.to_s)
260
+ true
261
+ elsif field = @table.fields[method.to_s]
262
+ true
263
+ elsif @table.relations and relation = @table.relations[method.to_s]
264
+ true
265
+ else
266
+ KSExpression.respond_to?(method.to_s)
267
+ end
268
+ end
269
+
270
+ def method_missing(method, *args)
271
+ # _blahblah() indicates that blahblah is a function to be invoked
272
+ # on the database side. This is a bit of a hack, but I don't have a
273
+ # better solution in my head at the moment.
274
+ if match = /^_(.*)/.match(method.to_s)
275
+ func = match[1]
276
+ KSFuncExpr.new(@table,func,@context,args)
277
+ elsif field = @table.fields[method.to_s]
278
+ KSFieldExpr.new(@table, field, @context)
279
+ elsif @table.relations and relation = @table.relations[method.to_s]
280
+ @context.joins << relation.join
281
+ KSTableExpr.new(relation.foreignTable, @context)
282
+ elsif KSExpression.respond_to?(method.to_s)
283
+ meth = KSExpression.method(method.to_s)
284
+ meth.call(args)
285
+ else
286
+ raise KSBadFieldName,"KSBadFieldName: '#{method}' is not a valid field name"
287
+ end
288
+ end
289
+
290
+ def expr_body
291
+ @table
292
+ end
293
+ end
294
+
295
+ class KSTrueExpr < KSExpression
296
+ def initialize(context)
297
+ @context = context
298
+ end
299
+
300
+ def expr_body
301
+ '1'
302
+ end
303
+ end
304
+
305
+ class KSFieldExpr < KSExpression
306
+
307
+ def initialize(table, field, context)
308
+ @table, @field, @context = table, field, context
309
+ end
310
+
311
+ def field
312
+ @field
313
+ end
314
+
315
+ def expr_body
316
+ "#{@table.table_name}.#{@field}"
317
+ end
318
+
319
+ alias :old_respond_to? :respond_to?
320
+ def respond_to?(method)
321
+ old_respond_to?(method)
322
+ end
323
+
324
+ def method_missing(method,*args,&block)
325
+ super(method,*args,&block)
326
+ end
327
+ end
328
+
329
+ class KSFuncExpr < KSExpression
330
+ def initialize(table, func, context, args)
331
+ @table, @func, @context, @args = table, func, context, args
332
+ end
333
+
334
+ def expr_body
335
+ "#{@func}(#{@args.join(',')})"
336
+ end
337
+ end
338
+
339
+ # These are the recognized relational operators.
340
+
341
+ KSExpression.operator(:AND, "AND", :&)
342
+ KSExpression.operator(:OR, "OR", :|)
343
+ KSExpression.operator(:LT, "<", :<)
344
+ KSExpression.operator(:GT, ">", :>)
345
+ KSExpression.operator(:LTE, "<=", :<=)
346
+ KSExpression.operator(:GTE, ">=", :>=)
347
+ KSExpression.operator(:LIKE, "LIKE", :=~)
348
+ KSExpression.operator(:EQ, "=", :==)
349
+ KSExpression.operator(:NEQ, "<=>", :<=>)
350
+ KSExpression.operator(:NOTEQ, "!=", :noteq)
351
+ KSExpression.operator(:NOTEQ2, "!=", :'!=')
352
+ KSExpression.unary_operator(:IS_NULL, "IS NULL", :is_null)
353
+ KSExpression.unary_operator(:IS_NOT_NULL, "IS NOT NULL", :is_not_null)
354
+ KSExpression.binary_function(:IN, "IN", :in)
355
+ KSExpression.binary_function(:BETWEEN, "BETWEEN", :between, KSExpression::KSBetweenFunction)
356
+ KSExpression.binary_function(:NOT_IN, "NOT IN", :not_in)
357
+ KSExpression.binary_function(:NOT_BETWEEN, "NOT BETWEEN", :not_between, KSExpression::KSBetweenFunction)
358
+ KSExpression.unary_function(:GREATEST, "GREATEST", :greatest)
359
+ KSExpression.unary_function(:LEAST, "LEAST", :least)
360
+ KSExpression.unary_function(:MIN, "MIN", :min)
361
+ KSExpression.unary_function(:MAX, "MAX", :max)
@@ -0,0 +1,102 @@
1
+ class KSTable
2
+ include DRbUndumped
3
+
4
+
5
+ attr_accessor :row, :context, :rollback_buffer, :rollback_hash
6
+
7
+ def pending_deletion?
8
+ @deletion
9
+ end
10
+
11
+ def read_only?
12
+ @read_only
13
+ end
14
+ alias :read_only :read_only?
15
+
16
+ def read_only=(cond)
17
+ @read_only = cond ? true : false
18
+ end
19
+
20
+ def serialized?
21
+ @serialized
22
+ end
23
+
24
+ def set_pending_deletion
25
+ @deletion = true
26
+ end
27
+
28
+ def reset_pending_deletion
29
+ @deletion = false
30
+ end
31
+
32
+ def set_serialized
33
+ @serialized = true
34
+ end
35
+
36
+ def initialize
37
+ @row = {}
38
+ @rollback_buffer = []
39
+ @rollback_hash = {}
40
+ @serialized = false
41
+ @pending_deletion = false
42
+ @read_only = false
43
+ end
44
+
45
+ def load(row, context = nil, read_only = false)
46
+ @row, @context, @read_only = row, context, read_only
47
+ self
48
+ end
49
+
50
+ def key
51
+ result = self.class.primaries.collect{|f| @row[f.to_s]}
52
+ end
53
+
54
+ def changed
55
+ @context.changed(self) if defined?(@context) && @context
56
+ end
57
+
58
+ def table_name
59
+ self.class.table_name
60
+ end
61
+
62
+ def inspect
63
+ table_name + @row.inspect
64
+ end
65
+
66
+ def delete # TODO: Add a cascade_delete that deletes the row plus any to_many relations to the row.
67
+ if @context.autocommit?
68
+ @context.delete_one(self)
69
+ else
70
+ set_pending_deletion
71
+ changed
72
+ nullify_self
73
+ end
74
+ end
75
+
76
+ def nullify_self
77
+ @rollback_buffer.push self.dup
78
+ @row.each_key do |key|
79
+ @rollback_hash[key] ||= []
80
+ @rollback_hash[key].push @row[key]
81
+ end
82
+
83
+ @row = {}
84
+ end
85
+ # Unwind the changes.
86
+
87
+ def rollback
88
+ @rollback_buffer.reverse.each do |rbval|
89
+ if Array === rbval
90
+ @row[rbval[0]] = rbval[1]
91
+ else
92
+ @row = rbval.row
93
+ reset_pending_deletion
94
+ end
95
+ end
96
+ @rollback_buffer.clear
97
+ @rollback_hash.each_key do |key|
98
+ @rollback_hash[key].clear
99
+ end
100
+ end
101
+
102
+ end