kansas 0.9.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.
@@ -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